写给自己看的单链表(4):快速排序

搬运自我的CSDN https://blog.csdn.net/u013213111/article/details/88670136

!!!Attention:以下操作中的单链表均带有头结点!!!
参考了这三篇文章:
单链表快速排序算法的实现
单链表的快速排序
单链表的快排实现
快速排序的思路是:首先,选取一个pivot;然后以pivot作为基准,对待排序的数据进行分区,得到两个部分,一个部分的数据均小于pivot,另一个部分的数据均大于pivot,然后对这两个部分再进行之前的操作,直至排序完成。
对数组来说,pivot通常会选取位于中间的数据,然后用两个指针分别从头、尾两端向中间移动。
但是对于单链表来说,由于只能从头到尾单方向进行移动,所以其快速排序的实现方式有所不同,取第一个节点为pivot(这里要注意开区间和闭区间的问题,下面细说),指针p从pivot开始向后移动,指针q从pivot->next开始向后移动,最后一步是交换pivot和p的值,最终呈现的结果是:从头结点至p-1(含p-1)之间的数据均小于pivot,从p+1(含p+1)至q之间的值均大于pivot。
用一个例子来看看Partition函数的思路:

下面看一下不同区间表达式下的代码,开区间和闭区间导致代码不同的思路来自于这篇文章:你常写的二分查找,真的是没有bug吗?
1.左闭右开,初始区间为 [head->next,NULL)
这是比较省事的一种表达方式。首先看QuickSort函数的循环条件,当left != right时继续排序,借用上面那篇文章中的一个例子来说就是:

由于我们采用的是左闭右开表示法,也就是 区间为 [left ,right) 且left == right 循环停止,,如果用个例子来解释,大家就会豁然开朗了, 假如[ left=4, right=4),此时用左闭右闭来解释的话,区间实际上是出现了右边界=3 , 左边界=4 , 这种情形,右边界 < 左边界,这意味着区间长度<=0 了,无法继续了。

在迭代的过程中,仍然要保持区间是左闭右开的。在第一次分区后,数据的两个部分分别表示为 [head->next,par) 和 [par->next, NULL),所以迭代的语句为QuickSort(head, left, par); QuickSort(head, par->next, NULL);注意Partation函数中的语句也要根据区间的左闭右开性质来写。

 1 void QuickSort(Lnode *head, Lnode *left, Lnode *right) //left is head->next, right is null
 2 {
 3     if ( left != right) {    //ATTENTION!!!
 4         Lnode *par = Partation(left, right);
 5         QuickSort(head, left, par);
 6         QuickSort(head, par->next, NULL);
 7     }
 8 }
 9
10 Lnode *Partation(Lnode *left, Lnode *right)
11 {
12     if (left == right)
13         return left;
14
15     Lnode *p, *q;
16     eletype pivot = left->data;
17     p = left;
18     q = p->next;
19     while (q != right) {
20         if (q->data < pivot) {
21             p = p->next;
22             SwapData(p, q);
23         }
24         q = q->next;
25     }
26     SwapData(left, p);
27     return p;
28 }
29
30 void SwapData(Lnode *p, Lnode *q)
31 {
32     eletype tmp;
33     tmp = p->data;
34     p->data = q->data;
35     q->data = tmp;
36 }

2.左开右开,初始区间为 (head,NULL)
这种表达方式使得调用函数时比较方便,因为参数直接就是head和NULL,但是写法比上一种稍麻烦了一点点。
QuickSort函数的循环条件发生了改变,当left->next != right时继续排序。在第一次分区后,数据的两个部分分别表示为 (head,par) 和 (par, NULL),所以迭代的语句为QuickSort(head, left, par); QuickSort(head, par, NULL);,Partation函数中的语句也要根据区间的左开右开性质来写。

 1 void QuickSort(Lnode *head, Lnode *left, Lnode *right)
 2 {
 3     if (left->next != right) {
 4         Lnode *par = Partation(left, right);
 5         QuickSort(head, left, par);
 6         QuickSort(head, par, NULL);
 7     }
 8 }
 9
10 Lnode *Partation(Lnode *left, Lnode *right)
11 {
12     if (left->next->next == right)
13         return left->next;
14
15     Lnode *p, *q;
16     eletype pivot = left->next->data;
17     p = left->next;
18     q = p->next;
19     while (q != right) {
20         if (q->data < pivot) {
21             p = p->next;
22             SwapData(p, q);
23         }
24         q = q->next;
25     }
26     SwapData(left->next, p);
27     return p;
28 }
29
30 void SwapData(Lnode *p, Lnode *q)
31 {
32     eletype tmp;
33     tmp = p->data;
34     p->data = q->data;
35     q->data = tmp;
36 }

