簡體   English   中英

將 3 個鏈表合並為 1 個(Java)

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

我有一個關於我正在參加的編碼課程的最終審查的問題。 它要求將 3 個鏈表合並為 1 個鏈表。 我遇到的問題是在合並列表時,我能夠按升序合並三個列表,但我缺少第二個列表 23 和 25 的最后 2 個節點。我不知道為什么它停在那里。 問題在這里:

編寫一個名為 LinkedTest 的程序:

  1. 創建三個排序的整數單鏈表,如下所示
First List:  2 11 19 21 24

Second List: 14 15 18 23 25

Third List:  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; 
    }

    }
}

輸出:

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

為什么要限制自己進行 3 向合並? 讓我們看一下 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;
    }
}

您可以將其視為許多帶有鏈接的鏈,鏈上帶有數字,其中每條鏈的編號按升序排列。 要構建一個包含所有數字的大鏈,您需要:

  • 抓住剩余鏈子的所有小端
  • 選擇最小的鏈接(如果沒有剩余,則已完成)
  • 將該鏈接從其鏈上取下,並將其添加到新鏈的末尾
  • 回到選擇舊鏈中最小的環節

在您的回答中,正如 Jacob_G 所指出的,當一個或多個列表為空時,您缺少一些選擇正確最小元素的邏輯。 雅各布的回答很好,但我雖然我會向您展示更大的圖景:如果您理解N3應該沒有延伸(另外,您對總體思路有了深入了解)。

您將獲得三個已排序的列表,並要求將它們合並為一個已排序的列表 實現這一點的算法非常簡單。

我建議跟蹤三個索引(每個輸入列表一個)並將它們全部初始化為0 (每個列表的第一個元素)。 因為每個列表包含相同數量的元素,您可以簡單地從0迭代到3n ,其中n列表s 之一的大小。

在循環內部,您希望使用各自的索引找到每個列表3個頭元素之間的最小元素。 為此,只需查看元素並將它們相互比較即可。 找到最小值后,您可以將該元素附加到合並列表中並增加相應列表的索引(這很重要,因為不要兩次附加相同的元素)。

您將重復此操作3n次(三個列表中的每個元素一次),結果將是一個按升序排序的合並列表

假設每個輸入列表的大小總是相等,這個算法是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]++

你的問題是你沒有寫出所有的條件。 讓我們看看您的用例。

第一個列表:2 11 19 21 24

第二個名單:14 15 18 23 25

第三個名單:3 9 17 20 22

根據您的代碼邏輯,在第一次合並中,我們在first.data < second.data得到2 下一回合,我們在同一位置有 11 < 14。 但在那之后我們現在有
19 21 24
14 15 18 23 25
3 9 17 20 22
並且在接下來的回合中都不會滿足任何條件。

我想你的代碼是從合並兩個列表的代碼中修改而來的。 您應該回憶為什么每段代碼都有效,並思考為什么您的代碼沒有按照您認為應該的方式工作。

讓我們假設您的原始算法如下所示:

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] ,假設算法的其他部分有效,那么您最終會在[] , [3, 4] , [5, 6] ,您將返回[3, 4]並丟棄[5, 6] ,這是不正確的。

一種解決方案是在任何一個列表出現時調用該算法的兩個列表版本。 這需要更多的代碼(這也是非常重復的),但是來自這個特定的代碼更直接。 另一種解決方案是僅在兩個列表為空時才終止,但這在遞歸情況下需要特別注意以檢查空值。


該方法的后半部分比較列表的第一個元素,並使用較小的元素作為新列表的頭部,然后將頭節點的next數據成員分配給“無論合並其余列表的結果是什么” ”。 請注意,在此版本的代碼中,每次只執行其中一個代碼塊。 (除非數據相等,否則會有bug。修復它留作練習。)只要不終止遞歸,我們就需要更深入,對吧!

您所做的修改不起作用,因為您將第一個數據與第二個數據進行比較,然后將第二個數據與第三個數據進行比較……然后呢? 讓我們制作一個表格,跟蹤代碼並記錄它的行為方式與您希望代碼的行為方式。

| 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

當 C 是最小元素時,看看您的代碼如何失敗? 你也需要解決這個問題。


最后,有一種解決方案甚至不需要創建 3 路合並。 從技術上講, merge(A, merge(B, C))應該仍然以 O(n) 時間復雜度運行,並且行為也正確。

祝你總決賽好運!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM