数据结构——快速排序

以下是数据结构里快速排序的设计思想和代码实现:

算法设计思想

取出序列里某个值为基准值,以基准值为分割点,将待排序序列划分为左右两个子序列。左子序列的所有元素都比基准值小,右子序列都比基准值大;重复此过程,直到整个序列都有序为止。

排序步骤

找基准值的方法:
1、选出待排序序列最左侧元素当基准值;
2、设置两个哨兵left和right,分别位于待排序序列左右两端。
3、右哨兵right先往左走,遇到比基准值小的元素时停止移动;
4、右哨兵right停止移动后,左哨兵left开始向右移动,直到遇到比基准值大的元素时停止移动;
5、交换左右哨兵所指向的元素(注意不是交换左右哨兵);
6、重复3~5过程,直到左右哨兵相遇,此时相遇的点称作“枢轴”,将基准值与枢轴的值交换(此时枢轴的位置的值就是本次排序的基准值,枢轴左侧的元素均比基准值小,枢轴右侧的元素均比基准值大);
7、一分为二,枢轴左右侧的序列便成了两个待排序列,将这两个子序列重复1~6的过程,直到整个序列有序。

4个实例(都以序列最左值为基准值)

实例0(13个元素)

初始状态: 5 8 0 0 5 9 3 7 1 0 0 3 6

此次排序区间:0-12
第1个元素与第11个元素互换 |5 3 0 0 5 9 3 7 1 0 0 8 6 |
第5个元素与第10个元素互换 |5 3 0 0 5 0 3 7 1 0 9 8 6 |
第7个元素与第9个元素互换 |5 3 0 0 5 0 3 0 1 7 9 8 6|
第8个元素与第8个元素互换 |5 3 0 0 5 0 3 0 1 7 9 8 6|
0-12区间排序完毕,枢轴为:8,交换枢轴和基准值。
此时序列: 1 3 0 0 5 0 3 0 5 7 9 8 6

此次排序区间:0-7
第1个元素与第7个元素互换 |1 0 0 0 5 0 3 3| 5 7 9 8 6
第4个元素与第5个元素互换 |1 0 0 0 0 5 3 3| 5 7 9 8 6
第4个元素与第4个元素互换 |1 0 0 0 0 5 3 3| 5 7 9 8 6
0-7区间排序完毕,枢轴为:4,交换枢轴和基准值。
此时序列: 0 0 0 0 1 5 3 3 5 7 9 8 6

此次排序区间:0-3
第0个元素与第0个元素互换 |0 0 0 0| 1 5 3 3 5 7 9 8 6
0-3区间排序完毕,枢轴为:0,交换枢轴和基准值。
此时序列: 0 0 0 0 1 5 3 3 5 7 9 8 6

此次排序区间:1-3
第1个元素与第1个元素互换 0 |0 0 0| 1 5 3 3 5 7 9 8 6
1-3区间排序完毕,枢轴为:1,交换枢轴和基准值。
此时序列: 0 0 0 0 1 5 3 3 5 7 9 8 6

此次排序区间:2-3
第2个元素与第2个元素互换 0 0 |0 0| 1 5 3 3 5 7 9 8 6
2-3区间排序完毕,枢轴为:2,交换枢轴和基准值。
此时序列: 0 0 0 0 1 5 3 3 5 7 9 8 6

此次排序区间:5-7
第7个元素与第7个元素互换 0 0 0 0 1 |5 3 3| 5 7 9 8 6
5-7区间排序完毕,枢轴为:7,交换枢轴和基准值。
此时序列: 0 0 0 0 1 3 3 5 5 7 9 8 6

此次排序区间:5-6
第5个元素与第5个元素互换 0 0 0 0 1 |3 3| 5 5 7 9 8 6
5-6区间排序完毕,枢轴为:5,交换枢轴和基准值。
此时序列: 0 0 0 0 1 3 3 5 5 7 9 8 6

此次排序区间:9-12
第10个元素与第12个元素互换 0 0 0 0 1 3 3 5 5 |7 6 8 9|
第10个元素与第10个元素互换 0 0 0 0 1 3 3 5 5 |7 6 8 9|
9-12区间排序完毕,枢轴为:10,交换枢轴和基准值。
此时序列: 0 0 0 0 1 3 3 5 5 6 7 8 9

