排序问题-快速排序

快速排序是在已知的排序算法中排序速度最快的,它的时间复杂度是O(NlogN),之所以特别快,只要是由于内部循环非常的精炼并且高度优化。和归并排序类似,快排也是基于分治的递归算法,将一个序列S快拍分为四步:

1.如果S中只有1个或者0个元素,则结束排序

2.在S中选取一个元素v作为快排使用的“枢轴”;

3.将S中除去枢轴v的元素分成两个分别大于枢轴和小于数轴的不相交的序列;

4.分别对对小于和大于枢轴的序列进行上述的递归。

分析快排的四个步骤不难发现,一般的快排算法还有很多可以优化的地方,针对前三步有以下的优化策略:

1.实际上不管是什么样的排序算法,对于很小规模的序列进行排序都很难体现其优越性,并且有的算法看起来还很臃肿;另外还有就是递归算法虽然看起来很简洁,但是比不上循环实现的效率。基于这两点,可以看出在适当的时候结束快排的递归就很有必要。

2.如果对一个元素个数为N的逆序的序列进行顺序排序,并且每次选取的“枢轴“都是当前序列中第一个元素,这样就会出现最坏的情况(即进行了N次递归嵌套),算法退化到时间复杂度为O(N2),示意图:

省略...

由此看出”枢轴“的选取是非常重要的,一个理想的数轴是选取数列中位于中间的元素,这样每次划分都可以将一半数据放在枢轴前一半在后,类似于平衡的二叉排序树,树的高度降到了最小。

########################################################################################

针对快排的两条优化策略:

在递归到规模较小的时候进行插入排序,最恰当的阈值还没有有效的证明,这里我选取了N=10,

源码如下(插入排序已经在”排序问题“的第一节时候分析了):

/*insert sort*/
void insert_sort(int arr[], int count)
{
	int i, pos;
	int tmp;

	for(pos = 1; pos < count; pos++)
	{
		tmp = arr[pos];
		for(i = pos; i > 0 && arr[i - 1] > tmp; i--)
		{
			arr[i] = arr[i - 1];
		}
		arr[i] = tmp;
	}
}

有几种方法可以”枢轴“进行选取:

1.选取指定位置元素作为枢轴,这样很可能导致最坏情况的产生;

2.产生一个随机数指向位置的元素作为数轴,但是由于目前的计算机都是确定的有限自动机,产生的都是伪随机数(如果种子选取的不好可能会成第一种情况),产生一个随机数的代价很大,在高速排序算法中得不偿失。最主要的是一个随机数并不能有效的降低排序树的高度。

3.估算出序列大致的中值。可以选取几个位置的元素计算出中值,并将其作为整个序列中值的估计值。这里使用第一最后和中间三个元素的中值作为“枢轴”,源码如下:

/*找出枢轴,头尾中间三个元素的中位数*/
int median3(int arr[], int left, int right)
{
	int center;

	center = (left + right) / 2;

	if(arr[left] > arr[center])
	{
		swap(&arr[left], &arr[center]);
	}
	if(arr[left] > arr[right])
	{
		swap(&arr[left], &arr[right]);
	}
	if(arr[center] > arr[right])
	{
		swap(&arr[center], &arr[right]);
	}

	swap(&arr[center], &arr[right - 1]);
	return arr[right -1];
}

从参考书上看到还有分割的优化策略:

第一步是通过将枢轴与最后元素交换使得枢轴元离开要被分割的数据段。i从第一个元素开始而j从倒数第二个元素开始,示意图:

在分割截断要做的就是把所有相对于枢轴的小元素移到枢轴的左边,把所有相对于数轴大的元素移到枢轴的右边。

1.当i在j的左边时,将i右移跳过小于枢轴的元素;

2.将j左移跳过大于枢轴的元素;

3.当i和j停止时,i指向的是一个大于枢轴的元素,而j指向的是一个小于枢轴的元素,这个时候互换i和j指向的元素

