hoare版本
思路 :
先保存left的下标到keyi 右边先走找小于keyi的值 当找到了right指向小于keyi的值 左边再走 找大于key的值,当找到了 left指向大于keyi的值 若找到比keyi的元素并且也找到比keyi小的元素就交换两个元素的位置 继续 right往前走 找到小 left 往回后找大 直到循环结束(left不小于right)
循环结束 left 和right 指向同一个元素, 因为右边先走 所以right指向一定是比key小的元素 交换 keyi 和 right 的位置 让keyi到正确的位置 返回新的keyi
当第一趟排序结束 得到两个条件 1.keyi到达正确的位置 2.keyi左边都是比keyi小的元素 keyi右边都是比keyi大的元素
int PartSort1(int* a, int left, int right)
{
//int keyi = a[left];
int keyi = left;
while (left < right)
{
//1(left<right)防止越界 右边先走 找小于keyi的值
while ((left<right)&& a[right] >= a[keyi])
{
--right;
}
//2(left<right)防止越界 左边再走 找大于keyi的值
while ((left < right) && a[left] <= a[keyi])
{
++left;
}
//3找到大于keyi的值 并且找到小于keyi的值 就交换它们的位置
Swap(&a[left], &a[right]);
//继续 1 2 3 直到不满足循环条件
}
//当循环结束 left == right
//因为left先走 所以相遇位置的值一定小于keyi 交换keyi 和right的位置
Swap(&a[keyi], &a[right]);
return right; // 返回新的keyi
}
挖坑法
思路:
保存left的值 到达keyi中 使left成为第一个坑位 然后右边先走 找小于key的值, 放进坑位 自己成为新的坑位 左边再走 找大于key的值, 放进坑位 自己成为新的坑位
当循环结束 left 和 right 相遇 因为left先走所以 相遇位置一定是坑位 再把keyi放进坑位返回新的keyi
当第一趟排序结束 得到两个条件 1.keyi到达正确的位置 2.keyi左边都是比keyi小的元素 keyi右边都是比keyi大的元素
//挖坑法
int PartSort2(int* a, int left, int right)
{
//保存left的值 使left成为第一个坑位
int keyi = a[left];
int hoult = left;
while (left < right)
{
//右边先走 找小于key的值, 放进坑位 自己成为新的坑位
while ((left < right) && a[right] >= keyi)
{
--right;
}
a[hoult] = a[right];
hoult = right;
//左边再走 找大于key的值, 放进坑位 自己成为新的坑位
while ((left < right) && a[left] <= keyi)
{
++left;
}
a[hoult] = a[left];
hoult = left;
}
//因为left先走所以 相遇位置一定是坑位 再把keyi放进坑位
a[hoult] = keyi;
return hoult;//返回新的keyi
}
前后指针法
思路:
prev 指向 left cur 指向left的下一个 cur 小于 keyi ++prev 再交换cur 和prev的值 cur++
当 cur指向的元素 大于 keyi cur就和 prev 拉开了距离 继续
当 循环结束 prev 再上一轮已经和cur 交换过了 所以 prev 指向的元素一定是小于 keyi的 再交换 keyi 和 prev 的位置 让keyi达到正确的位置
当第一趟排序结束 得到两个条件 1.keyi到达正确的位置 2.keyi左边都是比keyi小的元素 keyi右边都是比keyi大的元素
int PartSort3(int* a, int left, int right)
{
int keyi = left;
int prev = left;
int cur = prev + 1;
//[left,right]
while (cur <= right)
{
//防止越界
if ((cur <= right) && a[cur] < a[keyi]) // cur 小于 keyi ++prev 再交换cur 和prev的值 cur++
{
Swap(&a[cur], &a[++prev]);
}
++cur;//当 cur指向的元素 大于 keyi cur就和 prev 拉开了距离
}
//当 循环结束 prev 再上一轮已经和cur 交换过了 所以 prev 指向的元素一定是小于 keyi的
Swap(&a[prev], &a[keyi]);
return prev;
}
快速排序
思路:
第一趟排序后
1.keyi的左边都是比keyi小的元素 keyi的右边都是比keyi大的元素
2.keyi到达正确的位置
所以 keyi的位置不用动
继续排序 keyi的左边 和keyi的右边 有序 整体有序
keyi的左边 keyi的右边
[begin,keyi-1] keyi [keyi+1,end]
//快速排序
void QuickSort(int* a, int begin, int end)
{
//若 begin > end 说明 数组不存在 那还排个毛线 直接返回
// begin == end 说明 剩下一个元素 不用再排序了 直接返回
if (begin >= end)
return;
int keyi = PartSort2(a,begin,end);
//第一趟排序后
//1.keyi的左边都是比keyi小的元素 keyi的右边都是比keyi大的元素
//2.keyi到达正确的位置
//所以 keyi的位置不用动
//继续排序 keyi的左边 和keyi的右边 有序 整体有序
// keyi的左边 keyi的右边
//[begin,keyi-1] keyi [keyi+1,end]
QuickSort(a, begin, keyi - 1); //排序 keyi的左边 使keyi的左边有序
QuickSort(a, keyi + 1, end); //排序 keyi的右边 使keyi的右边有序
}
三目取中
思路:
在数组中 起始位置left 结束位置 end 中间位置 mid 获取次大的元素 再和 left交换 把 次大的元素换到 起始位置 直接给快速排序加个 极速
最理想的递归情况:
最坏的情况:升序或降序
升序 或降序 快速排序的效率特别菜
再它前面加一个三目取中 会好很多
int GetMid(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[mid] > a[left])
{
if (a[mid] < a[right])
{
return mid;
}
//mid 是最大的
else if (a[left] > a[right])
{
//返回次大的
return left;
}
else //a[left] <a[right]
{
//返回次大的
return right;
}
}
//a[mid] <a[left]
else
{
//left是最大的
if (a[mid] > a[right])
{
return mid;
}
//mid 是最小的
else if (a[left] > a[right])
{
//返回次大的
return left;
}
else //a[left]<a[right]
{
//返回次大的
return right;
}
}
}
PartSort1 hoare版本中
int mid = GetMid(a, left, right); Swap(&a[mid], &a[left]); ..... 其余不变
小区间优化
快速排序最好的情况下, 也避免不了递归的深入 每一层 的递归都 是以2倍的形式快速增长
最好办法加一个判断 小区间优化 当数组剩下的元素个数 小于10 不再递归 需要单独处理,使用InsertSort插入剩下的元素
数组剩下的元素可能在 开始 中间 结束 任意 位置 所以InsertSort的参数 不能是(a ,n) 而是
[a+begin] 是 当前 数组剩下的元素的开始位置 ,[end-begin+1] 是当前数组剩下的元素个数
/快速排序
void QuickSort2(int* a, int begin, int end)
{
//若 begin > end 说明 数组不存在 那还排个毛线 直接返回
// begin == end 说明 剩下一个元素 不用再排序了 直接返回
if (begin >= end)
return;
if ((end - begin + 1) > 10)
{
int keyi = PartSort2(a, begin, end);
//第一趟排序后
//1.keyi的左边都是比keyi小的元素 keyi的右边都是比keyi大的元素
//2.keyi到达正确的位置
//所以 keyi的位置不用动
//继续排序 keyi的左边 和keyi的右边 有序 整体有序
// keyi的左边 keyi的右边
//[begin,keyi-1] keyi [keyi+1,end]
QuickSort2(a, begin, keyi - 1); //排序 keyi的左边 使keyi的左边有序
QuickSort2(a, keyi + 1, end); //排序 keyi的右边 使keyi的右边有序
}
else//小区间优化 当数组剩下的元素个数 小于10 不再递归
{
InsertSort(a + begin, end - begin + 1);
}
}
非递归快速排序
思路:
使用栈 先把R 的下标放进栈再 把 L的下标放进栈
栈不为空 获取栈的元素放进 left 和 right的中 再删除栈里面的元素 使用 hoare版本单趟排序
因为栈的特点:后进先出 所以 先把 keyi 右边的放入栈(放进之前需要判断下序列只有一个元素或是不存在) 再把 keyi左边的放入栈 同样(放进之前需要判断下序列只有一个元素或是不存在) 重复执行操作 直到 栈为空
void QuickSortNonR(int* a, int begin, int end)
{
Stack sl;
StackInit(&sl); //初始化栈
//栈的特点: 后进先出
//所以 先进 右边 再进 左边
StackPush(&sl, end); //先把 右边的下标进栈
StackPush(&sl, begin);//再把 左边的下标进栈
while (!StackEmpty(&sl))
{
int left = StackTop(&sl);//待排的L
StackPop(&sl); //删除L
int right = StackTop(&sl);//待排的R
StackPop(&sl);//删除R
// Hoare版本
int keyi = PartSort1(a, left, right);
//[begin, keyi-1] keyi [keyi+1, end]
//1.keyi的左边都是比keyi小的元素 keyi的右边都是比keyi大的元素
//2.keyi到达正确的位置
//所以 keyi的位置不用动
//继续排序 keyi的左边 和keyi的右边 有序 整体有序
// keyi的左边 keyi的右边
//[begin,keyi-1] keyi [keyi+1,end]
//栈的特点: 后进先出
//keyi的 右边 先进栈
if (keyi + 1 < right)
{
StackPush(&sl, right);
StackPush(&sl, keyi+1);
}
//keyi的 左边 再进栈
if (left < (keyi - 1))
{
StackPush(&sl, keyi-1);
StackPush(&sl, begin);
}
}
//销毁栈
StackDestory(&sl);
}