此次排序区间:11-12
第11个元素与第11个元素互换 0 0 0 0 1 3 3 5 5 6 7 |8 9|
11-12区间排序完毕,枢轴为:11,交换枢轴和基准值。
此时序列: 0 0 0 0 1 3 3 5 5 6 7 8 9
排序完毕: 0 0 0 0 1 3 3 5 5 6 7 8 9

实例1(10个元素)

初始状态: 1 8 3 9 9 0 2 3 9 7

此次排序区间:0-9
第1个元素与第5个元素互换 |1 0 3 9 9 8 2 3 9 7 |
第1个元素与第1个元素互换 1 0 3 9 9 8 2 3 9 7
0-9区间排序完毕,枢轴为:1,交换枢轴和基准值。
此时序列: 0 1 3 9 9 8 2 3 9 7

此次排序区间:2-9
第3个元素与第6个元素互换 0 1 |3 2 9 8 9 3 9 7 |
第3个元素与第3个元素互换 0 1 |3 2 9 8 9 3 9 7 |
2-9区间排序完毕,枢轴为:3,交换枢轴和基准值。
此时序列: 0 1 2 3 9 8 9 3 9 7

此次排序区间:4-9
第9个元素与第9个元素互换 0 1 2 3 |9 8 9 3 9 7 |
4-9区间排序完毕,枢轴为:9,交换枢轴和基准值。
此时序列: 0 1 2 3 7 8 9 3 9 9

此次排序区间:4-8
第5个元素与第7个元素互换 0 1 2 3 |7 3 9 8 9| 9
第5个元素与第5个元素互换 0 1 2 3 |7 3 9 8 9| 9
4-8区间排序完毕,枢轴为:5,交换枢轴和基准值。
此时序列: 0 1 2 3 3 7 9 8 9 9

此次排序区间:6-8
第7个元素与第7个元素互换 0 1 2 3 3 7 |9 8 9| 9
6-8区间排序完毕,枢轴为:7,交换枢轴和基准值。
此时序列: 0 1 2 3 3 7 8 9 9 9
排序完毕: 0 1 2 3 3 7 8 9 9 9

实例2(7个元素)

初始状态: 3 2 5 0 0 4 1

此次排序区间:0-6
第2个元素与第6个元素互换 |3 2 1 0 0 4 5|
第4个元素与第4个元素互换 |3 2 1 0 0 4 5|
0-6区间排序完毕,枢轴为:4,交换枢轴和基准值。
此时序列: 0 2 1 0 3 4 5

此次排序区间:0-3
第0个元素与第0个元素互换 |0 2 1 0| 3 4 5
0-3区间排序完毕,枢轴为:0,交换枢轴和基准值。
此时序列: 0 2 1 0 3 4 5

此次排序区间:1-3
第3个元素与第3个元素互换 0 |2 1 0| 3 4 5
1-3区间排序完毕,枢轴为:3,交换枢轴和基准值。
此时序列: 0 0 1 2 3 4 5

此次排序区间:1-2
第1个元素与第1个元素互换 0 0 1 2 3 4 5
1-2区间排序完毕,枢轴为:1,交换枢轴和基准值。
此时序列: 0 0 1 2 3 4 5

此次排序区间:5-6
第5个元素与第5个元素互换 0 0 1 2 3 4 5
5-6区间排序完毕,枢轴为:5,交换枢轴和基准值。
此时序列: 0 0 1 2 3 4 5
排序完毕: 0 0 1 2 3 4 5

实例3(7个元素)

初始状态: 2 8 6 3 3 5 7

此次排序区间:0-6
第0个元素与第0个元素互换 |2 8 6 3 3 5 7|
0-6区间排序完毕,枢轴为:0
此时序列: 2 8 6 3 3 5 7
(刚好基准值是区间里的最小值,所以此次划分只有右侧序列一个序列,相应的如果基准值刚好是区间里的最大值,划分后只有左侧序列)
此次排序区间:1-6
第6个元素与第6个元素互换 2 |8 6 3 3 5 7|
(这里出现元素换自己,是因为代码里防止两个数相同时两个哨兵都不前进陷入死循环,所以设置两个元素相同时继续进行移动交换操作)
1-6区间排序完毕,枢轴为:6
此时序列: 2 7 6 3 3 5 8

此次排序区间:1-5
第5个元素与第5个元素互换 2 |7 6 3 3 5| 8
1-5区间排序完毕,枢轴为:5
此时序列: 2 5 6 3 3 7 8

