快速排序的理解

今天学习了一下快速排序,结合了之前的排序做了一点想法和总结也代码实现了一下。首先快速排序的思路其实和昨天的归并排序的总体思路是一样的都是分治算法。快速排序顾名思义肯定是各方面性能最好的这个算法特别的快,但是也有最坏的情形有时候该算法的时间复杂度却是O(N^2)。取决于性能的关键就是枢纽元的选择,要尽量的满足一般的元素在前一个数组里面,另一半的元素在后面一个数组里面,然后再递归的调用这样的一个函数,其实就是不停的找到合适的枢纽元然后不停的分成小数组。说到这里都知道枢纽元非常的重要就不可避免的说一下啊枢纽元该怎么样的选取才能够让该算法发挥它的优势呢。第一种想法肯定就是直接将第一个元素作为枢纽元,这样好像让代码简单了很多并且对于形似这样的数组似乎是可行的{6,1,2,7,9,3,4,5,10,8}因为6好像可以吧比自身大的和比自身小的分成两个均等的数组,这里其实有点点像二叉平衡树!但是仔细思考一下其实这是非常不好的办法,如果第一元素就是1后面的元素都比1大呢这种枢纽的选择似乎跟没选择是一样的,与其说这是一种不好的办法不如说是完全错误的!
现在来说另一种选取枢纽元的做法,就是利用随机数,如果没有了解过的可以去搜寻相关的资料,随机数的生成器,这样的话可以导致每次选择的枢纽元都是随机的,就算有时候运气不好导致了劣质的分割,但是应该是不会出现接连不断的产生劣质分割的,比上面一种方法比较好,随机数的生成也是非常的昂贵的,反而让原本迅速的算法拖慢了速度得不偿失。
最后定下来的三数中值分割法,其实我在学习这个方法的时候直接梦回昨天的归并排序了,也是找到一个center值然后将最左边的值和中间值还有最右边的值进行一个排序,并不是像归并排序一样直接分成两个数组,然后再进行组合虽然思路是一样,但是一定要记得我们选过枢纽元,在上一步操作的时候,我们放在最右边的值也就是right其实就是我们的枢纽元,然后将借用《啊哈,算法》中的例子right-1看成是j而left看作是i,就像两个哨兵一样i如果遇到了比枢纽元大的值就停下而j遇到比枢纽元小的值停下,没有这样的值就一直++i和++j,直到他们碰到碰到了话直接将枢纽元赋值到这个位置。这样的话第二次得分割肯定是比较恰当得。再递归进行这样的操作就可以了。

void QuickSort(int arr[], int n)
{
	Qsort(arr, 0, n - 1);
}

这段代码是一个启动程序,就是开始快速排序,传入的是数组还有角标!

int median(int arr[], int left, int right)
{
	int 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];
}```
//这里应该是解释过的,就是分割的方法,选取枢纽元的最佳办法,并且完成了这三个元素得排序,返回的值是枢纽元的值
```c
void Qsort(int arr[], int left, int right)
{
	int i, j;
	int pivot;
	if (left + 3 <= right)
	{
		pivot = median(arr, left, right);
		i = left;
		j = right - 1;
		for (;;)
		{
			while (arr[++i] < pivot) {}
			while (arr[--j] > pivot) {}
			if (i < j)
				swap(&arr[i], &arr[j]);
			else
				break;
		}
		swap(&arr[i], &arr[right - 1]);
		Qsort(arr, left, i - 1);
		Qsort(arr, i + 1, right);
	}
	else
		insertionSort(arr,SIZE);
}

这串代码是快速排序的核心首先if (left + 3 <= right)这一步判断要领悟到,虽然名字叫快速排序但是也不是什么时候都是最优解啊,不然直接得shell排序插入排序归并排序还有什么意义。所以这里加3,一个原因就是小数组,左边界和右边界比较临近的时候,数组元素比较小的时候,快排得效率是比不上插入排序的,还有一个原因其实大家也应该知道,就是里面嵌套的循环怎么退出呢,如果没有这个if的话还要设置一个退出的条件,这样既没有达到快排的效率还要多写代码,所以这里直接对小数组使用插入排序。继续往下看,就是分割,分割完了就是死循环,这里while里面是空实现,但是要特别注意看似这里循环条件里面的while(arr[++i]){}和while(arr[i]){i++}其实这两者语法上面是完全相同的,但是再快速排序算法里面后面的写法将直接导致算法崩溃的,因为直接是死循环!其次就是这里的>和<也是有讲究的pivot是枢纽元,我们之前一直在避开的问题就是如果数组里面的元素和枢纽元相等怎么办?其实这里是比较的复杂的,分为两种情况图中的代码采取的是停止的办法,就是遇到值相等的时候i和j就停止下来,然后惊进行很多次交换,有可能你会说这不是纯纯浪费时间吗?对排序没有任何影响的操作,但是其实停止比不停止好多了,如果你不停止的话首当其冲的问题就是i和j越界的问题,并且如果极端的数组全部元素都是相等的(虽然不可能对这样的数组排序),但是导致算法的事件复杂度变成O(N^2)但是什么操作都没有做!然后就是交换函数,采用指针交换的办法,应该大家可以理解,接下来就是对快排后的数组继续排序,递归很重要虽然一步一步分析清楚很难,但是尽量理解。
这就是今天学习快排的一些理解和想法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值