Sort List系列

本文探讨了单链表上的排序算法应用,分析了哪些排序算法适用于单链表,并提供了插入排序、选择排序、快速排序及归并排序的具体实现。

在严老师的数据结构书上学习了各种排序算法,比如插入排序,选择排序,堆排序,快速排序,归并排序等,不过书上代码的应用场景均是底层结构是顺序存储的情况,即数组。

今天总结下对于单链表,常用的排序算法能否使用。由于希尔排序、堆排序、计数排序都要求随机访问,而单链表只能顺序访问,故这 3 种是不能用于单向链表排序,其他常用的排序方法用于单链表时间复杂度和空间复杂度略有不同。下图是针对单链表的常用排序算法图:


Insertion Sort List

 

Sort a linked list using insertion sort.

使用简单插入排序和选择排序,代码如下:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *insertionSortList(ListNode *head) {
        if( !head ) return head;
        ListNode node(0);
        node.next = head;   //增加头结点,便于插入
        ListNode* cur = head->next;     //认为第一个节点时有序,从第二个节点开始往前插
        head->next = 0;
        while( cur ) {
            ListNode* pre = &node;
            ListNode* p = node.next;
            while( p && p->val <= cur->val ) {  //查找第一个大于cur节点的节点
                pre = p;
                p = p->next;
            }
            p = cur->next;
            cur->next = pre->next;  //插入节点
            pre->next = cur;
            cur = p;
        }
        return node.next;
    }
    
    ListNode* selectionSortList(ListNode* head) {
        if( !head ) return head;
        for( ListNode* pi = head; pi->next; pi = pi->next ) {
            ListNode* minNode = pi;
            for( ListNode* pj = pi->next; pj; pj = pj->next )
                if( pj->val < minNode->val ) minNode = pj;
            swap(pi->val, minNode->val);
        }
        return head;
    }
};

Sort List

 

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

如果使用O(N2)的算法,会超时,但是使用快速排序,在极端情况下,时间复杂度依旧达到了O(N2),依然超时,使用归并排序,时间复杂度为O(NlogN),可以顺利AC,下面代码是快排和归并的算法
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *sortList(ListNode *head) {
        return qSort(head);
    }
    
    ListNode* qSort(ListNode* head) {
        if( !head || !head->next ) return head;
        ListNode* lhead = head;     //左部分链表
        ListNode* rhead = NULL;     //右部分链表
        ListNode* mid = NULL;       //分区节点
        mid = partition(lhead, rhead);  //分区
        lhead = qSort(lhead);   //左链表快排
        rhead = qSort(rhead);   //右链表快排
        if( lhead ) {       //若左链表非空
            ListNode* p = lhead;
            while( p->next ) p = p->next;
            p->next = mid;
        }
        else {          //左链表为空
            lhead = mid;
        }
        mid->next = rhead;
        return lhead;
    }
    
    ListNode* partition(ListNode*& lhead, ListNode*& rhead) {
        ListNode node(0);
        node.next = lhead;
        ListNode* end = lhead;
        while( end->next ) end = end->next; //找到最后节点
        ListNode* pi = lhead;
        ListNode* pre = &node;
        for(ListNode* pj = lhead; pj != end; pj = pj->next) //单向扫描法。。。欲知详情有请度娘
            if( pj->val <= end->val ) {
                swap(pi->val, pj->val);
                pre = pi;
                pi = pi->next;
            }
        swap(pi->val, end->val);
        if( lhead == pi ) lhead = NULL; //左边部分为空的情况
        else pre->next = NULL;
        rhead = pi->next;   //分区后右边部分
        pi->next = NULL;
        return pi;      //中间节点
    }
    
    ListNode* mergeSort(ListNode* head) {
        if( !head || !head->next ) return head;     //若链表只有1个节点或没有,则直接返回
        ListNode* fast = head;
        ListNode* slow = head;
        while( fast && fast->next && fast->next->next ) {   //注意fast->next->next,不然当链表有两个节点时会死循环
            fast = fast->next->next;
            slow = slow->next;
        }
        fast = slow->next;
        slow->next = NULL;      //链表分成两部分,前一部分为head,后一部分为fast
        head = mergeSort(head);
        fast = mergeSort(fast);
        return merge(head, fast);   //merge两部分链表
    }
    
    ListNode* merge(ListNode* lhead, ListNode* rhead) {
        if( !lhead ) return rhead;
        if( !rhead ) return lhead;
        ListNode node(0);
        ListNode* tail = &node;
        while( lhead && rhead ) {
            if( lhead->val < rhead->val ) {     //左小取左边
                tail->next = lhead;
                tail = lhead;
                lhead = lhead->next;
                tail->next = NULL;
            }
            else {  //右小取右边
                tail->next = rhead;
                tail = rhead;
                rhead = rhead->next;
                tail->next = NULL;
            }
        }
        tail->next = lhead ? lhead : rhead;     //左链表不为空则取左边,否则取右边
        return node.next;
    }
};






