简体   繁体   English

将 3 个链表合并为 1 个(Java)

[英]Merging 3 linked lists into 1 (Java )

I have a question for a final review for a coding class I am taking.我有一个关于我正在参加的编码课程的最终审查的问题。 It's asking to merge 3 linked lists into 1 linked list.它要求将 3 个链表合并为 1 个链表。 The problem I am having is when merging the lists I am able to merge the three lists in ascending order but I am missing the last 2 nodes of the 2nd list 23 and 25. I cant figure out why it stops there.我遇到的问题是在合并列表时,我能够按升序合并三个列表,但我缺少第二个列表 23 和 25 的最后 2 个节点。我不知道为什么它停在那里。 The question is here:问题在这里:

Write a program named LinkedTest that:编写一个名为 LinkedTest 的程序:

  1. Creates three sorted singly linked lists of integers as shown below创建三个排序的整数单链表,如下所示
First List:  2 11 19 21 24

Second List: 14 15 18 23 25

Third List:  3 9 17 20 22
  1. Merges three linked lists into a new sorted linked list as show in the following: 2 3 9 11 14 15 17 18 19 20 21 22 23 24 25将三个链表合并成一个新的排序链表,如下所示: 2 3 9 11 14 15 17 18 19 20 21 22 23 24 25
  2. Returns the new sorted linked list Requirement: your program must have a time complexity less than or equal to O( nlog n )返回新排序的链表要求:你的程序的时间复杂度必须小于或等于 O( nlog n )

Here is my code:这是我的代码:

public class LinkedTest {

public static class ListNode {

    private int data;
    ListNode next;

    public ListNode(int data) {
        this.data = data;
        next = null;
    }
}

ListNode head;

public static void main(String[] args) {

    LinkedTest list = new LinkedTest();

        int[] data1 = { 2, 11, 19, 21, 24 };

        ListNode head1 = new ListNode(data1[0]);

        for (int i = 1; i < data1.length; i++)
            list.push(head1, data1[i]);

        System.out.print("First List: ");
        list.display(head1);

        int[] data2 = { 14, 15, 18, 23, 25 };

        ListNode head2 = new ListNode(data2[0]);

        for (int count = 1; count < data2.length; count++)
            list.push(head2, data2[count]);

        System.out.println(" Second List: ") ;

        list.display(head2);

        int[] data3 = { 3, 9, 17, 20, 22 };

        ListNode head3 = new ListNode(data3[0]);

        for (int count = 1; count < data3.length; count++)
            list.push(head3, data3[count]);

        System.out.println(" Third List: ") ;

        list.display(head3);

        ListNode n = list.LinkedTest(head1, head2, head3);

        System.out.print(" Merged List: ");

        list.display(n);
    }



public ListNode LinkedTest(ListNode first, ListNode second, ListNode third) { 
      ListNode head = null;

        if (first == null && second != null && third != null)

            return second;

        else if (second == null && third != null && first != null)

            return third;

        else if (third == null && first != null && second != null)

            return first;

        else if (first.data < second.data && first.data < third.data) 
        {
            head = first;
            head.next = LinkedTest(first.next, second, third);
        } 
        else if (second.data < third.data && second.data < first.data)
        {
            head = second;
            head.next = LinkedTest(first, second.next, third);
        }

        else if (third.data < first.data && third.data < second.data)
        {
            head = third;
            head.next = LinkedTest(first, second, third.next);
        }

        return head;
    }

    public void push(ListNode head, int n) 
    {
        while (head.next != null)
            head = head.next;
        head.next = new ListNode(n);
    }

    public void display(ListNode head)
    {
        ListNode tempDisplay = head; 
        while (tempDisplay != null) 
        {
            System.out.print(tempDisplay.data);
            tempDisplay = tempDisplay.next; 
    }

    }
}

Output:输出:

First List:   2 11 19 21 24 
Second List:  14 15 18 23 25 
Third List:   3 9 17 20 22 
Merged List:  2 3 9 11 14 15 17 18 19 20 21 22 24

Why limit yourself to 3-way merges?为什么要限制自己进行 3 向合并? Let us look at the general case of N-way merging, and then apply that to 3-way (or plain old boring 2-way).让我们看一下 N 路合并的一般情况,然后将其应用于 3 路(或普通的无聊的 2 路)。

// drop-in replacement for your LinkedTest(), but I like the name better:
// ListNode n = list.merge(head1, head2, head3);
public ListNode merge(ListNode... nodes) {

    // find smallest, keep its index
    int firstIndex = -1;
    int firstValue = 0;
    for (int i=0; i<nodes.length; i++) {
        ListNode n = nodes[i];
        if (n != null && (firstIndex == -1 || n.data < firstValue)) {
            firstIndex = i;
            firstValue = n.data;
        }
    }

    if (firstIndex == -1) {
        // reached the end of all lists
        return null;
    } else {
        // use node with smallest as next head
        ListNode head = nodes[firstIndex];

        // call again with all lists, but skipping head
        // because we are already using it in the result list
        nodes[firstIndex] = head.next;
        head.next = merge(nodes);
        return head;
    }
}

