文章目录
- 前言
- 快速排序
- 1.快排的前后指针法
- 1.1快排的前后指针法的代码实现
- 1.2快排的前后指针法的注意事项
- 2.快排的挖坑法
- 2.1快排的挖坑法的代码实现
- 2.2快排的挖坑法的注意事项
- 3.快排的hoare法
- 3.1快排的hoare法的代码实现
- 3.2快排的hoare法的注意事项
- 4快排的优化
- 4.1快排的三数取中
- 4.2快排的三数取中的实现方法
- 4.3快排的小区间优化
- 4.3快排的小区间优化的代码实现
前言
快速排序是一种基于分治策略的排序算法,通过选取一个基准元素将数组分为两部分,再递归地对这两部分进行排序,平均时间复杂度为O(n log n)。它在原地进行排序,且在实际应用中非常高效。
快速排序
快速排序有三种方式实现:hoare(托尼·霍尔)版本、前后指针、挖坑法
快速排序(QuickSort)的逻辑是基于分治策略,算法的基本步骤包括选择一个基准值(key),将数组分成两个子数组:一个小于基准值的子数组,另一个大于基准值的子数组,然后将key和最后的位置相互交换,最后递归地对这两个子数组进行快速排序。
1.快排的前后指针法
快速排序的前后指针方法个人认为是三种方法中较为简单,易于理解的一种了。
它通过两个指针(通常称为“前指针”和“后指针”)来有效地进行分区操作。
我们可以先看动图:
算法逻辑:
1.先定义一个基准值(key)和两个指针cur(快)和prev(慢),让两个指针指向key的下一个下标.
2.让cur先走,如果cur++小于key的话,就让prev也++.
3.直到cur大于key,prev就停止下来,不在发生移动,直到cur再次移动到比key小的,然后prev再次++,然后和cur进行交换,
4.直到cur走出数组,直接让prev与key进行交换。
5.最后进行递归,就可以了
1.1快排的前后指针法的代码实现
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
// 前后指针法
void quicksort(int* a, int left,int right)
{
if (left >= right)
return;
int key = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[key])
{
prev++;
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[key], &a[prev]);
// [left,key-1] [key+1,right]
quicksort(a, left, key - 1);
quicksort(a, key + 1, right);
}
1.2快排的前后指针法的注意事项
递归需要有跳出递归的条件,切记cur是小于等于right,不能超出数组的范围,否则就会导致越界风险。
2.快排的挖坑法
快速排序的挖坑法(也称为“填坑法”)是一种直观且有效的排序算法实现方式。
看动图:
算法逻辑:
1.首先将最左边的数定义为坑位,并把这个坑位的值记录在key中,同时定义两个变量,begin和end,一个从最左边开始每一个从右边开始。
2.先从右边开始向左边开始找小于key的数据,然后和坑位进行交换,交换完成之后,把坑位转移到end当前所在的位置。
3.然后再让begin开始向右移动,找到比key大的值然后和坑位进行交换,再次将坑位移动到当前begin所在的位置。
4.直到最后begin和end相遇了,且坑位的值已经进行过交换,将key值放入坑位。
5.再进行递归就可以实现算法了
2.1快排的挖坑法的代码实现
// 挖坑法
void quicksort(int* a, int left, int right)
{
if (left >= right)
return;
int key = a[left];
int keyi = left; // 坑位
int begin = left;
int end = right;
while (begin < end)
{
while(begin < end && a[end] >= key)
{
end--;
}
if (begin < end && a[end] < key)
{
a[keyi] = a[end];
keyi = end;
}
while(begin < end && a[begin] <= key)
{
begin++;
}
if (begin < end && a[begin] > key)
{
a[keyi] = a[begin];
keyi = begin;
}
}
a[keyi] = key;
// [left,keyi-1] [keyi+1,right]
quicksort(a, left, keyi - 1);
quicksort(a, keyi + 1 , right);
}
2.2快排的挖坑法的注意事项
开始比较的时候一定要从右边开始进行。
开始挖坑的时候切记,需要将key值进行保存,否则后期找不到最开始的key值。
再内部进行比较的时候,也一定要将begin和end进行比较,否则就会有报错。
数据进行交换的时候,坑位移动要发生改变,否则排序将失败。
3.快排的hoare法
快速排序的Hoare法是一种基于二叉树思想的交换排序方法,由托尼·霍尔在1962年提出。
动图:
算法逻辑:
1任取待排序元素序列中的某元素作为基准值(key),通常选择第一个或最后一个元素。
2.初始化两个指针,begin指向序列的开始,end指向序列的末尾。
3.begin从左边开始找比key大的值,找到就停下来。
4.end从右边开始找比key小的值,找到就和begin进行交换。
5.直到begin和end相遇,将这个位置与key进行交换,然后再分治递归即可。
3.1快排的hoare法的代码实现
// hoare法
void quicksort(int* a, int left, int right)
{
if (left >= right)
return;
int key = left;
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && a[end] >= a[key])
{
end--;
}
while (begin < end && a[begin] <= a[key])
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[key]);
key = begin;
// [left,keyi-1] [keyi+1,right]
quicksort(a, left, key - 1);
quicksort(a, key + 1, right);
}
3.2快排的hoare法的注意事项
开始比较的时候一定要从右边开始进行。
end找到比key小的值需要停下来,直到begin遇到比key大的值,才进行交换。
4快排的优化
快拍有两个优化,一个是三数取中,另一个是小区间优化
4.1快排的三数取中
其实key的取值是有方法的,取三个值中间的那个,取最左边的值、最右边的值和中间的那个值。避免最坏情况的O(N^2),避免有序和接近有序。
4.2快排的三数取中的实现方法
int GetMidi(int* a, int left, int right)
{
int midi = (left + right) / 2;
if (a[left] > a[right])
{
if (a[midi] < a[right])
{
return right;
}
else if(a[midi] > a[left])
{
return left;
}
else
{
return midi;
}
}
else // r < l
{
if (a[midi] < a[left])
{
return left;
}
else if (a[midi] < a[left])
{
return midi;
}
else
{
return right;
}
}
}
4.3快排的小区间优化
再数据个数较小的时候,为了避免继续递归分割,转而采用其他更高效的排序算法,如插入排序,以减少递归调用的次数,提高排序效率。
4.3快排的小区间优化的代码实现
// 快速排序hoare版本
void PartSort(int* a, int left, int right)
{
if (left >= right)
return;
if ((left + right) < 10)
{
InsertSort(a+left, (right - left + 1));
}
else
{
int midi = GetMidi(a, left, right);
Swap(&a[left], &a[midi]);
int key = left;
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && a[end] >= a[key])
{
end--;
}
while (begin < end && a[begin] <= a[key])
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[key], &a[begin]);
key = begin;
PartSort(a, left, key - 1);
PartSort(a, key + 1, right);
}
}
// 快速排序挖坑法
void PartSort2(int* a, int left, int right)
{
if (left >= right)
return;
if ((left + right) < 10)
{
InsertSort(a + left, (right - left + 1));
}
else
{
int midi = GetMidi(a, left, right);
Swap(&a[left], &a[midi]);
int key = left; // 坑位
int keyi = a[left];
int begin = left, end = right;
while (begin < end)
{
while (a[end] >= keyi && begin < end)
{
end--;
}
if (a[end] < keyi)
{
a[key] = a[end];
key = end;
}
while (a[begin] <= keyi && begin < end)
{
begin++;
}
if (a[begin] > keyi)
{
a[key] = a[begin];
key = begin;
}
}
a[key] = keyi;
// [left,key-1] key [key+1,right]
PartSort2(a, left, key - 1);
PartSort2(a, key + 1, right);
}
}
// 快速排序前后指针法
void PartSort3(int* a, int left, int right)
{
if (left >= right)
return;
if ((left + right) < 10)
{
InsertSort(a + left, (right - left + 1));
}
else
{
int midi = GetMidi(a, left, right);
Swap(&a[left], &a[midi]);
int key = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[key])
{
prev++;
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
key = prev;
// [left,key-1] key [key+1,right]
PartSort3(a, left, key - 1);
PartSort3(a, key + 1, right);
}
}