leetcode148归并排序 (递归与非递归c++实现)

本文深入解析了归并排序算法,包括其非线性时间比较特性、分治策略、时间复杂度为O(nlogn)及空间复杂度为O(n)的特点。提供了C++语言的递归与非递归实现代码,以及链表排序的示例。

归并排序:
是一种非线性时间比较类排序,采用分治的策略,通过比较来决定元素间的相对次序.时间复杂度不受数据本身影响.时间复杂度为O(nlogn),空间复杂度为O(n).

算法描述:
1.把长度为n的输入序列分成两个长度为n/2的子序列
2.对这两个子序列分别采用归并排序
3.将两个排好序的子序列合并为一个最终的排序序列.

c++递归实现:

#include<cstdlib>

using namespace std;

void MergeSort(int* A, int* L, int lenOfL, int* R, int lenOfR)
{
        int i=0, j=0, k=0;
        while(i<lenOfL && j<lenOfR)
        {
                if(L[i]<R[j])
                        A[k++]=L[i++];
                else
                        A[k++]=R[j++];

        }
        while(i<lenOfL)
                A[k++]=L[i++];
        while(j<lenOfR)
                A[k++]=R[j++];
}
void Merge(int *A, int len)
{
        if(len<2)
                return;
        int mid= len/2;
        int *L = new int [mid];
        int *R = new int [len-mid];
        for(int i=0; i<mid; i++)
                L[i] = A[i];
        for(int i=mid; i<len ;i++)
                R[i-mid] = A[i];
        Merge(L, mid);
        Merge(R, len-mid);
        MergeSort(A, L ,mid, R, len-mid);
        delete []L;
        delete []R;
}
int main()
{
        int number[8]={1, 4, 6,8, 3,4, 5,1};
        int len = sizeof(number)/sizeof(number[0]);
        Merge(number, len);
        for(int i=0; i<len; i++)
                cout<<number[i]<<" ";
        return 0;

}

非递归实现:

#include <iostream>
#include <cstdlib>
using namespace std;
void mergeSort(int *A, int low, int mid, int high)
{
        int *temp = new int[high-low+1];
        int i=low,j=mid+1,k=0;
        while(i<=mid && j<=high)
        {
                if(A[i]<A[j])
                        temp[k++] = A[i++];
                else
                        temp[k++] = A[j++];
        }
        while(i<=mid)
        {
                temp[k++]=A[i++];
        }
        while(j<=high)
        {
                temp[k++]=A[j++];
        }
        for(int i=low, k=0; i<=high; i++,k++)
                A[i]=temp[k];
        delete []temp;
}
void Merge(int *A, int n)
{
        int size=1,low,high,mid;
        while(size<=n-1)
        {
                low=0;
                while(low+size<=n-1)
                {
                        mid = low+size-1;
                        high = mid+size;
                        if(high>n-1)
                                high = n-1;
                        mergeSort(A,low,mid,high);
                        low = high+1;
                }
                size*=2;
        }
}
int main()
{
        int number[8]={1, 4, 6,8, 3,4, 5,1};
        int len = sizeof(number)/sizeof(number[0]);
        Merge(number, len);
        for(int i=0; i<len; i++)
                cout<<number[i]<<" ";
        return 0;

}

Sort a linked list in O(n log n) time using constant space complexity.

Example 1:

Input: 4->2->1->3
Output: 1->2->3->4
Example 2:

