ソートされたリストのデータ構造とアルゴリズム

                                              ソートされたリンクリスト

                                                                  参考Leetcode-148

1. ソートリンクリストをマージする

1. 方法 1: トップダウンのマージソート

    次のように進めます。

  • リンク リストの中点を見つけて、リンク リストを 2 つのサブリンク リストに分割します。

  • 2 つのサブリストをマージして並べ替えます。

  • 2 つのサブリストに対して最初の 2 つの手順を繰り返します。

    コードリファレンスは次のとおりです。

#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        return sortList(head, nullptr);
    }

    ListNode* sortList(ListNode* head, ListNode* tail) {
        if (head == nullptr) { // 为空结点返回
            return head;
        }
        if (head->next == tail) { // 只剩一个结点后返回
            head->next = nullptr;
            return head;
        }
        /* 双指针法找到中间结点 */
        ListNode* slow = head, *fast = head;
        while (fast != tail) {
            slow = slow->next;
            fast = fast->next;
            if (fast != tail) {
                fast = fast->next;
            }
        }
        ListNode* mid = slow;
        /* 合并两个子链表 */
        return merge(sortList(head, mid), sortList(mid, tail));
    }

    /* 合并两个子链表的方法 */
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) {
            temp->next = temp1;
        } else if (temp2 != nullptr) {
            temp->next = temp2;
        }
        return dummyHead->next;
    }
};

int main() {
    Solution sol;
    ListNode *l1 = new ListNode(-1);
    ListNode *l2 = new ListNode(5);
    ListNode *l3 = new ListNode(3);
    ListNode *l4 = new ListNode(4);
    ListNode *l5 = new ListNode(0);

    l1->next = l2;
    l2->next = l3;
    l3->next = l4;
    l4->next = l5;

    sol.sortList(l1);

    return 0;
}

    上記のアルゴリズムの時間計算量は O(n logn)、空間計算量は O(logn) です。ここで、n はリンク リストの長さです。

2. 方法 2: ボトムアップのマージソート

    具体的な方法は以下の通りです。

  • リンク リストを subLength の長さのサブリンク リストに分割し、2 つのサブリンク リストごとにマージします。

  • subLength の長さは [1, 2, 4, ...] に変更され、各ラウンドは前のラウンドの 2 倍になり、上記の手順を繰り返します。

    コード例は次のとおりです。

#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr) {
            return head;
        }
        /* 计算链表的长度 */
        int length = 0;
        ListNode* node = head;
        while (node != nullptr) {
            length++;
            node = node->next;
        }

        ListNode* dummyHead = new ListNode(0, head);
        for (int subLength = 1; subLength < length; subLength <<= 1) { // subLength左移1位,相当于subLength*2
            ListNode* prev = dummyHead, *curr = dummyHead->next;
            while (curr != nullptr) {
                ListNode* head1 = curr;
                for (int i = 1; i < subLength && curr->next != nullptr; i++) { // 根据subLength来更新curr,以此获得head2的位置
                    curr = curr->next;
                }
                ListNode* head2 = curr->next;
                curr->next = nullptr; // 断开链表,形成head1子链表
                curr = head2;
                for (int i = 1; i < subLength && curr != nullptr && curr->next != nullptr; i++) { // 同上
                    curr = curr->next;
                }
                ListNode* next = nullptr;
                if (curr != nullptr) { // 获取next指针,并且断开链表,形成head2子链表
                    next = curr->next;
                    curr->next = nullptr;
                }
                ListNode* merged = merge(head1, head2); // 合并两个子链表
                prev->next = merged;
                /* 更新prev、next指针 */
                while (prev->next != nullptr) {

                    prev = prev->next;
                }
                curr = next;
            }
        }
        return dummyHead->next;
    }

    /* 合并两个有序链表 */
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
        while (temp1 != nullptr && temp2 != nullptr) {
            if (temp1->val <= temp2->val) {
                temp->next = temp1;
                temp1 = temp1->next;
            } else {
                temp->next = temp2;
                temp2 = temp2->next;
            }
            temp = temp->next;
        }
        if (temp1 != nullptr) {
            temp->next = temp1;
        } else if (temp2 != nullptr) {
            temp->next = temp2;
        }
        return dummyHead->next;
    }
};

int main() {
    Solution sol;
    ListNode *l1 = new ListNode(-1);
    ListNode *l2 = new ListNode(5);
    ListNode *l3 = new ListNode(3);
    ListNode *l4 = new ListNode(4);
    ListNode *l5 = new ListNode(0);

    l1->next = l2;
    l2->next = l3;
    l3->next = l4;
    l4->next = l5;

    return 0;
}

    並べ替えのプロセスを次の図に示します。

最初のラウンド:

第二ラウンド:

第 3 ラウンド:

    その時間計算量は O(n logn)、空間計算量は O(1) です。ここで、n はリンク リストの長さです。

おすすめ

転載: blog.csdn.net/hu853712064/article/details/130543423