前言
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序
每一次排序,随便选出一个中间值,将待排序列中所有小于它的值放到左边,大于它的值放到右边,以中间值的下标为分界线,形成左右两个区间,这样进行一次排序后就将中间值放到相应的位置上了,然后左右区间进行重复的动作,直到序列有序。
主框架:
// 假设按照升序对array数组中[left, right]区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
//区间中只有一个元素或没有元素存在,直接返回
if(left>=right)
return;
//递归
// 按照基准值对array数组的 [left, right]区间中的元素进行划分
int keyi= partion(array, left, right);
// 划分成功后以keyi为边界形成了左右两部分 [left, keyi-1] 和 [keyi+1, right]
QuickSort(array, left, keyi-1);
QuickSort(array, keyi+1, right);
}
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,写递归框架时可想想二叉 树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
将区间按照基准值划分为左右两半部分的常见方式有:
1.Hoare法
单趟图解:
代码:
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
//1.Hoare
int Hoare(int arry[], int begin, int end)
{
//选最左侧作key
int keyi = begin;
int left = begin;
int right = end;
while (left<right)
{
//右边找小,先走
while (left < right && arry[right] >= arry[keyi])
{
right--;
}
//左边找大
while (left < right && arry[left] <= arry[keyi])
{
left++;
}
//交换:将大的放到右边,小的放到左边
swap(&arry[left], &arry[right]);
}
//left与right相遇
//交换:将key值放到相遇处(应该存放的位置)
swap(&arry[keyi], &arry[left]);
return left;
}
//快速排序
void QuickSort(int arry[], int left, int right)
{
if (left >= right)
return;
int keyi = Hoare(arry, left, right);
QuickSort(arry, left, keyi - 1);
QuickSort(arry, keyi + 1, right);
}
注意:
1.在左右找值时,首先要判断是否越界,其次,一定要加等于判断:
为什么会越界?
因为,你是在循环中一直找符合条件的值,如果一直找不到,就会一直找,这样就会发生越界。
举例:
为什么要加 = 号?
因为,不加 = 号,就会发生死循环。
举例:
2.选左边作key,一定要右边先走;右边作key,左边先走。
以左边作key为例,因为,最后left与right相遇,我们需要将key与相遇位置进行交换。那么要保证交换后,keyi的左边都是小于key的值,右边都是大于key的值。就必须右边先走。
毕竟,right与left相遇,只有两种情况:
left遇到right:
right遇到left:
总结:right找小,left找大,只有找到或者相遇它们才会停下,然后发生交换,这是一个回合制游戏,right先走,那它走的次数一定不小于left。 所以,不论left与right在何处相遇,所指向的值一定不大于key。
由于,hoare 法有太多需要注意的点,所以又衍生出了以下几种方法:
2.挖坑法
单趟图解:
代码:
//2.挖坑
int PartSort2(int* arry, int left, int right)
{
int key = arry[left];
int piti = left;//坑位下标
while (left < right)
{
//右边找小,先走
while (left < right && arry[right] >= key)
{
right--;
}
arry[piti] = arry[right];
piti = right;
//左边找大
while (left < right && arry[left] <= key)
{
left++;
}
arry[piti] = arry[left];
piti = left;
}
arry[piti] = key;
return piti;
}
3.指针法
单趟图解:
代码:
//3.前后指针
int PartSort3(int* a, int left, int right)
{
int prev = left;
int cur = left + 1;
int keyi = left;
while (cur <= right )
{
//找小,处理
//根据图解:我们只会在cur找到小值时发生交换
//其次,自己和自己交换没有太大的必要
//++prev:prev在后,所以先++,再交换。
//++prev != cur:++prev后,两者不重叠,再交换。
if (a[cur] < a[keyi] && ++prev != cur)
swap(&a[prev], &a[cur]);
//cur会一直往后走
cur++;
}
swap(&a[keyi], &a[prev]);
keyi = prev;
return keyi;
}
三种方法都大同小异,你喜欢哪种就用哪种。另外快排还有些优化性能的方法,下次再说啦!拜拜!😊😊