目录
快速排序
思想:
找到一个元素作关键值key一般从最左边或最右边找,让它在排在正确的位置及key的左边小于它、key的右边大于它。对于key它已经有序了,那在让它的左区间和右区间在去找一个key如此循环往复。当key的左区间和右区间小于1或不存在事就不需要在找新的key了。
实现:
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
//返回基准值所在位置
int div = PartSort3(a, left, right);
//左区间
QuickSort(a, left, div - 1);
//右区间
QuickSort(a, div + 1, right);
}
三种实现方法
那如何让key的左边小于它,key的右边大于他呢?一般有三种方案
hoare版本:
两个指针left和right分别指向数组的最两边,left从左向右找比key的数,right从右向左找比key小数,如果两边都找的了就交换。当left和right相遇时交换key和相遇元素的下标。
注:左边作key让右边先走,右边左key让左先走。
证明:
假设左边做key且右边找小左边找大
- 情况1: 右边没有找到比key小的,与左边相遇
- 情况2: 右边找到了比key小的,左边没有找到大的与右边相遇
无论那一种情况左边与右边相遇的位置都是小于key的
int PartSort1(int* a, int left, int right)
{
int key = left;
while (right > left)
{
//右边开始找小于key的值
while (right > left && a[key] <= a[right])
{
right--;
}
//左边开始找大于key的值
while (right > left && a[key] >= a[left])
{
left++;
}
//找到了就交换
Swap(&a[right],&a[left]);
}
Swap(&a[left], &a[key]);
//返回基准值所在位置
return left;
}
挖坑法:
假设排升序选左边的作key,一开始将key的位置作为坑,从右边找比key小的值如果找到了就把该值填入坑中,并让当前位置成为新的坑,在从左边找比key大的值如果找到了就把该值填入坑中,并让当前位置成为新的坑。如此循环往复。直到左边与右边相遇,此时坑一定在相遇处之后在把key填入坑中。
int PartSort2(int* a, int left, int right)
{
//左边作key和坑
int key = a[left];
int hole = left;
while (right > left)
{
//右边找小
while (right > left && a[right] >= key)
{
right--;
}
//将值填入坑中,自己在成为新的坑
a[hole] = a[right];
hole = right;
//左边找大
while (right > left && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
前后指针法:
定义两个指针prev和cur,[key,prev]的区域是小于key的,[prev+1,cur]的区域是大于key的。prev从key开始,cur从key+1开始。
情况1: cur小于key,让prev++,在交换prev与cur的内容,在让cur++
情况2: cur大于key,cur++
情况3: cur > right,终止循环,交换key与prev的内容
int PartSort3(int* a, int left, int right)
{
int key = left;
int prev = left;
int cur = left + 1;
while (right >= cur)
{
//让prev++,在交换prev与cur的内容但如果prev与cur相同就没必要交换
if ( a[cur] < a[key] && prev++ != cur)
{
Swap(&a[prev],&a[cur]);
}
cur++;
}
Swap(&a[prev], &a[key]);
return prev;
}
两种优化:
三数取中:
当基准值是最大或最小时排序的效率就很低所以基准值最好是一个不大不小的值,但是重新选一个基准值会影响到整个排序算法,这时无需更换基准值只需要把基准值的类容替换成一个不大不小的值即可,选出左边、中间、右边中第二大的并交换于基准值的数据。
以前hoare法为例
//三数取中
int GetMidNumi(int* a, int left, int right)
{
int midi = (right / 2) + (left / 2);
//让right大于left
if (a[right] < a[left])
{
Swap(&a[right],&a[left]);
}
//假设midi是最大的
if (a[right] < a[midi])
{
return right;
}
return a[midi] > a[left] ? midi : left;
}
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
//三数取中
int mid = GetMidNumi(a, left, right);
if (mid != left)
{
Swap(&a[mid], &a[left]);
}
int key = left;
while (right > left)
{
//右边开始找小于key的值
while (right > left && a[key] <= a[right])
{
right--;
}
//左边开始找大于key的值
while (right > left && a[key] >= a[left])
{
left++;
}
//找到了就交换
Swap(&a[right],&a[left]);
}
Swap(&a[left], &a[key]);
//返回基准值所在位置
return left;
}
小区间优化:
在快速排序递归的最后几层用插入排序,节省递归展开和增加效率
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
//元素个数大于10个就用递归
if ((right - left + 1) > 10)
{
//返回基准值所在位置
int div = PartSort3(a, left, right);
//左区间
QuickSort(a, left, div - 1);
//右区间
QuickSort(a, div + 1, right);
}
//小区间优化
//在快速排序递归的最后几层用插入排序,节省递归展开和增加效率
else
{
InsertSort(a + left, right - left + 1);
}
}
快速排序的非递归
思想:
用一个栈来模拟快排的递归过程,将区间的左右下标入栈。取出左右区间的下标选出一个基准值,如果基准值的右区间的元素个数大于1且右区间不为空将右区间的左右下标入栈,如果基准值的左区间的元素个数大于1且左区间不为空将左区间的左右下标入栈,如此循环往复直到栈为空。
实现:
void QuickSortNonR(int* a, int left, int right)
{
assert(a);
Stack st;
StackInit(&st);
//一次入两个值表示一个区间
StackPush(&st,right);
StackPush(&st, left);
while (!StackEmpty(&st))
{
//出区间
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int mid = PartSort3(a, begin, end);
//如果左右区间元素个数大于1就入栈
//先入右区间
if (end > mid+1)
{
StackPush(&st, end);
StackPush(&st, mid+1);
}
if (mid-1 > begin)
{
StackPush(&st, mid-1);
StackPush(&st, begin);
}
}
}