You can look at this like a lot of chains with links that have numbers on them, where the numbers of each chain are in ascending order.您可以将其视为许多带有链接的链,链上带有数字,其中每条链的编号按升序排列。 To build a big chain with all numbers in order, you:要构建一个包含所有数字的大链,您需要:

  • grab all the small ends of the remaining chains抓住剩余链子的所有小端
  • choose the smallest link (if none remains, you have finished)选择最小的链接(如果没有剩余,则已完成)
  • take that link off its chain, and add it to the end of your new chain将该链接从其链上取下,并将其添加到新链的末尾
  • go back to choosing the smallest link of the old chains回到选择旧链中最小的环节

In your answer, as Jacob_G has noted, you were missing some logic for choosing the correct smallest element when one or more lists were empty.在您的回答中,正如 Jacob_G 所指出的,当一个或多个列表为空时,您缺少一些选择正确最小元素的逻辑。 Jacob's answer is fine, but I though I'd show you the bigger picture: if you understand it for N , 3 should be no stretch (plus, you gain insight on the general idea).雅各布的回答很好,但我虽然我会向您展示更大的图景:如果您理解N3应该没有延伸(另外,您对总体思路有了深入了解)。

You're given three sorted lists and are asked to merge them into a single, sorted list .您将获得三个已排序的列表,并要求将它们合并为一个已排序的列表 The algorithm to achieve this is very simple.实现这一点的算法非常简单。

I recommend keeping track of three indexes (one for each input list ) and initialize them all to 0 (the first element of each list ).我建议跟踪三个索引(每个输入列表一个)并将它们全部初始化为0 (每个列表的第一个元素)。 Because each list contains the same number of elements, you can simply iterate from 0 to 3n , where n is the size of one of the list s.因为每个列表包含相同数量的元素,您可以简单地从0迭代到3n ,其中n列表s 之一的大小。

Inside the loop, you want to find the minimum element between the 3 head elements of each list by using the respective indexes.在循环内部,您希望使用各自的索引找到每个列表3个头元素之间的最小元素。 To do this, simply look at the elements and compare them against one-another.为此,只需查看元素并将它们相互比较即可。 Once you have found the minimum, you can append that element to your merged list and increment the respective list 's index (this is important as to not append the same element twice).找到最小值后,您可以将该元素附加到合并列表中并增加相应列表的索引(这很重要,因为不要两次附加相同的元素)。

You'll repeat this 3n times (once for each element in the three lists ), and the result will be a single merged list that is sorted in ascending order.您将重复此操作3n次(三个列表中的每个元素一次),结果将是一个按升序排序的合并列表

Assuming each input list will always be equal in size, this algorithm is O(3n) which is simplified to O(n) .假设每个输入列表的大小总是相等,这个算法是O(3n) ,它被简化为O(n)

A pseudo-code solution might look something like this:伪代码解决方案可能如下所示:

list1 = [2, 11, 19, 21, 24]
list2 = [14, 15, 18, 23, 25]
list3 = [3, 9, 17, 20, 22]
merged_list = []

indexes = [0, 0, 0]

for i in 0..15:
    minValue = Integer.MAX_VALUE
    minIndex = -1

    if indexes[0] < len(list1) and list1[indexes[0]] < minValue:
        minValue = list1[indexes[0]]
        minIndex = 0

    if indexes[1] < len(list2) and list2[indexes[1]] < minValue:
        minValue = list2[indexes[1]]
        minIndex = 1

    if indexes[2] < len(list3) and list3[indexes[2]] < minValue:
        minValue = list3[indexes[2]]
        minIndex = 2

    merged_list += minValue
    indexes[minIndex]++

Your problem here is you didn't write all the conditions.你的问题是你没有写出所有的条件。 Let's look at your use case.让我们看看您的用例。

First List: 2 11 19 21 24第一个列表:2 11 19 21 24

Second List: 14 15 18 23 25第二个名单:14 15 18 23 25

Third List: 3 9 17 20 22第三个名单:3 9 17 20 22

According to your code logic, in 1st merge, we get a 2 in first.data < second.data .根据您的代码逻辑,在第一次合并中,我们在first.data < second.data得到2 Next turn we have 11 < 14 in the same location.下一回合,我们在同一位置有 11 < 14。 But after that we now have但在那之后我们现在有
19 21 24 19 21 24
14 15 18 23 25 14 15 18 23 25
3 9 17 20 22 3 9 17 20 22
and none of the conditions will be satisfied in next turns.并且在接下来的回合中都不会满足任何条件。

