当前位置: 首页 > news >正文

排序----快速排序(快排)(递归版)

首先讲一下单趟的思路:

        在这一块数据中,记录第一个元素为key,然后设置L和R两个指针,L找比key处的元素大的,R找比key处元素小的,找到了就交换这两个位置的元素。当两个指针相遇时,若相遇点的元素比key处的值小,就把相遇点的元素与key处的元素进行交换;若相遇点的数据比key处的数据大,就把相遇点之前的元素与key处的元素相比较。

        那么这样走完一趟的过程中,key处的元素就位于正确的位置,同时,key左侧的元素都比他小,右侧的元素的都比他大。这一趟也实现了自然分块,那么再递归进行每一块的排序,同上一段所述。直到最后每一块只有一个元素,就代表排好了。

注意:

1.keyi记录的需要是下标,然后通过下标进行最后一次交换。万万不可把下标对应的值赋值给keyi,这样的话就不是交换了。

2.注意指针前进/后退的循环的进行条件,一定要保证left<right,这样的话进入循环++就会left=right,否则left和right可能正好错过了,变成了left>right。

3.注意递归的终止条件。

        举例:当left=0、right=1、keyi=1时:

4.为什么最后与keyi交换的位置的元素一定比keyi处的元素小?

//以下为简单版快排(即还存在着一些坑)

        时间复杂度:大约是O(N*logN)。递归的过程可以看成是满二叉树,递归的次数就是树的高度就是logN,然后每一层begin和end分别++和--,就可以看成是N次,即可得到这个时间复杂度。

***继续找坑:

        当我们要排序的序列是一个有序序列时,我们选择第一个元素为keyi,但是end找不到比keyi的元素更小的元素,那就一直往前走,走到头和自己交换一下;begin同理。然后不断建立栈帧,每次建立都是第一个元素为keyi,然后begin和end找不到符合条件的元素,这样的话会导致递归层次太深(需要进行N次,时间复杂度为O(N^2)) ,就会造成栈溢出。

        因此,不能固定选择第一个元素为keyi。

        我们采用三数取中的方法:

        即left、midi、right中选择对应元素不是最大也不是最小的位置来作为keyi。

//三数取中函数

//保证取得keyi处的值不是数组的最小值

//排序主函数

        下一个效率问题来了,当区间内只有五个数据时,加入我们还继续选择快排来不断递归,那么我们需要进行好多次递归和判断。(快排相当于满二叉树,满二叉树最后一层占整棵树的50%,倒数第二层占25%,倒数第三层占12.5%...,假如我们把这几层去掉,那么时间效率大大提高)

        那我们这个小区间采用什么排序方法呢?希尔排序和堆排序有点大材小用,就是说一共最多十个数据,没有必要分组或者建堆;冒泡排序和选择排序太慢。所以我们选择插入排序。

        注意插入排序两个参数的写法。

int GetMidi(int* a, int left, int right)
{int midi = (left + right) / 2;// left midi rightif (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] < a[right]){return right;}else{return left;}}else // a[left] > a[midi]{if (a[midi] > a[right]){return midi;}else if (a[left] < a[right]){return left;}else{return right;}}
}void QuickSort(int* a, int left, int right)
{if (left >= right)return;// 小区间优化,不再递归分割排序,减少递归的次数if ((right - left + 1) < 10){InsertSort(a+left, right - left + 1);}else{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int begin = left, end = right;while (begin < end){// 右边找小while (begin < end && a[end] >= a[keyi]){--end;}// 左边找大while (begin < end && a[begin] <= a[keyi]){++begin;}Swap(&a[begin], &a[end]);}Swap(&a[keyi], &a[begin]);keyi = begin;// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
}

//在进行数据测试的时候发现,当重复数据过多的时候,快排比希尔排序和堆排序性能高很多。

ps:面试时不要写三数取中和小区间优化,这一块写起来很浪费时间,可以稍微和面试官说一下。

        更好理解的但是没有任何效率提升的版本:挖坑法。

(此处仅说明思路)

        我们选择第一个位置为key,把第一个位置的值给key,然后begin++和end--(此处若选择左边为key,那就end先走;若选择右边为key,那就begin先走),end遇到比key处的元素更小的元素,就把这个元素给begin指针指向的位置(该指针指向的位置为选择的key,形成坑位),同时,这个end的位置形成一个坑位。然后begin++,begin遇到比key大的元素就把这个元素给end形成的坑位,同时,begin由于把元素给出去了,就形成了一个坑位......最后begin和end相遇,此时相遇的位置一定是一个坑位,就把最开始取出来的key值放进去。就完成了第一轮排序。

另一种思路(霍尔排序):

整体思想:把比选定的key的值大的元素不断后移。

        我们首先选定第一个元素为key,第一个元素处为前指针,第二个元素处为后指针。后指针处的元素小于选定的key值就进行前后指针交换,然后后指针往后走;若后指针指向的元素大于选定的key的值,那就让后指针继续往后走。最后后指针走到头,前指针指向最后一个小于key的元素,将这个元素与key交换,那么key左边全是小于(等于)他的,右边全是大于他的,同时返回这个位置,也就把数组进行自然分组了。然后递归,进行同样的做法。

int GetMidi(int* a, int left, int right)
{int midi = (left + right) / 2;// left midi rightif (a[left] < a[midi]){if (a[midi] < a[right]){return midi;}else if (a[left] < a[right]){return right;}else{return left;}}else // a[left] > a[midi]{if (a[midi] > a[right]){return midi;}else if (a[left] < a[right]){return left;}else{return right;}}
}
int PartSort2(int* a, int left, int right)
{// 三数取中int midi = GetMidi(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;int prev = left;int cur = prev+1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);cur++;}Swap(&a[prev], &a[keyi]);return prev;
}
void QuickSort(int* a, int left, int right)
{if (left >= right)return;int keyi = PartSort2(a, left, right);// [left, keyi-1] keyi [keyi+1, right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}


http://www.mrgr.cn/news/32992.html

相关文章:

  • 构建高可用和高防御力的云服务架构第一部分:深入解析DDoS高防(1/5)
  • git submodule
  • 低代码可视化工具-uniapp页面跳转传参-代码生成器
  • 为什么喝酱酒会回甘?
  • T4—猴痘识别
  • Redis数据结构之哈希表
  • 【HTTP】请求“报头”,Referer 和 Cookie
  • 盘点3款.NetCore(C#)开源免费商城系统
  • C++(2)进阶语法
  • 十四、运算放大电路
  • 初中数学证明集锦之三角形内角和
  • 【小沐学GIS】blender导入OpenStreetMap城市建筑(blender-osm、blosm)
  • 结构体对齐、函数传参、库移植
  • Spring:统一结果私有属性造成的前端无法访问异常报错问题
  • 博客管理系统可行性分析报告
  • Elionix 电子束曝光系统
  • 分析redis实现分布式锁的思路
  • 【亿美软通-注册/登录安全分析报告】
  • 掌握 JavaScript 中的函数表达式
  • 安装黑群晖系统,并使用NAS公网助手访问教程(好文)