Javatpoint Logo
Javatpoint Logo

Flattening a Linked List in C++

In this article, you will learn about the flattening a linked list in C++ with different approaches and examples.

Flattening a linked list in C++ means converting a linked list of linked lists into a single, sorted linked list. It is a common problem in data structures and algorithms. A linked list of linked lists is a structure where each node of the outer linked list points to a separate linked list, and you want to merge these individual linked lists into a single sorted linked list. There are several approaches for flattening a linked list in C++. Some main approaches for flattening a linked list are as follows:

Approach-1: Merge sort and Recursion

This approach recursively flattens the linked list and uses the merge function to merge individual linked lists in a sorted manner. The resulting linked list will be a single sorted linked list containing all the elements from the original linked list of linked lists.

Program:

Let's take an example to illustrate the merge sort and recursion function in C++.

Output:

5 7 10 19 20 28 35 40 45

Explanation:

  • In this example, each node has three components: data, which stores the value of the node, next, a pointer to the next node in the same linked list, and down, a pointer to the next linked list.
  • The merge function takes two sorted linked lists (a and b) as input and merges them into a single sorted linked list.
  • It uses recursive calls to compare the data of nodes from the two lists and merge them accordingly.
  • The result of merging is a new linked list that maintains the sorted order.
  • The flatten function is responsible for flattening the linked list of linked lists.
  • It takes the head of the linked list as input and recursively flattens the rest of the list.
  • It first checks if the current list is empty or has only one element (!head || !head->next), in which case it returns the list as it is.
  • If there are more linked lists to be flattened (head->next), it recursively flattens the remaining part and then merges the current list with the flattened list using the merge function.
  • The insert function is a utility function used to insert a new node with a specified data value at the end of a linked list.
  • It checks if the linked list is empty (!head), in which case it sets the new node as the head.
  • Otherwise, it traverses the linked list to find the last node and appends the new node to the end.
  • The display function is used to print the flattened linked list.
  • It traverses the flattened list from the head to the end while printing each node's data value.
  • In the main function, we create a sample linked list of linked lists using the insert function. We insert various elements into the linked lists in a hierarchical manner.
  • After that, we call the flatten function to flatten the list.
  • Finally, we display the flattened list using the display function.

Complexity Analysis:

Time Complexity:

  1. The merge function has a time complexity of O(n + m), where n and m are the sizes of the two linked lists being merged.
  2. The flatten function recursively merges linked lists. Suppose there are 'k' linked lists and each linked list contains an average of 'n' elements. The time complexity is O(k * n * log(k)) because the merging process is performed recursively on each level of the hierarchy.
  3. The insert function inserts elements one by one, and for each insertion, it traverses the linked list to find the end. Therefore, the time complexity of insertion is O(n), where 'n' is the size of the linked list at the time of insertion.
  4. The display function iterates through the flattened linked list once, so it has a time complexity of O(k * n), where 'k' is the number of linked lists and 'n' is the total number of elements in the flattened list.
  5. Overall, the time complexity of the entire code can be approximated as O(k * n * log(k)).

Space Complexity:

  1. The merge function has a space complexity of O(n + m), where 'n' and 'm' are the sizes of the two linked lists being merged. This space is used for the recursive function call stack.
  2. In the worst case, the space complexity is O(k), where 'k' is the number of linked lists.
  3. The insert function allocates space for a new node for each insertion. In the worst case, the space complexity is O(n), where 'n' is the number of elements inserted.
  4. The display function uses a constant amount of extra space, so its space complexity is O(1).
  5. Overall, the space complexity of the code depends on the number of linked lists ('k') and the total number of elements ('n') in the flattened list. In the worst case, the space complexity can be approximated as O(k + n).

Approach-2: Using Priority queue.

A priority queue is a data structure that allows elements with a certain priority to be inserted and extracted efficiently. In this case, the priority will be determined by the data value of the nodes.

  1. Create a min-heap (priority queue) to efficiently manage the next nodes from the linked lists.
  2. Initialize an empty result list that will store the flattened linked list.
  3. Initially, insert the head of each linked list into the priority queue.

