算法 - 快速排序(Quick_sort)

发布于:2023-01-30 ⋅ 阅读:(1637) ⋅ 点赞:(1)

目录

什么是快速排序?

快速排序的使用场景:

演示快速排序的过程:

第一趟排序:

第二趟排序:

通过代码来实现:

 对快速排序的总结:


什么是快速排序?

在写快速排序的代码之前,我们先对快速排序的排序原理以及定义进行梳理:

快速排序(Quick_sort)是对冒泡排序的一种改进,它也是属于冒泡类的方法。它的基本思想是通过一趟排序将待排序列分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后对这两部分的分割后的序列再次进行排序,直到达到整个序列有序的目的。

快速排序相当于是冒泡排序升级版本,排序特点就是越乱越快,所以也是一种高效的算法。快速排序就是一个给基准数据并找到其正确索引位置的过程。

快速排序的使用场景:

对于数据量大,数据分布高度随机的场景,使用快速排序算法的效率是非常高的,甚至高于堆排序。

演示快速排序的过程:

我在这里给出来一个随机数组:

int ar[11] = {12,21,6,3,23,2,3,2,1,14,3};

我们用一个总图来描绘快速排序的过程:

 

在这里我重新给出一个数组我们分步来分析:

int ar[10] = {4,7,9,6,5,3,10,2,8,1};

第一趟排序:

接下来我们在画板上演示快速排序的过程:

在这里我们首先选定基准元素,基准元素的选择是随机的,我们为了方便起见直接选择序列的第一个元素4作为基准元素,然后我们定义两个指针变量分别指向序列的开始和序列的末尾。我们把基准元素拿出来,此时第一个元素的位置就相当于是一个坑。 

我们从右边的指针开始,把指针指向的元素和基准元素做比较,如果指针所指向的元素比基准元素大则把指针向左移动,如果指针所指向的元素是小于基准元素的我们则把指针所指向的元素填入坑中。我们发现此时的1是小于4的,则把1填入至坑中。

这时1原本的位置成为了新的坑,同时p指针向后移动一位,此时我们发现p所指向的元素7是大于基准元素4的,于是我们把7填入坑的位置。

此时7原本的位置又成为了新的坑,我们现在将q指针向左移动,碰到了元素8大于基准元素,不动,直到碰到比基准元素小的数停下,此时q停到了2的位置,2  < 4我们这时将2填入坑的位置。

此时2原本的位置成为了新的坑,p指针向右移动,我们碰到了元素9,元素9是大于基准元素的,此时将元素9填入到坑的位置。

继续上述操作,原本9的位置变为了新的坑,q指针继续向左移动,我们发现当q指向元素3的时候,3小于基准元素,于是将3填入坑的位置。

原本3的位置变为了新坑,p指针向后移动我们发现元素6大于基准元素,于是将6填入坑中。

此时原本6的位置变为了新坑,q指针继续向左移动,移动一位发现元素5是大于基准元素的,所以5元素不动,继续向左移动,此时p指针和q指针相撞,这时我们将基准元素填回现在的坑中,此时第一趟排序完成。

我们更改元素的颜色,此时原基准元素4 左侧的蓝色方框的元素代表小于基准元素的区域,右侧黄色方格的元素代表大于基准元素的区域,

第二趟排序:

现在相当于我们在基准元素的两侧又重新分为了两列,左边的都小于基准元素,右边的都大于基准元素。然后这两列数列使用刚才的方法进行第二趟排序,这也是分治法的思想。

此时我们先对4左侧的元素进行快速排序:

此时我们将2元素拿出来当作基准元素,从右侧q指针所指向的元素进行比较,我们发现3大于1,继续将q指针向左侧移动,我们发现2大于1,q向左移动与p指针对撞,排序结束,将基准元素放回坑的位置。继续将2作为基准元素,从右侧指针q开始比较,我们发现3大于2,位置不动,q指针向左侧移动与p指针相撞排序结束。

现在我们对4右侧的元素进行快速排序:

