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

时间:2018-12-19 02:04:49

标签: java algorithm merge sortedlist

我有一个问题需要对我正在参加的编码课程进行最终审查。它要求将3个链接列表合并为1个链接列表。我遇到的问题是合并列表时,我能够按升序合并三个列表,但是我错过了第二个列表23和25的最后两个节点。我无法弄清楚为什么它停在那里。问题在这里:

编写一个名为LinkedTest的程序,

  1. 创建三个按整数排序的单链接列表,如下所示

(第一名)2 11 19 21 24

(第二列表)14 15 18 23 25

(第三列表)3 9 17 20 22

  1. 将三个链表合并到一个新的排序链表中,如下所示:2 3 9 11 14 15 17 18 19 20 21 22 23 24 25
  2. 返回新的排序链表 要求:您的程序的时间复杂度必须小于或等于O(nlog n)

这是我的代码:

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; 
    }

    }
}

输出:

第一名单:211192124 第二名单:1415182325 第三名单:39172022 合并清单:23911141517181920212224

4 个答案:

答案 0 :(得分:0)

系统会为您提供三个排序的列表,并要求您将它们合并为一个排序的列表。实现此目的的算法非常简单。

我建议跟踪三个索引(每个输入 list 一个),并将它们全部初始化为0(每个 list 的第一个元素)。由于每个 list 包含相同数量的元素,因此您可以简单地从0迭代到3n,其中n之一的大小列表

在循环内部,您想通过使用各自的索引来找到每个 list 3 head 个元素之间的最小元素。为此,只需查看元素并将它们与另一个元素进行比较。找到最小值后,可以将该元素追加到合并列表中,并增加相应的 list 的索引(这对于避免两次相同的元素都非常重要)。

您将重复3n次(对三个列表中的每个元素一次),结果将是一个合并的 list 升序排序。

假设每个输入 list 的大小始终相等,则此算法为O(3n),简化为O(n)

伪代码解决方案可能看起来像这样:

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]++

答案 1 :(得分:0)

您的问题是您没有写所有条件。 让我们看看您的用例。

第一个列表: 2 11 19 21 24

第二个列表: 14 15 18 23 25

第三列表: 3 9 17 20 22

根据您的代码逻辑,在第一次合并中,我们在2中得到一个first.data < second.data。下一回合,我们在相同位置有11 <14。但是之后我们有了
19 21 24
14 15 18 23 25
3 9 17 20 22
下一轮将不满足任何条件。

答案 2 :(得分:0)

我想您的代码是从合并两个列表的代码修改而来的。您应该回想一下为什么每段代码都能工作,并思考为什么代码无法按照您认为的方式工作。

让我们假设您的原始算法如下:

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;
}

方法的前半部分检查是否需要终止递归。在此,当两个列表之一到达末尾时,代码终止。例如,如果要将列表[1, 3, 5, 7]与列表[2, 4, 6, 8, 9, 10]合并,则最终将列表[]与列表[8, 9, 10]合并。那时候您将需要通过返回列表来终止并因此完成递归。

您对算法所做的修改是不正确的,因为在三向合并中,当第一个列表不在节点中时,您不能简单地返回第二个列表。终止条件变得更加复杂,您确实需要结束递归时应该注意。例如,如果您尝试合并[1, 2][3, 4][5, 6],并假设算法的另一部分有效,那么最终将在{{1}之间进行合并},[][3, 4],您将为其返回[5, 6]并丢弃[3, 4],这是不正确的。

一种解决方案是在任一列表出局时调用算法的两列表版本。这需要更多的代码(这也是非常重复的),但是从这个特定的代码来看更直接。另一种解决方案是仅在两个列表为空时才终止,但这在递归情况下需要特别注意以检查空值。


该方法的后半部分在列表的第一个元素之间进行比较,并使用较小的元素作为新列表的头,然后将头节点的[5, 6]数据成员分配给“无论结果如何”合并其余列表为”。请注意,在此版本的代码中,每次都会精确执行其中一个代码块。 (除非数据相等,否则将存在一个错误。应将其作为练习来解决。)只要递归没有终止,我们就需要更深入,对!!

您所做的修改不起作用,因为您正在将第一个数据与第二个数据进行比较,然后将第二个数据与第三个数据进行比较。然后呢?让我们做一个表,跟踪代码并记录其行为以及您希望代码的行为。

next

看看每当C是最小元素时代码如何失败?您也需要解决此问题。


最后,有一种解决方案甚至不需要创建3向合并。从技术上讲,| 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 仍应在O(n)上运行,时间复杂度也应正确发挥作用。

祝你决赛好运!

答案 3 :(得分:0)

为什么要限制自己进行三向合并?让我们看一下N路合并的一般情况,然后将其应用于3路(或普通的老式无聊2路)。

Jasmine

您可以将其视为许多链上带有链接的链,链上带有数字,其中每个链的数字按升序排列。要建立一个按顺序排列所有数字的大链,您可以:

  • 抓住其余链的所有小端
  • 选择最小的链接(如果没有剩余的链接,则表示您已经完成)
  • 取消该链接的链,并将其添加到新链的末尾
  • 回到选择旧链条的最小链接

正如Jacob_G所指出的,在回答中,当一个或多个列表为空时,您缺少一些用于选择正确的最小元素的逻辑。 Jacob的回答很好,但是我虽然会向您展示更大的图景:如果您对// 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; } } 理解,N应该一点也不张扬(而且,您会获得对一般想法的洞察力)。< / p>