Input: -1->5->3->4->0
Output: -1->0->3->4->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    //归并排序:时间复杂度O(nlogn),空间复杂度O(n)
    ListNode* sortList(ListNode* head) {
        if(head==nullptr)
            return nullptr;
        if(head->next == nullptr)
            return head;
        ListNode* slow = head;
        ListNode* fast =head;
        while(fast->next && fast->next->next)
        {
            slow = slow->next;
            fast = fast->next->next;;
        }
        ListNode* right = slow->next;
        slow->next = nullptr;
        ListNode* l1 = sortList(head);
        ListNode* l2 = sortList(right);
        return merge(l1, l2);
        
    }
    ListNode* merge(ListNode* left, ListNode* right)
    {
        ListNode* newHead = new ListNode(0), *cur = newHead;
        if(!left) return right;
        if(!right) return left;
        while(left && right)
        {
            if(left->val < right->val)
            {
                cur->next = left;
                left = left->next;
            }
            else
            {
                cur->next = right;
                right=right->next;
            }
            cur = cur->next;
                
        }
        if(left)
            cur->next = left;
        if(right)
            cur->next = right;
        return newHead->next;
    }
};
<think>首先,用户的问题是关于在C++实现归并排序来排序一个链表类的。用户的查询是:"我想了解如何在C++的链表类中使用归并排序实现排序功能",所以核心是使用归并排序在链表数据结构上。我需要参考系统指令:确保行内数学表达式用$...$,独立公式用$$...$$。LaTeX语法要正确。使用中文回答。生成相关问题。在引用的段落末尾添加引用标识。用户提供了一些引用,这些是站内引用,我应该参考它们来构建回答。-引用[1]讨论了LeetCode上排序链表的问题,强调使用归并排序最有效,因为链表不适合快速排序或堆排序。-引用[2]比较了各种排序算法,指出快速排序不适合链表,而归并排序适合。-引用[3]提供了具体的C++代码示例,使用了`mergeSort`函数和`sortList`函数来实现归并排序在链表上。-引用[4]强调了在链表上实现归并排序时需要数组归并排序的基础、递归理解和使用指针。-引用[5]列出了多种链表排序方法,但用户聚焦于归并排序。用户上一次的问题似乎是与排序链表相关,我需要尽量参考这些引用。回答结构应该清晰:逐步解释如何在C++链表类中实现归并排序。关键点:1.**归并排序基本概念**:归并排序是一种分治算法。它将列表分成两半,递归排序两半,然后合并排序的部分。2.**链表上的优势**:不像数组,链表不需要随机访问,分治时只需要修改指针,所以归并排序高效。3.**实现步骤**(基于引用[3]和[4]):-**分割链表**:使用快慢指针找到中点,将链表分成两个子链表。-**递归排序**:递归调用排序函数在子链表上。-**合并两个排序链表**:合并函数合并两个已排序的链表。4.**伪代码或代码示例**:参考引用[3]的代码,提供类似的结构。使用代码块表示。-例如,类定义、`sortList`函数、`merge`函数(可能叫`merge`或`mergeSort`)。5.**关键函数**:-`ListNode*sortList(ListNode*head)`:排序函数,处理边界(如空链表或单节点链表),然后分拆递归。-`ListNode*merge(ListNode*a,ListNode*b)`:合并两个排序链表。6.**注意事项**:-递归的深度:确保递归正确。-指针操作:链表操作需要小心指针管理,避免内存泄露或无效指针。7.**稳定性**:提到归并排序是稳定的。生成相关问题:在回答结束时,添加相关问题。添加引用标识:在相关段落末尾。确保使用中文回答。结构:1.**引言**:简要说明归并排序在链表上的适用性。2.**基本步骤**:分步解释实现过程。3.**代码示例**:提供C++代码片段。4.**注意事项和优点**。5.**相关问题**。相关问题列表基于主题:例如,优化、比较其他排序、应用等。最终输出:</think> ### C++链表类中使用归并排序实现排序功能的方法 归并排序在链表数据结构上尤其有效,因为它不需要随机访问(如数组索引),仅通过修改指针即可完成分治过程。链表的结构使其在拆分和合并时时间复杂度为$O(n \log n)$,且归并排序是**稳定排序算法**[^2]。以下是如何在C++链表类中实现归并排序的详细步骤和解释。整个过程基于归并排序的分治原则:将链表拆分成子链表,递归排序,再合并排序结果[^1][^3]。 #### 步骤 1: 理解链表类的基本结构 在C++中,链表通常通过节点类 `ListNode` 表示。每个节点包含一个值和一个指向下一个节点的指针。这是实现的基础: ```cpp struct ListNode { int val; // 存储节点的值 ListNode* next; // 指向下一个节点的指针 ListNode(int x) : val(x), next(nullptr) {} // 构造函数 }; ``` 链表的排序功能通常在另一个类(如 `LinkedList` 或直接在解决方案类)中实现。核心思路是将 `head` 节点作为输入,输出排序后的新链表头节点。 #### 步骤 2: 实现归并排序的关键函数 归并排序涉及三个主要函数:`sortList`(主排序函数)、`merge`(合并函数)和 `findMiddle`(或使用快慢指针找中点)。下面分步解释每个函数的作用和实现逻辑: 1. **主排序函数 `sortList(ListNode* head)`**: - **作用**:处理递归分治过程。如果链表为空或只有单个节点,直接返回(基例);否则,拆分成两个子链表,递归排序后合并。 - **拆分逻辑**:使用**快慢指针**找到链表中点(时间复杂度 $O(n)$)。快指针每次移动两步,慢指针移动一步,当快指针到达尾部时,慢指针即中点[^3][^4]。 - **递归调用**:排序左半部分(从头节点到中点)和右半部分(中点之后),然后合并它们。 - **代码示例**: ```cpp ListNode* sortList(ListNode* head) { // 基例: 链表为空或只有一个节点 if (head == nullptr || head->next == nullptr) { return head; } // 找到中点:快慢指针方法 ListNode* slow = head; ListNode* fast = head->next; while (fast != nullptr && fast->next != nullptr) { slow = slow->next; // 慢指针移动一步 fast = fast->next->next; // 快指针移动两步 } ListNode* mid = slow->next; // 中点为 slow 的下一个节点 slow->next = nullptr; // 拆分链表:左链表从头开始,右链表从 mid 开始 // 递归排序子链表 ListNode* left = sortList(head); ListNode* right = sortList(mid); // 合并排序后的子链表 return merge(left, right); } ``` 2. **合并函数 `merge(ListNode* a, ListNode* b)`**: - **作用**:合并两个已排序的链表为一个新排序链表。使用虚拟头节点简化指针操作。 - **核心过程**:遍历两个链表,比较当前节点值,将较小值添加到新链表中,直到一个链表耗尽,再将剩余链表追加。 - **指针管理**:需要维护一个尾指针 `tail`,以高效添加新节点(避免每次从头遍历)。 - **稳定性**:归并排序是稳定的,相等值的节点顺序不会被破坏[^2]。 - **代码示例**: ```cpp ListNode* merge(ListNode* a, ListNode* b) { // 创建虚拟头节点,简化合并过程 ListNode dummy(0); // 临时节点 ListNode* tail = &dummy; // 尾指针指向 dummy // 合并过程:比较 a 和 b 的当前节点值 while (a != nullptr && b != nullptr) { if (a->val <= b->val) { tail->next = a; // 连接较小的节点 a = a->next; // 移动 a 指针 } else { tail->next = b; // 连接较小的节点 b = b->next; // 移动 b 指针 } tail = tail->next; // 更新尾指针 } // 追加剩余链表(如果 a 或 b 还有节点) if (a != nullptr) tail->next = a; if (b != nullptr) tail->next = b; return dummy.next; // 返回新链表的头(dummy 的下一个节点) } ``` 3. **整体封装**: - 在链表类中,将这些函数集成。例如: ```cpp class Solution { public: ListNode* sortList(ListNode* head) { // 如上实现 } private: ListNode* merge(ListNode* a, ListNode* b) { // 如上实现 } }; ``` #### 步骤 3: 完整代码示例(链表归并排序实现) 以下是一个完整、可运行的C++示例,使用归并排序对链表进行排序。这段代码参考了LeetCode题目的解法[^3][^4]。 ```cpp #include <iostream> using namespace std; // 链表节点类 struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; // 链表排序类 class LinkedListSorter { public: ListNode* sortList(ListNode* head) { if (head == nullptr || head->next == nullptr) { return head; } // 快慢指针找中点 ListNode* slow = head; ListNode* fast = head->next; while (fast != nullptr && fast->next != nullptr) { slow = slow->next; fast = fast->next->next; } ListNode* mid = slow->next; slow->next = nullptr; // 分割链表 // 递归排序 ListNode* left = sortList(head); ListNode* right = sortList(mid); // 合并 return merge(left, right); } private: ListNode* merge(ListNode* a, ListNode* b) { ListNode dummy(0); ListNode* tail = &dummy; while (a && b) { if (a->val <= b->val) { tail->next = a; a = a->next; } else { tail->next = b; b = b->next; } tail = tail->next; } tail->next = (a != nullptr) ? a : b; return dummy.next; } }; // 辅助函数:打印链表 void printList(ListNode* head) { while (head != nullptr) { cout << head->val << " "; head = head->next; } cout << endl; } // 测试代码 int main() { // 创建示例链表: 4 -> 2 -> 1 -> 3 ListNode* head = new ListNode(4); head->next = new ListNode(2); head->next->next = new ListNode(1); head->next->next->next = new ListNode(3); LinkedListSorter sorter; ListNode* sorted = sorter.sortList(head); printList(sorted); // 输出: 1 2 3 4 // 释放内存 (防止内存泄露) ListNode* temp; while (sorted) { temp = sorted; sorted = sorted->next; delete temp; } return 0; } ``` #### 步骤 4: 关键注意事项和优点 - **时间复杂度与空间复杂度**: - 时间复杂度为 $O(n \log n)$,其中 $n$ 是链表长度。这来自于分割($O(n)$)和合并($O(n)$)的递归树深度 $\log n$[^1]。 - 空间复杂度为 $O(\log n)$,主要是递归栈空间(非尾递归)。对于大型链表,可能涉及栈溢出,可以优化为迭代版本(自底向上归并)。 - **优点在链表上的体现**: - 避免了快速排序在链表上的劣势:快速排序需要随机访问(如通过索引交换元素),但链表只有顺序访问,所以归并排序更高效[^2]。 - 指针操作只需修改节点指向,不涉及元素复制或移动,这比数组归并排序更高效。 - **常见错误和调试建议**: - **指针错误**:在分割链表时,确保 `slow->next = nullptr` 正确断开链表,否则递归时可能死循环。使用虚拟头节点在合并中可简化指针管理[^4]。 - **内存管理**:如果在实际应用中分配了新节点,确保在不再需要时删除内存(如测试代码中的 `delete`),以防泄露。 - **边界处理**:主函数应检查输入链表是否为空。代码示例中已包含。 - **与其他排序算法比较**: - 归并排序适合链表,而冒泡排序或快速排序可能导致 $O(n^2)$ 最坏情况(链表无法高效分治)[^5]。例如,快速排序需要随机访问基准元素,这在链表中难以实现。 #### 相关实践建议 在实际项目(如LeetCode算法题)中,优先使用归并排序实现链表排序。确保理解递归过程,并考虑优化点,如自底向上归并减少栈空间使用。归并排序在链表排序中具有显著优势,尤其在数据规模大时[^1][^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值