4.一直重复上边的步骤,直到i和j相遇。

一趟排序完成。源码如下:

/*快排的主例程*/
void q_sort(int arr[], int left, int right)
{
	int i, j;
	int pivot;

	if(left + CUT_OFF <= right)
	{
		pivot = median3(arr, left, right);
		i = left;
		j = right - 1;

		while (1)
		{
			while(arr[++i] < pivot){}
			while(arr[--j] > pivot){}
			if(i < j)
			{
				swap(&arr[i], &arr[j]);
			}
			else
			{
				break;
			}
		}
		swap(&arr[i], &arr[right - 1]);

		q_sort(arr, left, i - 1);
		q_sort(arr, i + 1, right);
	}
	/*如果序列小于10,则使用插入排序*/
	else
	{
		insert_sort(arr+left, right - left + 1);
	}
}

在上边的排序中,如果i和j遇到了等于枢轴元素的关键字,那么就让i和j都停止,这样可以将等于枢轴的关键字等分的划入枢轴两边的序列中,尽可能的将两边元素数保持接近以降低树的高度。

快排的驱动程序:

int quick_sort(int arr[], int count)
{
	q_sort(arr, 0 , count - 1);
}

要处理的第一个例程是枢轴的选取,最简单的是对A[left],A[right], A[center]进行排序,三个元素中最大值被放在A[right]中,把A[center]元素与A[right-1]的元素互换做为枢轴元素,此时应将i和j分别初始化为left+1和right-2。因为A[left]比枢轴元素小,所以可以将它做j的警戒标志。因为到最后i将停在哪些等于枢轴元素的关键字上,所以将枢轴存储在A[right-1]上,做为警戒。

同样用前几个排序算法使用的随机数文件做测试,10w随机数排序时间如下:

90w随机数字的排序时间:

时间: 2024-10-25 04:24:46

排序问题-快速排序的相关文章

04快速排序(代码填空)

排序在各种场合经常被用到.快速排序是十分常用的高效率的算法. 其思想是:先选一个"标尺",用它把整个队列过一遍筛子,以保证:其左边的元素都不大于它,其右边的元素都不小于它. 这样,排序问题就被分割为两个子区间.再分别对子区间排序就可以了. 下面的代码是一种实现,请分析并填写划线部分缺少的代码. #include <stdio.h> void swap(int a[], int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t;

js数组冒泡排序,快速排序的原理以及实现

