算法入门——排序算法(快速排序)

快速排序的思想是:
找到一个基准数,并将其余的数与基准数依次进行比较,将比基准数大的数放在哨兵的一边,再将小于基准数的数放在另一边(等于基准数的数可放在哨兵的任意一边)。

基准数就是一个用来参照的数。举个例子来展现它的作用:我们现在对“6 1 2 7 9 3 4 5 10 8”这10个数进行排序。
首先我们随便找一个数作为基准数,为了方便,我们就将第一个数 6 作为基准数。接下来,需要将这个序列中所有比基准数大的数放在 6 的右边,比基准数小的数放在 6 的左边,得到:
3 1 2 5 4 6 9 7 10 8
6 的原始序列在第一位,我们的目标就是将 6 挪到序列中间的某个位置,假设这个位置是 k ,现在就需要我们寻找k,并且以第 k 位为分界点,左边的数都小于等于 6 ,右边的数都大于等于 6 。仔细想一想,我们该如何完成这个操作?
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换它们。这里可以用两个变量(哨兵)i 和 j,分别指向序列的最左边和最右边。及让哨兵 i 指向数字6,哨兵 j 指向数字8。
在这里插入图片描述

首先哨兵 j 开始出动(因为我们的基准数是最左边的数,所以需要让哨兵 j 先出动。为什么需要这样,大家开动脑筋想一想)。哨兵 j 一步一步地向左移(即 j --),直到找到一个小于 6 的数停下来。接下来哨兵 i 再一步一步地向右移动(即 i++),直到找到一个大于 6 的数停下来。最后哨兵 j 停在了数字 5 的面前,哨兵 i 停在了数字 7 的面前。
在这里插入图片描述

在这里插入图片描述

现在交换哨兵 i 和哨兵 j 所指向的元素的值。交换后的序列如下:
6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。接下来哨兵 j 继续向左移动(每次必须是哨兵 j 先出发),此时哨兵 j 到达4(比基准数 6 要小)之后停了下来。哨兵 i 继续向右移动,到达 9 (比基准数 6 要大)之后停了下来。此时将进行交换,交换后的序列如下:
6 1 2 5 4 3 9 7 10 8
在这里插入图片描述

第二次交换结束,“探测”继续。哨兵 j 继续向左移动,发现了3之后停了下来。哨兵 i 继续向右移动,我们此时会发现一个问题,那就是哨兵 i 和哨兵 j 相遇了,哨兵 i 和哨兵 j 都走到了3 的面前。不要担心,这种情况就表示我们的“探测”结束。我们将基准数 6 和 3 进行交换。交换之后的序列如下:
3 1 2 5 4 6 9 7 10 8
在这里插入图片描述
到此第一轮的“探测”真正结束。此时以基准数 6 为分界点,6左边的数都小于等于6 ,右边的数都大于等于6 。回顾我们刚才的一系列操作,很容易理解哨兵 j 的任务就是要找到小于基准数的数,哨兵 i 的任务就是找到大于基准数的数,直到哨兵i 和哨兵j 碰头为止。
现在基准数6 已经归位,它正好处在序列第6位。此时原来的序列以6 为分界点拆分成了两个序列,左边的序列是“ 3 1 2 5 4”,右边的序列是“9 7 10 8”。虽然现在的序列还是很乱,但是不要担心,我们已经掌握了方法。我们只需要将左边的序列和右边的序列分别进行我们的“探测”就可以了。最终的排序结果流程如下图:
在这里插入图片描述

快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放在基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样每次交换的时候就就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然,在最坏的情况下人可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的,都是O(N^2),它的平均时间复杂度为O(NlogN),其实快速排序是基于一种叫做“二分”的思想。
具体代码如下:

#include<stdio.h>
//定义全局变量a[101]和n,这两个变量会在主函数和我们定义的quicksort函数里面使用
int a[101], n;

//定义快速排序函数
void quicksort(int left, int right) {
	int i, j, t, temp;
	if(left > right)    
		return;
	
	temp = a[left];  //用temp来保存基准数 
	i = left;
	j = right;
	
	while(i!=j){
		//先从右往左开始 
		while(a[j]>=temp && i<j)
			j--;
			
		//在从左往右 
		while(a[i]<=temp && i<j)
			i++;
			
		//当i和j没有相遇时,将两个数进行交换 
		if(a[i]>a[j]){
			t = a[i];
			a[i] = a[j];
			a[j] = t;
		}
		
		//将基准数放大它应该在的位置 
		a[left] = a[i];
		a[i] = temp;
		
		//采用递归的方法来处理基准数左右两边的数 
		quicksort(left,i-1);   //处理基准数左边的数 
		quicksort(i+1,right);   //处理右边的数 
		return;
	}
}

//主函数
int main(){
	int i;
	printf("乱序的数一共有几位?");
	scanf("%d",&n); 
	
	printf("输入这些数:");
	for(i=1; i<=n; i++) 
		scanf("%d",&a[i]);
	
	quicksort(1,n);
	
	printf("排完序之后:\n");
	for(i=1; i<=n; i++) 
		printf("%d ",a[i]);
	
} 

可以输入以下数据进行验证:

10
6 1 2 7 9 3 4 5 10 8

运行结果是:

1 2 3 4 5 6 7 8 9 10

下面是程序执行过程中数组a的变化过程,带红色字体的数表示的是已归位的基准数。

6 1 2 7 9 3 4 5 10 8
3 1 2 5 4 6 9 7 10 8
2 1 3 5 4 6 9 7 10 8
1 2 3 5 4 6 9 7 10 8
1 2 3 5 4 6 9 7 10 8
1 2 3 4 5 6 9 7 10 8
1 2 3 4 5 6 9 7 10 8
1 2 3 4 5 6 8 7 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

L-阿烽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值