2018年第一次试着写单链表的快速排序。所使用的方法虽然代码非常简洁,只有20行,但可惜并不是纯正的快速排序,而且使用的是数据交换也不是节点链接改变,造成效率也有点问题。后来又于2022年重写单链表的快速排序。这一次想出了一种很容易理的解方法,且是比较纯正的快速排序了,但可惜代码行数较多,近60行了,且其中引入了2个局部变量作为辅助,并不那么干净。
以上这一切都记录在拙作《单链表的快速排序与归并排序》中了。(这篇文章还另外介绍了单链表的归并排序)
今天忽有兴趣重写一下单链表的快速排序。写之前没有看以前的代码,使得思维并没有受到之前程序的影响。完成后,只觉今是而昨非。现在的代码比之前的有了许多进步,比如:是纯正的快速排序了;行数也由近60行缩减到39行了。虽然还觉得有哪里不那么完美,奈何水平有限,暂且如此吧。
算法本身不再介绍了;但在贴出代码之前,还是先介绍下具体写法。
首先,这是一个函数,比如就叫quick_sort_singly_linked_list
吧。
第1个问题,它返回的是什么?有2种写法皆可。
第一种写法,它返回一个Node *
,代表排序后的单链表的首节点;
第二种写法,它什么也不返回,即返回值为void
; 这也是可以的,因为可以将首节点作为一个参数放在参数列表中,比如弄一个二级指针。
为简便起见,这里还是采用大家耳熟能详的写法,即返回Node *
.
第2个问题,它的参数是什么呢?
可以和数组的快速排序做对比。因此,应该有2个参数,即 (Node * head, Node * tail)
.
这里的tail
是取不到的,即需要做排序的节点是在[head, tail)
这个区间。这也符合一开始的时候tail
为nullptr
.
核心的做法是什么呢?
- 使用指针
p
遍历[head, tail)
- 指针
h1
代表第一个链表的首节点,即所有小于等于head
的节点组成的链表 - 指针
h1p
代表第一个链表的游标,因此其最后值代表第一个链表的最后一个节点 - 指针
h2
代表第二个链表的首节点,及所有大于head
的节点组成的链表 - 指针
h2p
代表第二个链表的游标,因此其最后的值代表第二个链表的最后一个节点,也即tail
的前一个节点 - 用
nullptr
作为h1
和h1p
的初始值,因为一个链表里未必有比head小或等的节点 - 用
head
作为h2
和h2p
的初始值 - 一次遍历走完以后,需要使用
if (h1) h1p->next = head;
来把第一个链表和第二个链表连接成一个链表 - 之后,需根据
h1
是否为nullptr
来判定如何进行递归处理 - 普通情况下,以
(h1, head)
和(head->next, tail)
分别对这2段链表进行递归的快速排序; 然后再用head->next = p2;
将递归后的2段结果进行连接(p2
为第二段链表递归调用的返回值) - 若是
h1
为nullptr
的情况,则更简单,只需对(head->next, tail)
进行递归排序,等于只是排除了head这一个元素,因为它就是最小的; - 最终返回的是递归后的第一个链表的首节点
p1
;(若h1
为nullptr
, 则将递归的结果赋给head->next
, 然后返回head
即可)
文字写出来实在是太罗嗦了。看代码则简洁很多。
#include <iostream>
using namespace std;struct Node {int val;Node * next;Node(int v, Node * p=nullptr) : val(v), next(p) {}
};void print_linked_list(Node *p) {while (p) {cout << p->val << " ";p = p->next;}cout << endl;
}void delete_linked_list(Node *p) {while (p != nullptr) {Node * tmp = p->next;delete p;p = tmp;}
}// For [head, tail), put the head in the middle, so that,
// left nodes <= head < right nodes
// Return: the latest head node of the singly linked list
Node * quick_sort_singly_linked_list(Node * head, Node * tail)
{if (head == tail || head->next == tail) return head;Node *h1 = nullptr, *h1p = nullptr;Node *h2 = head, *h2p = head;Node * p = head->next; while (p != tail) {if (p->val <= head->val) {if (h1) {h1p->next = p;h1p = p;}else { // h1 is nullptr, i.e. this is the first smaller nodeh1 = h1p = p;}}else { // p->val > head->val h2p->next = p; h2p = p;}p = p->next; }// Join the 2 linked list if (h1) h1p->next = head;h2p->next = tail; if (h1 == nullptr) { // head is the minimum node in [head, tail)head->next = quick_sort_singly_linked_list(head->next, tail);return head;}Node * p1 = quick_sort_singly_linked_list(h1, head);Node * p2 = quick_sort_singly_linked_list(head->next, tail);head->next = p2;return p1;
}void test_case_01()
{Node * a1 = new Node(10);Node * a2 = new Node(5);Node * a3 = new Node(8);Node * a4 = new Node(20);Node * a5 = new Node(26);Node * a6 = new Node(18);a1->next = a2;a2->next = a3;a3->next = a4;a4->next = a5;a5->next = a6;print_linked_list(a1);Node * h = quick_sort_singly_linked_list(a1, nullptr);print_linked_list(h);delete_linked_list(h);
}int main()
{test_case_01();return 0;
}
(END)