目录
1、快速排序的概念:
2、快速排序的版本:
2.1、递归版本:
对于如何按照基准值将待排序列分为两子序列,即单趟排序的常见的方式有:
QuickSort(int* a, int begin,int end) 是外层递归函数,不同的递归方法QuickSort函数都是一样的,PartSort(int* a,int left,int right) 是单趟函数,也正是写法不同的地
方,所以我们这里主要探讨的是 PartSort 单趟排序函数、
递归版本的快速排序的整体思路为:
先选出基准值key,一般是给定的随机数组的第一个元素或者是最后一个元素,先使用下面三种方法进行单趟排序,若排升序,单趟排序后,则让基准值key左边部
分的所有数据均小于等于key,基准值key右边部分的所有数据均大于等于key,若排降序,单趟排序后,则让基准值key左边部分的所有数据均大于等于key,基准
值key右边部分的所有数据均小于等于key,这就完成了单趟排序,但是要注意,升序情况下, 基准值key左边部分的所有数据均小于等于key,但不一定是升序的,
基准值key右边部分的所有数据均大于等于key,但也不一定是升序的,降序情况下, 基准值key左边部分的所有数据均大于等于key,但不一定是降序的,基准值
key右边部分的所有数据均小于等于key,但也不一定是降序的,然后再使用分治的思想去递归基准值key的左右子序列即可、
2.1.1、hoare版本:
所谓单趟排序,其过程为 : 选出一个基准值Key,一般选取给定的随机数组中的第一个元素或者是最后一个元素,若选取数组中第一个元素作为基准值Key的话,则
定义变量Keyi,再把left赋值给Keyi,若选取数组中最后一个元素作为基准值Key的话,则定义变量Keyi,再把right赋值给Keyi、若排升序,则单趟排序结束后要
求,基准值左边的序列中的所有数据都小于等于基准值,基准值右边的序列中的所有数据都大于等于基准值、若排降序,则单趟排序结束后要求,基准值左边的序
列中的所有数据都大于等于基准值,基准值右边的序列中的所有数据都小于等于基准值、
思路:
以升序为例,若选取给定的随机数组中的第一个元素作为基准值Key的话,则定义变量Keyi,并把left赋值给Keyi,则right必须先走,而right在找比基准值Key小的数
据,直到找到比基准值Key小的数据后,right停止,此时,left 再开始走,而left是在找比基准值Key大的值,直到找到后停止,然后交换left和right所在位置上的数
据,然后right再找小,left再找大,找到之后再进行交换,当left和right相遇时,即相等时,再把该相遇位置上的值与Keyi位置上的数据Key进行交换,从而达到目
的,在此,即使数组元素个数为偶数个,则left和right也一定会相遇的,和数组元素个数奇数偶数没有关系,right找到比Key小的数就不动了,left找到比Key大的数
就不动了,每次移动过程中,一定是其中一个去与另外一个相遇,即,两者同时只有一个在移动,另外一个不动,所以一定会相遇,其次,最后一步需要把相遇位
置上的数据和Keyi位置上的数据Key进行交换,若相遇位置上的数小于Keyi位置上的数据Key是可以的,此时相遇位置上的数据不会等于Key,是因为right找小,left
找大,两者直接把等于Key的值跳过了,但若大于的话,就是不对的,怎么保证相遇位置上的数据一定是小于Keyi位置上的数据Key呢?,left和right两者不会在一
个数据比Key大的位置上相遇,是因为,right先走已经保证了,即每一次交换完之后都是right先走,left再走,此时的相遇只有两种情况,一,right遇到left,二,left
遇到right,若是第二种情况的话,right不动,left与right相遇,此时相遇位置上的数据一定比Key小,若是第一种情况的话,left不动,right与left相遇,因为上一次交
换后,left所在位置上的值一定比Key小,所以,right与left相遇的位置一定也比key要小,降序的话则right找大,left找小、
若选取给定的随机数组中的最后一个元素作为基准值Key的话,则定义变量Keyi,并把right赋值给Keyi,则left必须先走,其他的不需要变动,这样就保证了相遇位
置上的元素一定比Key大,分析同上、
以升序为例,若经过一次单趟排序,使得key左边的所有数据均小于等于key,key右边的所有数据均大于等于key,再将key左序列和右序列进行上述单趟排序,如
此反复进行上述操作,直到子区间中只有一个数据或者不存在数据时,即子区间中只有一个数据或者子区间不存在时,为子问题的最小规模,则递归结束,因为子
区间中只有一个数据或者不存在数据时,可以看成升序,也可看成降序、
总结:
升序并且第一个元素作为基准值,则right先走,left后走,并且,left找大,right找小,升序并且最后一个元素作为基准值,则left先走,right后走,并且,left找大,
right找小,降序并且第一个元素作为基准值,则right先走,left后走,并且,left找小,right找大,降序并且最后一个元素作为基准值,则left先走,right后走,并
且,left找小,right找大、
代码实现:
//单趟排序->hoare版本、
int PartSortHoare(int*a, int left, int right)//一级指针传参,一级指针接收、
{
assert(a);
//方法一:
//选取数组首元素作为基准值、
//三数取中法,取出数组首元素,尾元素,中间位置元素,选择这三者中,不是最大,也不是最小的那个值的下标、
int midi = GetMidIndex(a, left, right);
//若选择数组首元素作为基准值key,则应为:Swap(&a[midi], &a[left]); 这样就让数组首元素,尾元素,中间位置元素,三者中,不是最大,也不是最小的那个值作为了基准值key、
Swap(&a[midi], &a[left]);
//下面的代码不需要进行改变,在单趟排序中,不管使用何种方法,仍选取数组首元素或者尾元素作为基准值key、
int keyi = left; //用keyi记录key的下标、
while (left < right)
{
//升序、
//right先走、
while (left < right && a[right] >= a[keyi])//right找小,找不到则right--,降序则right找大,找不到则right--,变为<=、
{
right--;
}
//left再走、
while (left < right && a[left] <= a[keyi])//left找大,找不到则left++,降序则left找小,找不到则left++,变为>=、
{
left++;
}
//上面的>=和<=中的=,一是表明与基准值相等的数据在基准值的左边和右边都是就可以的,二是避免死循环,比如: 5 5 2 3 5,若两者不加等于的话,就会造成死循环、
//上面两个while中的 left < right 主要是为了防止越界访问的,若选择数组首元素作为基准值排升序和排降序时都可能会越界访问,比如1 2 3 4 5、
//交换、
Swap(&a[left], &a[right]);//找到后就交换a[left]和a[right],让小的去左边,大数去右边,降序则让小的去右边,大的去左边、
}
//交换相遇位置和keyi位置上的数据、
Swap(&a[keyi], &a[left]);
//Swap(&a[keyi], &a[right]);
return right;
//return left;
方法二:
选取数组尾元素作为基准值、
三数取中法,取出数组首元素,尾元素,中间位置元素,选择这三者中,不是最大,也不是最小的那个值的下标、
//int midi = GetMidIndex(a, left, right);
若选择数组尾元素作为基准值key,则应为:Swap(&a[midi], &a[right]); 这样就让数组首元素,尾元素,中间位置元素,三者中,不是最大,也不是最小的那个值作为了基准值key、
//Swap(&a[midi], &a[right]);
下面的代码不需要进行改变,在单趟排序中,不管使用何种方法,仍选取数组首元素或者尾元素作为基准值key、
//int keyi = right; //用keyi记录key的下标、
//while (left < right)
//{
// //升序、
// //left先走、
// while (left < right && a[left] <= a[keyi])//left找大,找不到则left++,降序则left找小,找不到则left++,变为>=、
// {
// left++;
// }
// //right再走、
// while (left < right && a[right] >= a[keyi])//right找小, 找不到则right--, 降序则right找大, 找不到则right--, 变为<= 、
// {
// right--;
// }
// //上面的>=和<=中的=,一是表明与基准值相等的数据在基准值的左边和右边都是就可以的,二是避免死循环,比如: 5 5 2 3 5,若两者不加等于的话,就会造成死循环、
// //上面两个while中的 left < right 主要是为了防止越界访问的,若选择数组尾元素作为基准值排升序和排降序时都可能会越界访问,比如1 2 3 4 5、
// //交换、
// Swap(&a[left], &a[right]);//找到后就交换a[left]和a[right],让小的去左边,大数去右边,降序则让小的去右边,大的去左边、
//}
交换相遇位置和keyi位置上的数据、
//Swap(&a[keyi], &a[left]);
Swap(&a[keyi], &a[right]);
return right;
//return left;
}
2.1.2、挖坑法:
思路:
和hoare方法类似,但有一些不同,若选择数组首元素为基准值key,则数组中第一个位置即为坑1,此时需要把数组第一个位置上的数据,即数组首元素保存到key
中,那么数组中第一个位置上的数据就已经被保存下来了,也就代表着数组第一个位置上的数据可以被覆盖了,即称之为坑1,若进行升序排列的话,则left找大,
right找小,由于坑1在左边,而升序排序需要把小于等于key的数据放在key的左边,并且right找的是比key小的值,跳过大于等于key的值,所以很自然的就先让
right先走,找到比key小的数据放在坑1里,然后该比key小的数据所在的位置变成新的坑2,left再走,找到比key大的值放在坑2中,然后该比key大的数据所在的位
置变成新的坑3,重复上述操作,直到left与right相遇,则停止,并且两者相遇的时候,所在的位置一定是一个坑位,然后再把key中的值放到该相遇位置的这个坑位
上,此时就满足了基准值key的左边所有的数据均小于等于key,基准值key的右边所有的数据均大于等于key,这就是所谓的挖坑法,与hoare方法有共同点,也有
不同点,挖坑法相比于hoare方法的优势是,挖坑法更好理解,不需要理解在升序并且选择数组首元素作为基准值的的条件下,left和right相遇的位置上的数据一定
比key小,不需要理解在升序并且选择数组尾元素作为基准值的的条件下,left和right相遇的位置上的数据一定比key大,不需要理解在降序并且选择数组首元素作为
基准值的的条件下,left和right相遇的位置上的数据一定比key大,不需要理解在降序并且选择数组尾元素作为基准值的的条件下,left和right相遇的位置上的数据一
定比key小,其次就是,不需要理解为什么选取数组首元素为key,则right必须先走,left后走,为什么选取数组尾元素为key,则left先走,right后走的原因,因为这
种情况下关于谁先走的问题就比较自然,具体解释见上述,