While the priority queue is not empty:

  1. Extract the node with the smallest data value from the priority queue.
  2. Add this node to the result list.
  3. If this node has a "down" pointer (i.e., it's part of a linked list), insert the next node from the same linked list into the priority queue.

Continue this process until the priority queue is empty.

Program:

Let's take an example to demonstrate the flattening a linked list using priority queue in C++.

Output:

5 7 10 19 20 28 35 40 45

Explanation:

  • The code defines a Node structure to represent nodes in the linked list. Each node has an integer data, a pointer to the next node in the same linked list (next), and a pointer to the next linked list (down).
  • A custom comparison function named NodeComparison is defined to compare nodes based on their data values in a way that ensures that the node with the smallest data value is at the top of the priority queue (min-heap).
  • This function is responsible for flattening the linked list of linked lists using a priority queue.
  • It takes the head of the linked list of linked lists as input.
  • A min-heap (priority queue) minHeap is created to efficiently manage nodes based on their data values.
  • The function iterates through the outer linked list to push the top nodes of each inner linked list into the priority queue.
  • A dummy node (dummy) is created to simplify list construction, and a tail pointer is used to keep track of the last node in the result list.
  • In the while loop, nodes are popped from the priority queue in order of their data values, and they are appended to the result list. If a node has a "down" pointer, the next node from the same linked list is pushed into the priority queue.
  • The result list is returned, excluding the dummy node.
  • The insert function is a utility function used to insert a new node at the end of a linked list.
  • It checks if the linked list is empty or not and appends the new node accordingly.
  • The display function is a utility function to print the flattened linked list.
  • In the main function, a sample linked list of linked lists is created using the insert function. Various elements are inserted into the linked lists in a hierarchical manner.
  • The flattenUsingPriorityQueue function is called to flatten the list.
  • Finally, the flattened list is displayed using the display function.

Complexity Analysis:

Time Complexity:

  1. The for loop that iterates through the outer linked list and pushes the top nodes of each inner linked list into the priority queue has a time complexity of O(k * log(k)), where 'k' is the number of linked lists. The insertion operation into a priority queue takes O(log(k)) time.
  2. The while loop runs until the priority queue is empty. In each iteration, it pops a node from the priority queue and pushes the next node from the same linked list into the queue (if applicable). The number of iterations is determined by the total number of nodes in the linked lists, which is 'n.' Therefore, the time complexity of the while loop is O(n * log(k)) in the worst case.
  3. Overall, the time complexity of the code is O(k * log(k) + n * log(k)).

Space Complexity:

  1. The priority queue (minHeap) is used to store at most 'k' nodes at any given time. The space complexity for the priority queue is O(k).
  2. A new flattened linked list is constructed during the merging process. The space required to store this list is O(n), where 'n' is the total number of nodes in the flattened list.
  3. The code uses a few additional variables and pointers, but their space complexity is constant (O(1)).
  4. Overall, the space complexity of the code is O(k + n), where 'k' is the number of linked lists, and 'n' is the total number of nodes in the flattened list.

Approach-3: Using stack

This approach is typically more memory-efficient than using a priority queue and is suitable for both sorted and unsorted linked lists. In this approach, we utilize a stack to keep track of the nodes at the current level (linked list).

We start by pushing the top nodes of each linked list onto the stack. As we process the top node, we push the next node from the same linked list onto the stack if it exists. This process continues until the stack is empty.

Program:

Let's take an example to demonstrate the flattening a linked list using stack in C++.

Output:

5 7 10 19 20 28 30 35 40 45

Explanation:

  • In this program, the Node structure is used to represent the nodes of the linked list. Each node contains an integer data, a pointer to the next node in the same linked list (next), and a pointer to the next linked list (down).
  • This function is used to merge two sorted linked lists and return the merged result.
  • It takes two pointers a and b, which represent the heads of the two sorted lists to be merged.
  • The function compares the data values of the nodes in a and b. It recursively selects the smaller value and appends it to the result. The down pointers are updated to merge the sublists.
  • It is the main function for flattening the linked list.
  • It uses a stack to manage nodes while flattening and sorting the linked list.
  • Initially, it pushes the head node of each list onto the stack.
  • After that, it iteratively pops two nodes from the stack, merges them using the mergeSortedLists function, and pushes the result back onto the stack.
  • This process continues until there is only one list left in the stack.
  • This utility function is used to insert a new node at the end of a list. It appends a new node with the specified data value to the end of an existing list.
  • This utility function is used to display the flattened linked list by iterating through it and printing the data values of each node.
  • In the main function, a sample linked list of linked lists is constructed using the insert function. Various elements are added to the linked lists to create a hierarchical structure.
  • The flattenUsingStack function is called to flatten and sort the list. The result is stored in the head variable.
  • Finally, the display function is used to print the flattened and sorted linked list.
  • The output of the code will be the flattened and sorted linked list in ascending order. The code uses a stack to efficiently manage the nodes and ensures that the flattened list is sorted correctly.

Complexity Analysis:

Time Complexity:

  1. The flattenUsingStack function pushes and pops nodes in the stack, and for each pair of nodes, it calls mergeSortedLists. In the worst case, the stack size may be proportional to the total number of nodes in the linked lists, which we'll denote as 'N'. As a result, the time complexity is O(N * (m + n)), where 'N' is the total number of nodes, and 'm' and 'n' are the sizes of the lists being merged.
  2. These functions iterate through the linked lists once. Their time complexity is O(N), where 'N' is the total number of nodes.
  3. Overall, the time complexity of the entire code is O(N * (m + n)).

Space Complexity:

  1. The space used by the stack is proportional to the depth of the linked list of linked lists, which is equivalent to the number of linked lists ('k'). The maximum space required for the stack is O(k).
  2. A new flattened and sorted linked list is constructed during the merging process. The space required to store this list is O(N), where 'N' is the total number of nodes in the flattened list.
  3. The code uses a few additional variables and pointers, but their space complexity is constant (O(1)).
  4. Overall, the space complexity of the code is O(k + N).

Approach-4: Using In-place Rearrangement method

The "In-Place Rearrangement Method" is an approach to flattening a linked list of linked lists where the goal is to rearrange the pointers within the existing nodes to create a flattened linked list without using additional data structures. This method modifies the original linked list structure.

Program:

Let's take an example to demonstrate the flattening a linked list using in-place rearrangement method in C++.

Output:

5 7 10 19 20 28 30 35 40 45

Explanation:

  • In this example, the Node structure is used to represent the nodes of the linked list. Each node contains an integer data, a pointer to the next node in the same linked list (next), and a pointer to the next linked list (down).
  • This function is used to merge two sorted linked lists into a single sorted list. It's implemented as a recursive
  • It takes two pointers, a and b, which represent the heads of the two sorted lists to be merged.
  • The down pointers are also updated to merge the sublists.
  • It is the main function for flattening and sorting the linked list. It's implemented as a recursive function.
  • It recursively flattens and sorts the list by merging pairs of lists.
  • The base case is when there's only one list, or when the list has only one node (i.e., head or head->next is null).
  • In the recursive case, it recursively flattens and merges the current list with the result of the flattened next list.
  • This utility function is used to insert a new node at the end of a list. It appends a new node with the specified data value to the end of an existing list.
  • This utility function is used to display the flattened linked list by iterating through it and printing the data values of each node.
  • Finally, the display function is used to print the flattened and sorted linked list.
  • The output of the code will be the flattened and sorted linked list in ascending order. The code efficiently flattens the linked lists using a merge sort-like approach.

Complexity Analysis:

Time complexity:

  1. The time complexity of merging two sorted linked lists with m and n elements is O(m + n). In the worst case, it's called for all pairs, so its time complexity is O(N * (m + n)), where 'N' is the total number of nodes, and 'm' and 'n' are the sizes of the lists being merged.
  2. The flattenAndSort function recursively flattens and sorts the linked list. In the worst case, it's called for each level of the linked list hierarchy, which results in a time complexity of O(k * N * (m + n)), where 'k' is the number of levels.
  3. These functions iterate through the linked lists once. Their time complexity is O(N), where 'N' is the total number of nodes.
  4. Overall, the time complexity of the entire code is O(k * N * (m + n)).

Space Complexity:

  1. The code uses recursion for both mergeSortedLists and flattenAndSort. The space used for the recursion stack is proportional to the depth of the linked list of linked lists, which is equivalent to the number of linked lists ('k'). The maximum space required for the recursion stack is O(k).
  2. The space required to store the sorted, flattened list is O(N), where 'N' is the total number of nodes in the flattened list.
  3. The code uses a few additional variables and pointers, but their space complexity is constant (O(1)).
  4. Overall, the space complexity of the code is O(k + N).

Approach-5: Using a Dummy Node and Recursion

The "Using a Dummy Node and Recursion" method is an approach to flatten a linked list of linked lists while maintaining sorted order without modifying the original structure. This method uses a dummy node to merge the sorted linked lists in a recursive manner.

Program:

Let's take an example to demonstrate the flattening a linked list using a Dummy Node and Recursion in C++.

Output:

5 7 10 19 20 30 35 40 45

Explanation:

  • In this program, the Node structure represents the nodes of the linked list, with data for the value, next pointing to the next node at the same level, and down pointing to the next linked list (sublist).
  • It merges two sorted linked lists using a dummy node. It iterates through the lists, selecting the smaller value to create a merged list. The down pointers are updated for sublists.
  • The main function for flattening and sorting recursively divides the input list into two parts, processes each part, and merges them using mergeSortedLists. It maintains sorted order.
  • The insert function appends a new node to the end of a list and display prints the flattened linked list.
  • It constructs a sample hierarchical linked list comprising various elements and sublists.
  • The code calls flattenUsingDummyAndRecursion on the head of the hierarchical list. Sublists are flattened, and the merged result is sorted in ascending order.
  • The sorted, flattened linked list is displayed as the final output.
  • This method efficiently preserves the original structure while creating a sorted flattened list, making it suitable for applications requiring both properties.

Complexity Analysis:

Time Complexity:

The time complexity of this code is primarily determined by two key functions: mergeSortedLists and flattenUsingDummyAndRecursion.

mergeSortedLists Function:

  1. The time complexity of merging two sorted linked lists with lengths 'm' and 'n' is O(m + n). In the code, this function is called recursively during the flattening process for each pair of lists.
  2. The overall time complexity for mergeSortedLists within the flattenUsingDummyAndRecursion function is O(N * (m + n)), where 'N' is the total number of nodes in the linked list, and 'm' and 'n' represent the sizes of the lists being merged.

flattenUsingDummyAndRecursion Function:

  1. This function recursively flattens and sorts the linked list. It's called for each level in the hierarchical structure, which involves the entire list.
  2. The worst-case time complexity of this function is O(k * N * (m + n)), where 'k' is the number of levels in the hierarchical structure.

Space Complexity:

  1. The code uses recursion for both mergeSortedLists and flattenUsingDummyAndRecursion. The space used for the recursion stack is proportional to the depth of the linked list hierarchy, which is equivalent to the number of linked lists ('k'). The maximum space required for the recursion stack is O(k).
  2. The space required to store the sorted, flattened list is O(N), where 'N' is the total number of nodes in the flattened list.
  3. The code uses a few additional variables and pointers, but their space complexity is constant (O(1)).
  4. Overall, the space complexity of the code is O(k + N), where 'k' represents the depth of the hierarchy, and 'N' is the total number of nodes in the flattened list. The code efficiently flattens and sorts the linked list while preserving the original structure.

Approach-6: Using Auxiliary array

The "Using an Auxiliary Array" method is an approach to flatten a linked list of linked lists while maintaining sorted order. Unlike other methods, this approach does not modify the original structure. Instead, it collects the values from the linked list, sorts them using an auxiliary array, and then creates a new linked list from the sorted values.

Program:

Here's the C++ code that uses an auxiliary array to flatten a linked list of linked lists while maintaining a sorted order.

Output:

5 7 10 19 20 30 35 40 45

Explanation:

  • In this program, the Node structure represents the nodes of the linked list, with data for the value, next pointing to the next node at the same level, and down pointing to the next linked list (sublist).
  • It is the central function responsible for flattening and sorting the linked list.
  • It first traverses the input hierarchical linked list, collecting all the values and storing them in a dynamic array called values.
  • The collected values array is sorted using a standard sorting algorithm (std::sort), ensuring that the values are in ascending order.
  • A new linked list (newHead) is initialized to hold the flattened and sorted values.
  • The code iterates through the sorted values and creates new nodes for each value, which are added to the end of the newHead
  • The tail pointer tracks the last node of the list, allowing for efficient node insertion.
  • The code includes two utility functions: insert, which inserts a new node at the end of a list, and display, which prints the flattened linked list.
  • In the main function, a sample hierarchical linked list is constructed by inserting various elements and sublists.
  • The flattenUsingAuxiliaryArray function is called on the head of the hierarchical list. The function processes and sorts the values into a new linked list.
  • The code prints the sorted, flattened linked list as the final output.
  • This method preserves the original structure of the linked lists while creating a sorted flattened list. However, it has a space complexity associated with the auxiliary array, which can be a drawback for large linked lists.

Complexity Analysis:

Time Complexity:

Collecting Values: The code traverses the entire hierarchical linked list to collect values. It requires visiting each node, which results in a time complexity of O(N), where 'N' is the total number of nodes in the hierarchical linked list.

Sorting the Values: The sorting operation is performed on the collected values using std::sort. The time complexity for this operation is typically O(N * log(N)), where 'N' represents the number of values to be sorted.

Creating the Flattened List: After sorting, the code iterates through the sorted values to create a new linked list. The creation process also takes O(N) time since it visits each value once.

Overall, the time complexity of the code is dominated by the sorting operation, making it O(N * log(N)) in the worst case.

Space Complexity:

Values Array: The code uses an auxiliary array (values) to collect the values from the linked list. The space required for this array is O(N) because it stores 'N' values.

Flattened List: The code creates a new linked list to store the flattened and sorted values. The space required for this list is also O(N) as it contains 'N' nodes.

Additional Space: The code uses a few additional variables and pointers, but their space complexity is constant (O(1)).

The overall space complexity of the code is O(N), where 'N' is the total number of nodes in the hierarchical linked list.

Conclusion:

1. Merge Sort and Recursion:

Method Description: This approach uses a merge sort-like technique along with recursion to flatten and sort the linked list. It's efficient and works well for sorted linked lists.

Pros: Preserves original structure, works for sorted lists and is relatively efficient.

Cons: May not be the most space-efficient method.

Time and Space Complexity: Time complexity is O(N * log(N)) in the worst cases. Space complexity depends on the method used but can be O(N).

2. Using Priority Queue:

Method Description: This method involves using a priority queue to merge and sort linked lists during the flattening process.

Pros: Efficient for unsorted lists, low space complexity.

Cons: Modifies original structure and may not be as efficient for sorted lists.

Time and Space Complexity: Time complexity is O(N * log(K)) where 'K' is the number of linked lists. Space complexity is O(K) due to the priority queue.

3. Using a Stack:

Method Description: This method combines a stack with recursion to flatten and sort the linked list. It's a hybrid approach that can be adapted to various scenarios.

Pros: Preserves the original structure, adaptable for different requirements.

Cons: Recursion may use additional stack space, can be complex.

Time and Space Complexity: Time complexity depends on the specific implementation. Space complexity depends on the depth of recursion and stack usage.

4. In-Place Rearrangement:

Method Description: This approach rearranges pointers within the nodes to create a flattened linked list without using additional data structures.

Pros: Preserves the original structure in place, efficient use of space.

Cons: May be more complex to implement and understand.

Time and Space Complexity: Time complexity is O(N) due to a single traversal. Space complexity is O(1) because it modifies the existing structure.

5. Using a Dummy Node and Recursion:

Method Description: This method uses a dummy node and recursive merging to flatten and sort the linked list.

Pros: Preserves original structure, easy to implement.

Cons: Recursion can use additional stack space.

Time and Space Complexity: Time complexity is O(N * log(N)) in the worst cases. Space complexity is O(N) due to recursion.

6. Using an Auxiliary Array:

Method Description: This approach collects values from the linked list, sorts them using an auxiliary array, and creates a new linked list from the sorted values.

Pros: Preserves the original structure, easy to understand.

Cons: It may not be suitable for large lists.

Time and Space Complexity: Time complexity is O(N * log(N)) for sorting. Space complexity is O(N) due to the auxiliary array.







Youtube For Videos Join Our Youtube Channel: Join Now

Feedback


Help Others, Please Share

facebook twitter pinterest

Learn Latest Tutorials


Preparation


Trending Technologies


B.Tech / MCA