算法介绍——快速排序

快速排序

快速排序是最常用的排序算法。

快速排序是从冒泡排序演变而来的算法,但是比冒泡排序要高效得多,所以叫做快速排序。快速排序之所以快速,是因为它使用了 “分治法”。

和冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。不同的是,冒泡排序在每一轮只把一个元素移动到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。

在这里插入图片描述

  • 红色:基准元素
  • 浅灰:比基准元素小的元素
  • 深灰:比基准元素大的元素

那么,问题来了!如何选择基准元素?又如何把其他元素移动到基准元素的两端呢?

举例说明

假设我们要对 “6 1 2 7 9 3 4 5 10 8” 这 10 个数进行排序。首先在这个序列中随便找一个数作为基准数。为了方便,我们选择第一个数 6 作为基准数。接下来需要将这个序列中所有比基准数大的数放在 6 的右边,比基准数小的数放在 6 的左边。

方法其实很简单:分别从初始序列两端开始探测,先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换它们。

为了达到这个目的,我们可以创建两个指针变量 i 和 j,分别指向序列的两端。这个过程如下图所示(注意:每次都必须是指针 j 先出发)。

在这里插入图片描述

第一轮结束后,可以看到此时序列以基准数 6 为分界点,其左边的数都小于等于 6,右边的数都大于等于 6。

接下来,以 6 为分界点将序列拆分成两个新序列,左边的序列是 “3 1 2 5 4”,右边的序列是 “9 7 10 8”。虽然这两个序列还是乱序的,但不用怕,我们利用刚刚的方法来分别处理左边和右边的序列即可。整个过程如下图所示。

在这里插入图片描述

到此,排序全部结束!

快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,这样排序也就结束了。

C代码实现

#include <stdio.h>
#include <assert.h>

static void qsort(int* a, const unsigned int size)
{
	assert(a);

	if(size < 2) return;

	int base = a[0];     /* 以序列中的第一个数为基准值 */
	int *i = &a[0];      /* 指针变量i指向第一个 */
	int *j = &a[size-1]; /* 指针变量j指向最后一个 */
	int t;
	static unsigned int round = 0;

	printf("\nRound %u: size=%u, i=%d, j=%d\n", ++round, size, *i, *j);

	while(i != j) {
	
		while(*j >= base && j > i) j--; /* 先从右往左找小于base的数 */
		while(*i <= base && i < j) i++; /* 再从左往右找大于base的数 */

		printf("%d, %d\n", *i, *j);

		if(i < j) {  /* 如果i和j没有重合则交换位置 */

			t = *i; *i = *j; *j = t;  
		}
		else {  /* 如果i和j重合则将基准数归位 */
		
			t = *i; *i = a[0]; a[0] = t;

			int *left = &a[0]; /* 左子序列 */
			int *right = i+1;  /* 右子序列 */

			qsort(left, (i-&a[0]));       /* 递归处理左子序列 */
			qsort(right, (&a[size-1]-i)); /* 递归处理右子序列 */
		}
	}
}

int main(void)
{
	int a[10] = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
	int i;
    
	for(i=0; i<10; i++) 
		printf("%d ", a[i]);
	printf("\n");

	qsort(a, 10); /* 快速排序 */

	printf("\n");
	for(i=0; i<10; i++) 
		printf("%d ", a[i]);
	printf("\n");

	return 0;
}

编译并执行代码,如下:

在这里插入图片描述

这里把每一轮排序的情况打印出来了,小伙伴们可以对照 i 和 j 的位置,加深对快速排序算法的理解。

复杂度分析

快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。

因此,快速排序的最差时间复杂度和冒泡排序一样,也就是 O ( N 2 ) \rm{O(N^2)} ,平均时间复杂度为 O ( N l o g N ) \rm{O(NlogN)}

发布了299 篇原创文章 · 获赞 1219 · 访问量 159万+

猜你喜欢

转载自blog.csdn.net/luckydarcy/article/details/102493329