此次排序区间:1-4 第2个元素与第4个元素互换 2 |5 3 3 6| 7 8 第3个元素与第3个元素互换 2 |5 3 3 6| 7 8
1-4区间排序完毕,枢轴为:3
此时序列: 2 3 3 5 6 7 8(记得最后一步是把枢轴的值和基准值交换喔)

此次排序区间:1-2
第1个元素与第1个元素互换 2 |3 3| 5 6 7 8
1-2区间排序完毕,枢轴为:1
此时序列: 2 3 3 5 6 7 8
排序完毕: 2 3 3 5 6 7 8

关于稳定杂性

快速排序是不稳定排序,因为在快速排序的随机选择比较子(即pivot)阶段,如果选择一个数作为比较子,而把大于等于比较子的数均放置在大数数组中,则原数组中相等的两个数非原序,这就是“不稳定”。

关于复杂性

快速排序的时间复杂度为O(nlogn)。最好时间复杂度为O(nlogn),最坏时间复杂度为O(n^2),平均时间复杂度为O(nlogn)。

关于复杂性

快速排序的空间复杂度为O(logn)。最优的情况下空间复杂度为O(logn),每一次都平分数组的情况。最差的情况下空间复杂度为O(n),退化为冒泡排序的情况。

源代码(风格参考严蔚敏版数据结构,但过程作了降时间优化,具体过程不同书上):

/*
		广西师范大学 计算机科学与工程学院 
		GuangXi Normal University 
		College of Computer Science and Engineering  
		Student STZ 
*/ 
#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define ElemType int
#define Status int//表示状态 
#define OK 1
#define ERROR 0
#define maxnum 13
#define max 10

typedef struct SqList{
	ElemType *list;
	ElemType lenght;
}SqList,*Sl;

Status initial(Sl sl){
	sl->list = new ElemType[maxnum];
	sl->lenght = maxnum;
	srand((unsigned)time(NULL));//时间种子
	for(int i=0;i<sl->lenght;i++){//随机生成10个数
		sl->list[i] = rand()%max;
	}
	return 1;
}

void showList(SqList sl){
	for(int i=0;i<maxnum;i++){
		cout<<sl.list[i]<<" ";
	}
	cout<<endl;
}

void swap(SqList &sl,int i,int j){
	int t = sl.list[i];
	sl.list[i] = sl.list[j];
	sl.list[j] = t;
}

int PartSort(SqList &sl,int left,int right){
	int L = left,R = right;//纯属用来输出使用,无实际意义 
	cout<<"\n此次排序区间:"<<L<<"-"<<R<<endl; 
	//left和right表示左右哨兵,left哨兵从左往右走,right哨兵从右往左走 
	int pivotindex = left;//取区间最左边元素为key
	while(left<right){//两个哨兵未相遇
		//right哨兵先走 
		while(left<right&&sl.list[pivotindex]<=sl.list[right]){
			right--;//找到比key小的元素就跳出 
		}
		//找到比key小元素了,此时right哨兵停止移动,left哨兵开始移动 
		while(left<right&&sl.list[pivotindex]>=sl.list[left]){
			left++;//找到比key大的元素就跳出 
		} 
		//交换这哨兵所指的元素
		swap(sl,left,right);
		cout<<"第"<<left<<"个元素与第"<<right<<"个元素互换"<<endl; 
		showList(sl);
	}
	int encounter = left;//哨兵相遇(枢轴)时指向的元素,和key交换
	swap(sl,pivotindex,encounter);
	cout<<L<<"-"<<R<<"区间排序完毕,枢轴为:"<<encounter<<",交换枢轴和基准值。"<<endl;
	cout<<"此时序列:\n";showList(sl); 
	return encounter;//返回枢轴 
}

void qSort(SqList &sl,int left,int right){
	if(left<right){
		int pivot = PartSort(sl,left,right);
		qSort(sl,left,pivot-1);//左子序列 
		qSort(sl,pivot+1,right);//右子序列 
	}
	
}

void quickSort(SqList &sl){
	qSort(sl,0,sl.lenght-1);
} 

int main(){
	SqList sl;
	if(!initial(&sl)){
		cout<<"初始化失败"<<endl;
		return 0;
	}
	cout<<"初始化完毕。初始状态:"<<endl;
	showList(sl);
	quickSort(sl);
	cout<<"排序完毕:"<<endl;
	showList(sl);
	return 0;
} 

再补充两个例子吧:

在这里插入图片描述
在这里插入图片描述

源代码(书上同款过程的代码):