I imagine that your code was modified from the code for merging two lists.我想你的代码是从合并两个列表的代码中修改而来的。 You should recall why each piece of the code works, and think why your code isn't working the way you think it should work.您应该回忆为什么每段代码都有效,并思考为什么您的代码没有按照您认为应该的方式工作。

Let's assume that your original algorithm looks like this:让我们假设您的原始算法如下所示:

public ListNode LinkedTest(ListNode first, ListNode second) {

    ListNode head = null;

    if (first == null)

        return second;

    else if (second == null)

        return first;

    else if (first.data < second.data) 
    {
        head = first;
        head.next = LinkedTest(first.next, second);
    } 
    else if (second.data < first.data)
    {
        head = second;
        head.next = LinkedTest(first, second.next);
    }

    return head;
}

The first half of the method checks for the need to terminate the recursion.该方法的前半部分检查是否需要终止递归。 Here, the code terminates when one of the two lists has reached the end.在这里,当两个列表之一到达末尾时,代码终止。 For example, if you are merging the list [1, 3, 5, 7] with the list [2, 4, 6, 8, 9, 10] , you are going to end up with merging the list [] with the list [8, 9, 10] .例如,如果您将列表[1, 3, 5, 7]与列表[2, 4, 6, 8, 9, 10]合并,则最终将列表[]与列表合并[8, 9, 10] That's when you will need to terminate by returning the list and therefore completing the recursion.那是您需要通过返回列表并因此完成递归来终止的时候。

The modification you have made to the algorithm is incorrect because in a three-way merge, you cannot simply return the second list when the first list is out of nodes.您对算法所做的修改是不正确的,因为在三向合并中,当第一个列表超出节点时,您不能简单地返回第二个列表。 The termination criteria has become more complicated and you should pay attention to when you really need to end your recursion.终止条件变得更加复杂,您应该注意何时真正需要结束您的递归。 For example, if you have are try to merge [1, 2] , [3, 4] , [5, 6] , assuming the other part of the algorithm works, then you will end up with a merge between [] , [3, 4] , [5, 6] for which you are going to return [3, 4] and discard [5, 6] , which is not correct.例如,如果您尝试合并[1, 2] , [3, 4] , [5, 6] ,假设算法的其他部分有效,那么您最终会在[] , [3, 4] , [5, 6] ,您将返回[3, 4]并丢弃[5, 6] ,这是不正确的。

One solution is to call the two-list version of the algorithm when any one list is out.一种解决方案是在任何一个列表出现时调用该算法的两个列表版本。 This requires more code (that is quite repetitive too), but is more straightforward coming from this particular code.这需要更多的代码(这也是非常重复的),但是来自这个特定的代码更直接。 Another solution is to terminate only when two lists are empty, but this requires special care in the recursive case to check for null values.另一种解决方案是仅在两个列表为空时才终止,但这在递归情况下需要特别注意以检查空值。


The second half of the method compares between the first elements of the lists and uses the smaller element as the head of the new list, then assigns the next data member of the head node to "whatever the result of merging the rest of the lists is".该方法的后半部分比较列表的第一个元素,并使用较小的元素作为新列表的头部,然后将头节点的next数据成员分配给“无论合并其余列表的结果是什么” ”。 Note that in this version of the code, exactly one of the code blocks is executed every time.请注意,在此版本的代码中,每次只执行其中一个代码块。 (Unless the data are equal, then there would be a bug. Fixing it is left as an exercise.) As long as the recursion is not terminated, we need to go deeper, right! (除非数据相等,否则会有bug。修复它留作练习。)只要不终止递归,我们就需要更深入,对吧!

The modification that you made does not work because you are comparing the first data to the second data, then the second to the third.. then what?您所做的修改不起作用,因为您将第一个数据与第二个数据进行比较,然后将第二个数据与第三个数据进行比较……然后呢? Let's make a table, trace the code and record how it behaves vs how you would want the code to behave.让我们制作一个表格,跟踪代码并记录它的行为方式与您希望代码的行为方式。

| Order | Your code   | Expected | Result  |
|-------|-------------|----------|---------|
| A<B<C | head = A    | head = A | Correct |
| A<C<B | head = A    | head = A | Correct |
| B<A<C | head = B    | head = B | Correct |
| B<C<A | head = B    | head = B | Correct |
| C<A<B | head = A    | head = C | WRONG!  |
| C<B<A | head = null | head = C | WRONG!  | * note: no recursion in this case

See how your code fails whenever C is the smallest element?当 C 是最小元素时,看看您的代码如何失败? You need to fix this too.你也需要解决这个问题。


Finally, there's one solution that doesn't even require creating a 3-way merge.最后,有一种解决方案甚至不需要创建 3 路合并。 Technically, merge(A, merge(B, C)) should still run at O(n) time complexity behaves correctly too.从技术上讲, merge(A, merge(B, C))应该仍然以 O(n) 时间复杂度运行,并且行为也正确。

Good luck on your finals!祝你总决赛好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM