浅析快速排序算法

1.快速排序算法简介

        快速排序算法有什么作用?快速排序算法有什么优势?快速排序算法基于什么原理实现?既然是简介,我想,就应该把这三个问题先介绍一遍。

        快速排序算法的作用,与冒泡算法相类似,就是对无序的序列进行排序,使无序的序列成为有序的序列。就像我们一屋子的杂物,经过我们的整理,可以变得井然有序。

        快速排序算法是对冒泡算法的改进,当然具有相当的优势,但是具体有什么样的优势呢?快速排序算法在平均时间性能而言,相较于其它的各种排序算法(如堆排序、归并排序),它所需要的时间最少。当然,在最坏的情况下,它的时间性能可能不如堆排序和归并排序,但这并不妨碍它成为最常使用的算法。它的排序时间只有冒泡算法的4%。至于其它的数学概念,笔者就不打算多提了,这篇文章主要讲算法实现的原理和思路,并最终写出一个能够进行快速排序的简单程序。

        快速排序算法的基本思想是:在待排序序列中任选一个记录作为枢轴(也叫哨兵值、主元、轴值等等),通过一趟排序将待排序的记录分割成两个独立的部分(比如图1.1中的子序列1和子序列2),其中前一部分记录关键字均小于等于枢轴的关键字,后一部分记录的关键字都大于等于枢轴的关键字。然后分别对这两个独立的子序列按照同样方法再进行快速排序(如图1.1.1的子序列1又分为子序列1-1和子序列1-2),依次类推,直到分割出的每个子序列只包含一个记录为止。这个时候整个序列就达到有序了。

       上述的过程,打个比方,比如将金沙江大桥作为枢轴,一边是西藏界,另外一边是四川界。西藏界住的主要是藏民,四川界住的主要是汉人。这是第一趟排序。接着,西藏界的再来划分,划分出波蜜、八宿、拉萨等等这些地方;四川界划分出成都、泸定、康定等等这些地方。快速排序就有点类似于这种分而治之的思想。国家先把西南地区的秩序定出来,分开西藏和四川,你们自己去管理,西藏和四川又自己再不断的细分,按照同样的方法,各个地方自己去管理。每个地方都管理有序了,整个大的区域就有序了。

     

        图1.1

       顺便一提,快速排序算法是不稳定的排序方法。例如待排序记录的关键字序列为:{3,4,2,1,2'},以3为枢轴经过一次划分,可得{2',1,2}和{4}两个子序列,对前一个子序列继续进行一趟快速排序,得到有序序列{1,2',2},则排序后的有序序列为{1,2',2,3,4}。两个2的相对顺序在排序前后发生了改变。(注:为了便于区分这两个2,笔者加了一个标记以便区分。)在不同的场景下,应该选择最为合适的算法。只是相对而言,快速排序算法是目前大家公认最佳的排序法。

2.快速排序算法讲解

        为了更直观一点地说明,我们假设有10张不同的数字纸牌,纸牌是洗乱的,然后把纸牌摊开,从左到右,分别是45,32,67,54,34,32,21,25,67,98这十个数字。我们把这十个数字存入一个数组中(可以想像放入十个连续的格子)。如如图2.1所示:


图2.1

        图2.1中的Low和High两个指针(实际上是两个整形的变量)分别用于指示左半部分元素的位置以及右半部分元素的位置。现在是初始值,Low指示的位置是a[0],而High指示的位置是a[9]。

        首先,我们选取一个枢轴,一般来说可以选择原始数组元素中的第一个。例如,这里我们可以选择a[0]中的元素45作为枢轴,赋给一个临时变量Pivotkey,可以想像我们的手作为Pivotkey临时变量,将纸牌45拿在手上。

        第二步,从High所指的位置开始向左搜索,找到第一个小于枢轴(即45)的元素,将它(即25)放到原来枢轴的位置(即a[0]这个空间里)上。可以想像成,我们从a[7]这个格子里把25这张纸牌取出来,然后放到a[0]这个格子里。接着,从Low所指的位置开始向右搜索,找到第一个大于枢轴的元素,这里我们找到的是67,位于a[2]中。我们想像将这张67的纸牌从a[2]这个格子里拿起来。由于刚刚a[7]的格子里的纸牌被我们拿起来了,空了,所以我们把这张67的纸牌又放到a[7]这个格子里面。这样一遍操作以后的结果如图2.2所示(注:红色部分是交换后的结果,下同):


图2.2

        为什么a[2]的格子空了呢?因为作为枢轴的这张纸牌还拿在我们手上。这张牌留到最后才放。

        第三步,High指针继续往左走,发现21小于45,将21这张纸牌从a[6]这个格子中拿起来放到a[2]这个格子里。Low指针继续向右走,发现54大于45,将54这张纸牌拿起来又放到刚刚空了的格子a[6]中。这一遍操作以后的结果如图2.3所示:

      

图2.3

        第四步,High指针继续往左走,发现32小于45,将32这张纸牌从a[5]这个格子中拿起来,放到刚刚为空的格子a[3]中。Low指针继续向右走。我们可以发现后面的数字第一个比45大的数就只有54了。但是快速排序算法有一个限制条件,就是Low指针只能走到与High指针重合的地方,然后就停止。也就是说,Low指针走到a[5]就停止了,这时High=Low。到现在,我们终于可以把手中的枢轴(45)放下了,就放在a[5]这个格子里。结果如图2.4所示:


图2.4

        经过以上的四步,基本上完成了第一次分割。以45为枢轴,左半部分都是小于45的元素,而右半部分都是大于45的元素。后面的对数组0~4和数组6~9的分割排序都是依照这个原理进行的,这里就不再赘述了。也因为这个原因,所以我们可以采用递归的方式实现。


3.快速排序算法的代码实现

#include "stdio.h"

int Partitions(int a[],int Low,int High);//分割排序函数 
void Qsort(int a[],int Low,int High);//通过递归调用进行分割排序 
void Quicksort(int a[],int n);//快速排序 

int main()
{
	int i,a[11]={45,32,67,54,34,32,21,25,67,98};
	
	printf("原始数据:");
	for(i=0;i<11;i++)
	{
		printf("%3d",a[i]);
	}
	printf("\n");
	

	Quicksort(a,11);//快速排序 
	
	
	printf("快速排序:");
	for(i=0;i<11;i++)
	{
		printf("%3d",a[i]);
	}
	printf("\n");
}

int Partitions(int a[],int Low,int High)// 分割
{
	int Pivotkey=a[Low];//取出轴值 

	while(Low<High)//Low和High还没重合时,Low总是小于High的 
	{
		while(Low<High && a[High]>=Pivotkey)//Low和High两指针未重合且High指针所指向的值大于轴值 
		{
			High--;//向左移动,直到High指针所指向的值小于轴值 
		}
      	
   			a[Low]=a[High];//将高位的值放到左边去,即放到低位里去 
  
		while(Low<High && a[Low]<=Pivotkey)//Low和High两指针未重合且High指针所指向的值小于轴值 
		{
			Low++;//向右移动 ,直到High指针所指向的值大于轴值 
		}
      	
   			a[High]=a[Low];//将低位的值放到右边去,即放到高位里去 
	}

		a[Low]=Pivotkey;//把轴值存到中间的数组里去 
		return Low;//返回轴值的位置 
}

void Qsort(int a[],int Low,int High)
{
	int Pivottag;//轴值的位置 

	if(Low<High)
	{
   		//递归调用
   		Pivottag=Partitions(a,Low,High);
   		Qsort(a,Low,Pivottag-1);//区间[Low,Pivottag-1] ,即小于轴值的部分 
   		Qsort(a,Pivottag+1,High);//区间[Pivottag+1,High] ,即大于轴值的部分 
	}
}

void Quicksort(int a[],int n)//快速排序 
{
	Qsort(a,0,n);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值