应一些小伙伴的要求,写了一个同书上的过程。书上的过程和优化过后的过程区别在于:书上原版的过程使用的是“不停更新key”的思想,而优化过的代码使用的思想是“找到区间最初的key的最终归宿后再更新key的位置”的思想。优化过后的代码的操作次数比书本原版的操作次数大概减少了一半。

#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define ElemType int
#define Status int//表示状态 
#define OK 1
#define ERROR 0
#define maxnum 13
#define max 10

typedef struct SqList{
	ElemType *list;
	ElemType lenght;
}SqList,*Sl;

Status initial(Sl sl){
	sl->list = new ElemType[maxnum];
	sl->lenght = maxnum;
	srand((unsigned)time(NULL));//时间种子
	for(int i=0;i<sl->lenght;i++){//随机生成10个数
		sl->list[i] = rand()%max;
	}
	return 1;
}

void showList(SqList sl){
	for(int i=0;i<maxnum;i++){
		cout<<sl.list[i]<<" ";
	}
	cout<<endl;
}

void swap(SqList &sl,int i,int j){
	int t = sl.list[i];
	sl.list[i] = sl.list[j];
	sl.list[j] = t;
}

int PartSort(SqList &sl,int left,int right){
	int L = left,R = right;//纯属用来输出使用,无实际意义 
	cout<<"\n此次排序区间:"<<L<<"-"<<R<<endl; 
	//left和right表示左右哨兵,left哨兵从左往右走,right哨兵从右往左走 
	int pivotindex = left;//取区间最左边元素为key
	while(left<right){//两个哨兵未相遇
		//right哨兵先走 
		while(left<right&&sl.list[pivotindex]<=sl.list[right]){
			right--;//找到比key小的元素就跳出 
		}
		swap(sl,pivotindex,right);//更改key 
		//找到比key小元素了,此时right哨兵停止移动,left哨兵开始移动 
		while(left<right&&sl.list[pivotindex]>=sl.list[left]){
			left++;//找到比key大的元素就跳出 
		} 
		swap(sl,pivotindex,left);//更改key
		cout<<"第"<<left<<"个元素与第"<<right<<"个元素互换"<<endl; 
		showList(sl);
	}
	int encounter = left;//哨兵相遇(枢轴)时指向的元素,和key交换
	swap(sl,pivotindex,encounter);
	cout<<L<<"-"<<R<<"区间排序完毕,枢轴为:"<<encounter<<",交换枢轴和基准值。"<<endl;
	cout<<"此时序列:\n";showList(sl); 
	return encounter;//返回枢轴 
}

void qSort(SqList &sl,int left,int right){
	if(left<right){
		int pivot = PartSort(sl,left,right);
		qSort(sl,left,pivot-1);//左子序列 
		qSort(sl,pivot+1,right);//右子序列 
	}
	
}

void quickSort(SqList &sl){
	qSort(sl,0,sl.lenght-1);
} 

int main(){
	SqList sl;
	if(!initial(&sl)){
		cout<<"初始化失败"<<endl;
		return 0;
	}
	cout<<"初始化完毕。初始状态:"<<endl;
	showList(sl);
	quickSort(sl);
	cout<<"排序完毕:"<<endl;
	showList(sl);
	return 0;
} 

两款代码的区别:

//优化后的代码
while(left<right){//两个哨兵未相遇
		//right哨兵先走 
		while(left<right&&sl.list[pivotindex]<=sl.list[right]){
			right--;//找到比key小的元素就跳出 
		}
		//找到比key小元素了,此时right哨兵停止移动,left哨兵开始移动 
		while(left<right&&sl.list[pivotindex]>=sl.list[left]){
			left++;//找到比key大的元素就跳出 
		} 
		//交换这哨兵所指的元素
		swap(sl,left,right);
	}
//书本过程
while(left<right){//两个哨兵未相遇
		//right哨兵先走 
		while(left<right&&sl.list[pivotindex]<=sl.list[right]){
			right--;//找到比key小的元素就跳出 
		}
		swap(sl,pivotindex,right);//更改key 
		//找到比key小元素了,此时right哨兵停止移动,left哨兵开始移动 
		while(left<right&&sl.list[pivotindex]>=sl.list[left]){
			left++;//找到比key大的元素就跳出 
		} 
		swap(sl,pivotindex,left);//更改key
	}

很明显,一个大的while之下,书本的过程使用了两次swap,而优化过后的代码只使用了一次swap。当然,两个都是快排方法,没有谁对谁错,只有性能谁好点谁差点的区别,仅此而已。

敬请批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芣苢的成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值