### C# 中 `List<T>.Sort` 方法详解 #### 排序基础 在C#中,`List<T>` 提供了一个名为 `Sort()` 的方法来对列表中的元素进行排序。对于基本数据类型的列表,默认情况下会按照升序排列[^1]。 ```csharp // 创建并初始化整数列表 var numbers = new List<int> { 2, 1, 3, 4, 5 }; numbers.Sort(); foreach (var num in numbers) { Console.Write(num + " "); } Console.WriteLine(); // 输出:1 2 3 4 5 ``` 为了实现降序排序,在比较器内部可以简单地交换两个操作数的位置或给返回的结果加上负号: ```csharp // 对于整数列表执行降序排序 numbers.Sort((x, y) => -(x.CompareTo(y))); foreach (var num in numbers) { Console.Write(num + " "); } Console.WriteLine(); // 输出:5 4 3 2 1 ``` #### 自定义类型排序 当处理复杂的数据结构如类实例时,则需要提供额外的信息告诉程序如何对比这些对象之间的顺序。这可以通过几种方式完成: - **通过实现 `IComparable<T>` 或者 `IComparer<T>` 接口** 如果希望某个特定类型的集合总是遵循某种固定的排序逻辑,可以在该类型本身内嵌入这种行为——即让这个类型继承自 `IComparable<T>` 并重写其成员函数 `CompareTo(T other)` 来指定具体的比较规则;或者创建一个新的实现了 `IComparer<T>` 的类作为外部比较器传递给 `Sort()` 函数调用。 - **利用匿名委托表达式** 当只需要临时改变一次性的排序策略而不必修改原有代码库的时候,可以选择直接在线编写简单的 lambda 表达式来进行快速定制化排序。这种方式特别适合那些只涉及几个字段的情况。 下面是一个例子展示了如何基于键值对内的属性对 `KeyValuePair` 类型的对象数组做排序: ```csharp public class KeyValuePair { public uint id; public string s = ""; public float d; public double c; // 构造函数省略... } // 假设有这样一个包含多个键值对的列表 var kvps = new List<KeyValuePair>(); kvps.Add(new KeyValuePair { id = 1U, s = "apple", d = 0.5F }); kvps.Add(new KeyValuePair { id = 2U, s = "banana", d = 0.7F }); // 按照字符串 's' 成员降序排列 kvps.Sort((a, b) => -String.Compare(a.s, b.s)); foreach (var kvp in kvps) { Console.WriteLine($"{kvp.id}: {kvp.s}"); } ``` 上述代码片段将会先打印 `"banana"` 后跟 `"apple"` ,因为字母表上 B 大于 A 所以 banana 应位于 apple 之前。 另外还有一种更灵活的方式是使用 LINQ 查询语法配合 OrderBy / ThenBy 系列扩展方法来做多级条件下的排序组合,不过这里不再赘述。 #### 参数化的部分排序 有时候可能并不想整个序列都被重新安排而是只想调整其中一部分子集里的相对位置关系。这时就可以借助带有三个参数版本的 `Sort(int index, int count, IComparer comparer)` 进行局部范围的操作了[^2]: ```csharp // 只针对前半段元素按浮点数'd'从小到大排序 int halfSize = kvps.Count / 2; kvps.Sort(0, halfSize, Comparer<float>.Create((left, right) => left.CompareTo(right))); for (int i = 0; i < kvps.Count; ++i) { var item = kvps[i]; Console.WriteLine($"Index={i}, Value={item.d}"); } ``` 这段代码仅影响到了列表开头至中间位置处各元素间的先后次序而不会干扰其余部分的内容布局。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值