实习了好久,大概用了半年ng2了吧,突然被同事问到js排序问题,一时竟有些懵逼,回来就温故一下,希望自己不忘初心,加油加油! 冒泡排序: 随便从数组中拿一位数和后一位比较,如果是想从小到大排序,那么就把小的那一位放到前面,大的放在后面,简单来说就是交换它们的位置,如此反复的交换位置就可以得到排序的效果. function sortA(arr){ for(var i=0;i<arr.length-1;i++){ for(var j=i+1;j<arr.length;j++){ //获取第一个值和

详谈排序算法之交换类排序(两种方法实现快速排序【思路一致】)

1.冒泡排序    起泡排序的思想非常简单.首先,将 n 个元素中的第一个和第二个进行比较,如果两个元素的位置为逆序,则交换两个元素的位置:进而比较第二个和第三个元素关键字,如此类推,直到比较第 n-1 个元素和第 n 个元素为止:上述过程描述了起泡排序的第一趟排序过程,在第一趟排序过程中,我们将关键字最大的元素通过交换操作放到了具有 n 个元素的序列的最一个位置上.然后进行第二趟排序,在第二趟排序过程中对元素序列的前 n-1 个元素进行相同操作,其结果是将关键字次大的元素通过交换放到第 n-1

快速排序深入之荷兰国旗问题

一.序言 在使用partition-exchange排序算法时,如快速排序算法(即使选择了一个好的关键元素pivot values),我们往往面临一个很尴尬的境地--当排序对象中有很多重复的元素,partition-exchange排序算法表现很不尽如人意.当所有元素都相等时,这就特别容易理解了.在每次递归中,左边部分是空的(没有元素比关键元素小),而右边部分只能一个一个递减移动.结果导致耗费了二次方时间来排序相等元素.为了解决这个问题(有时叫做荷兰国旗问题),我们详细介绍下解决这个问题的方法.

一种递归实现的快速排序精讲

简介: 快速排序是个"综合素质"较好的排序,比如javaSE中的Arrays.sort()实现原理,也是用的是快速排序思想.下面就看看一种快速排序的递归实现方式 要点: 1,分治思想,把问题划分成可以与本问题处理方式相同的若干子问题,使用递归来解决. 如排序问题,可以 (1)把原数组A[p,q](原问题)划分成三个部分 :较小部分A[p,m-1]  中间元素x=A[m]  较大部分A[m+1,q] 这样把部分当做一个整体看待是有序的A[p,m-1] <  A[m]  <  

数据持久化、单例、重载【添加对不可访问的成员的操作】、魔术方法、类常量、static关键字对self的补充【静态延迟绑定实现$this的效果】、参数类型约束【参数前加类名】、遍历【iterator接口】、快速排序

1.数据持久化过程[传输(例如表单提交或php交互mysql)和保存过程] 使用的是字符串形式的流数据. 数据流就是为了传输[按照序列的形式进行传输] [http://baike.baidu.com/link?url=0MtUQMhFzc_EwJc09rXZV8KlfOL4jis6XNbRfmGA3rQhDcGwOp8togLVQjXBV34M] 所以将其他类型数据转化为字符串的过程也是序列化的过程 [这个概念和图片.视频的流媒体的区别?] [注意点] 另外mysql中sql语句中的某些关键词为

快速排序的过程

通过前面问题以及引入了“信息熵”的概念,我们可以重新来理解排序的本质: 一组未排序的N个数字,它们一共有N!种重排,其中只有一种排列是满足题意的(譬如从大到小排列). 换句话说,排序问题的可能性一共有N!种.任何基于比伯爵娱乐城较的排序的基本操作单元都是“比较a和b”,这就相当于猜数字游戏里面的一个问句,显然这个问句的答案只能是“是”或“否”,一个只有两种输出的问题最多只能将可能性空间切成两半,根据上面的思路,最佳切法就是切成1/2和1/2(将数组切成一半).也就是说,我们希望在比较了a和b的大

java 交换排序之(冒泡排序、快速排序)

2016年上班第一天,闲来无事,先写篇博文来结束今天.我们大家都知道java的排序算法很多,接下来我就先看看java最常用的几种排序算法的思想源码附上.(本文所有排序只针对值排序,关于对象排序问题待续.....) 1.插入排序(直接插入排序.二分法插入排序.表插入排序.shell排序) 2.选择排序(直接选择排序.堆排序) 3.交换排序(冒泡排序.快速排序) 4.分配排序(基数排序) 5.归并排序(内排序.外排序) 一.java冒泡排序实现(大家最喜欢也是最简单的排序算法,但是性能不是那么ok

[golang] 数据结构-快速排序

快速排序是个非常经典.高效.常用的排序算法.很多语言标准库里的排序算法都有用到它. 原理快排原理其实比较简单,就是将原本很大的数组拆成小数组去解决问题.要拆就得找个拆的位置.如果吧这个位置称为支点,那么快速排序问题就变成了不断的去找到拆分的支点元素位置.通常找支点就是以某个元素为标准,分别从最右侧元素向左找到比指定元素小的位置,再从最左侧开始向右找比指定元素大的位置.如果两个位置不相同就交换两个位置,在继续分表从两头相向寻找.找到合适的位置就是我们需要的支点.支点两边的元素再各自重复上面的操作,