此时我们将5元素拿出来当作基准元素,5的位置为坑,q指针指向的元素开始和基准元素做比较,q指针每一次向左移动所指向的元素均是大于5的,位置不动,直到q指针和p指针相撞,排序结束。

第二趟我们用6作为基准元素,q现在向左边移动我们发现q每一次的移动所指向的元素均是大于6的,所以他们的位置不动。p指针所指向的5元素是小于6元素的,位置不动,现在重新将6元素置于坑中。

左子序列现在的长度仅为1,所以无需再做比较。此时我们将元素10作为基准元素拿出来,10原来的位置为坑,从右侧q指针所指向的元素7开始比较,我们发现7是小于10元素的,所以我们将7填入坑中(原来10的位置)。现在移动左侧p指针,p指针向后移动的每一个元素都是小于基准元素的,我们将10元素填入坑中,此时序列为:

指针对撞,第三趟排序结束。现在我们开始第四趟排序,将9作为基准元素拿出来,p指针指向9所在的位置:

现在从右侧q指针开始比较,10是大于9的位置不动,q向左迁移一位,8是小于9的,我们将8填入坑中,此时指针p向后迁移一位与指针q对撞,将9置于新的坑中,第四趟排序结束。

我们现在进行第五趟排序,右自序列的长度为 1,已经不需要再比较,排序完成。 

通过代码来实现:

#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
#define MAXSIZE 11
void initar(int *ar,int len)
{
	assert(ar != nullptr);
	for(int i = 0;i < len;i++){
		ar[i] = rand() % 30;
	}
}
void showar(int *ar,int len)
{
	assert(ar != nullptr);
	for(int i = 0;i < len;i++){
		printf("%d ",ar[i]);
	}
	printf("\n--------------------------\n");
}
void Quick_sort(int *ar,int left,int right)
{
	assert(ar != nullptr);
	if(left >= right){
		return;
	}
	int i = left;//使用i下标代表左边界
	int j = right;//使用j下标代表右边界
	int pivot = ar[i];//pivot代表的就是基准元素,将i下标对应的元素取出当作基准元素
	while(i < j){//循环条件为左边界小于右边界
		while(i < j && ar[j] >= pivot)//循环条件为当右侧j下标对应的元素大于基准元素时
		j--;//j下标向左迁移一位
		ar[i] = ar[j];//将j下标对应的值挪到坑的位置,这里可以理解为将值赋给i下标对应的元素位置,也就是大的往前挪
		while(i < j && ar[i] <= pivot)//循环条件为当右侧下标j对应的值小于基准元素时
		i++;//i下标向右侧迁移一位
		ar[j] = ar[i];//就是把小值往后挪,进行赋值操作
	}
	ar[i] = pivot;//基准元素更新
	Quick_sort(ar,left,i - 1);//递归,基准元素的左半部分
	Quick_sort(ar,i + 1,right);//递归,基准元素的右半部分
}
int main()
{
    srand((unsigned int)time(NULL));
	int ar[MAXSIZE];
	initar(ar,MAXSIZE);
	printf("原始数据为:\n");
	showar(ar,MAXSIZE);
    printf("\n经过快速排序后的数据为:\n");
	Quick_sort(ar,0,MAXSIZE - 1);
	showar(ar,MAXSIZE);
    return 0;
}

运行结果:

排序完成 

 对快速排序的总结:

快速排序(Quick_sort)的特点就是:序列越乱越快,越有序越慢。

时间复杂度:

最优情况:O(nlogn)每趟排序数据元素都能被平均分配成两个部分,形成一个完美的二叉树。

最坏情况:O(n^{2})也就是原序列相对最有序的情况,形成的树只有左子树和右子树,比较次数为:(n - 1) + (n - 2) + (n - 3)+……+ 1 = n * (n - 1)/2

空间复杂度:

O(1)

和希尔排序类似,序列元素的比较与交换是跳跃进行的,会改变元素的相对位置,所以快速排序依旧是一个不稳定算法,但这也不妨碍它是所有排序算法中平均效率最高的算法。


网站公告

今日签到

点亮在社区的每一天
去签到

热门文章