原文地址:https://www.cnblogs.com/lyrich/p/10586600.html

时间: 2024-10-17 00:23:48

写给自己看的单链表(4):快速排序的相关文章

写给自己看的单链表(5):归并排序

搬运自我的CSDN https://blog.csdn.net/u013213111/article/details/88670270 !!!Attention:以下操作中的单链表均带有头结点!!!参考怎样实现链表的归并排序 由于待处理的单链表带有头结点,因此把程序分为MergeSort和MergeSortCore两部分,其中MergeSort只是用来处理头结点的,这与写给自己看的单链表(2):进阶操作中的合并程序类似. 从MergeSortCore的伪代码可以一窥归并排序的思路: 1 Lnod

看数据结构写代码(4)单链表

单链表比较简单,中间倒也没出什么大问题,只是 在写 插入 和 删除的 算法的 时候 ,时间复杂度 是正常 算法的2倍.后来 改正了. 下面奉上代码.如有 bug,欢迎指出. // SingleList.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstdlib> enum E_State { E_State_Error = 0, E_State_OK = 1, }; typedef int ElementTyp

Java实现单链表的快速排序和归并排序

本文描述了LeetCode 148题 sort-list 的解法. 题目描述如下: Sort a linked list in O(n log n) time using constant space complexity. 题目要求我们在O(n log n)时间复杂度下完成对单链表的排序,我们知道平均时间复杂度为O(n log n)的排序方法有快速排序.归并排序和堆排序.而一般是用数组来实现二叉堆,当然可以用二叉树来实现,但是这么做太麻烦,还得花费额外的空间构建二叉树,于是不采用堆排序. 故本

单链表的快速排序(转)

单链表的特点是:单向.设头结点位head,则最后一个节点的next指向NULL.如果只知道头结点head,请问怎么将该链表排序? 设结点结构为 struct Node{ int key; Node* next; }; 那么一般人见到这种题目,立马就会想到指针交换.是的,大家被指针交换的题目做多了,形成思维定势了.对于这道题,我们完全可以利用值交换来达到排序的目的. 当然,怎么值交换? 很多人得第一想法就是选择排序,这个木有问题,不过它的复杂度为O(n^2):有木有更好一点的方法呢?归并,不错,归

单链表排序——快速排序实现

利用快速排序,同向一前一后两个指针 #ifndef LIST_H_ #define LIST_H_ #include <iostream> #include <utility> class List { private: struct ListNode { int _value; ListNode* _next; }; public: List(): _head(nullptr) {} ~List() { while (nullptr != _head) { auto tmp =

数据结构与算法系列四(单链表)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

看图理解单链表的反转

如何把一个单链表进行反转? 方法1:将单链表储存为数组,然后按照数组的索引逆序进行反转. 方法2:使用3个指针遍历单链表,逐个链接点进行反转. 方法3:从第2个节点到第N个节点,依次逐节点插入到第1个节点(head节点)之后,最后将第一个节点挪到新表的表尾. 方法4:   递归(相信我们都熟悉的一点是,对于树的大部分问题,基本可以考虑用递归来解决.但是我们不太熟悉的一点是,对于单链表的一些问题,也可以使用递归.可以认为单链表是一颗永远只有左(右)子树的树,因此可以考虑用递归来解决.或者说,因为单

C语言写单链表的创建、释放、追加(即总是在最后的位置增加节点)

昨天周末给学妹讲了一些指针的知识,本来我对指针就是似懂非懂的状态,经过昨天一讲,我对指针的学习就更深刻了果然给别人讲课也是学习的一个方法.加上最近复习数据结构,发现我的博客里没有链表的博文,所以趁这时候加上一篇. 在此之前,我们先谈一下我要说的一些基本知识: ①函数参数为什么是双指针? 我们先写一下这么一个程序: # include<stdio.h>void Gai(int m){ m=5;}int main(void){ int a=1; Gai(a); printf("%d\n&

线性表之单链表学习小结(初学数据结构必看)

花了好几个小时,详细规划出了整个过程,包括所有基本操作...有什么疑问请下方留言 #include<iostream> using namespace std; #define ElemType char #define ERROR 0 #define OK 1 typedef struct Node { ElemType data; struct Node *next; }Node,*LinkList; void init_linklist(LinkList L)/*对单链表进行初始化*/