快速排序
一、原理
快速排序是比较快的排序方法。 它的基本思想是通过一组排序将要排序的数据分割成独立的两部分,首先数组第一个元素作为基值(首元素可能不会是最优基值,那么怎样选取基准元素呢?),其中一部分的所有数据大于基值,另外一部分的所有数据小于等于基值,然后再按此方法对这两部分数据进行快速排序,整个排序过程可以递归进行,以此使所有数据变成有序序列。
二、算法设计
快速排序算法的思想如下:
- 分解:先从原序列中取出一个元素作为基值。以基值元素为界,将原序列分解为两个子序列,小于或等于基值元素的子序列在基值左侧,大于或等于基值元素的子序列在基值右侧;
- 治理:对两个子序列进行快速排序
- 合并:将两个有序子序列合并为一个有序序列,得到原问题的解
三、分治策略
如何分解是一个难题,因为若基准元素选取不当,原序列就有可能被分解为规模为 0 和 n-1 的两个子序列,这样快速排序就退化为冒泡排序了。
例如,有序列(30,24,5,58,18,36,12,42,39),采用快速排序的分治策略,第 1 次选取 5 作为基准元素,序列被分解后如下图所示。
第 2 次选取 12 作为基准元素,序列被分解后如下图所示:
这样做的效率是最低的,最理想的状态是将原序列分解为两个规模相当的子序列,那么怎样选取基准元素呢?一般来说,可通过以下方法选取基准元素:
-
选取第一个元素;
-
选取最后一个元素;
-
选取中间位置的元素;
-
选取第一个元素、最后一个元素、中间位置的元素这三者的中位数;
-
选取区间位置随机数 k(left≤k≤right),选取 a[k] 作为基准元素。
四、快速排序图解
再此,先拿数组首元素作为基值。
设当前待排序序列为 a[left:right],其中 left≤right:
-
选取数组的第一个元素作为基准元素,pivot=a[left],i=left,j=right;
-
从右向左扫描,找小于或等于 pivot 的数,令 a[i]=a[j],i++;
-
从左向右扫描,找大于或等于 pivot 的数,令 a[j]=a[i],j--;
-
重复第 2~3 步,直到 i 和 j 重合,将 pivot 放到中间,即 a[i]=pivot,返回 mid=i。
此时完成一趟排序,分为以mid为界的两个子序列,左侧子序列都小于或等于pivot,右侧子序列都大于pivot。然后合并两个序列,进行快速排序。
下面序列(30, 24, 5, 58, 18, 36, 12, 42, 39)为例,演示快速排序的过程。
- 初始化。i=left,j=right,pivot=r[left]=30。
2) 从右向左扫描,找小于或等于 pivot 的数,找到 a[j]=12。
swap(a[i],a[j]),i++,如下图所示:
3) 向右走,从数组的左边位置向右找,一直找到比 pivot 大的数,找到 R[i]=58 ,R[i] 与 R[j] 交换 ,j--。

4) 向左走,从数组的右边位置向左找,一直找到小于等于 pivot 的数,找到R[j]=18,R[i]与R[j]交换,i++。如下图所示:

5) 向右走,从数组的左边位置向右找,一直找到比 pivot 大的数,这是 i=j,第一轮排序结束,返回 i 的位置,mid=i 。以上的操作是对序列进行分解,其代码如下图所示:
cpp
#include <iostream>
#include <algorithm>
//划分函数
int part(int* n, int low, int hight)
{
//基值
int i = low;
int j = hight;
int pivot = n[low];
while (i != j)
{
while (i < j && n[j] > pivot){ j--; }//从右向左开始找一个 小于等于 pivot的数值
if (i < j)
{
std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
}
while (i < j && n[i] <= pivot) { i++; }//从左向右开始找一个 大于 pivot的数值
if (i < j)
{
std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位
}
}
return i;//返回最终划分完成后基准元素所在的位置
}
6)然后在分别对这两个序列(12,24,5,18)和(36,58,42,39)进行快速排序(递归)。其代码如下所示:
cpp
//合并排序
void QuickSort(int* n, int low, int hight)
{
if (low < hight)
{
int mid = part(n, low, hight); // 返回基准元素位置
QuickSort(n, low, mid - 1); // 左区间递归快速排序
QuickSort(n, mid + 1, hight); // 右区间递归快速排序
}
}
五、AC代码
cpp
#include <iostream>
#include <algorithm>
//划分函数
int part(int* n, int low, int hight)
{
//基值
int i = low;
int j = hight;
int pivot = n[low];
while (i != j)
{
while (i < j && n[j] > pivot) { j--; }//从右向左开始找一个 小于等于 pivot的数值
if (i < j)
{
std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
}
while (i < j && n[i] < pivot) { i++; }//从左向右开始找一个 大于 pivot的数值
if (i < j)
{
std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位
}
}
return i;//返回最终划分完成后基准元素所在的位置
}
//合并排序
void QuickSort(int* n, int low, int hight)
{
if (low < hight)
{
int mid = part(n, low, hight); // 返回基准元素位置
QuickSort(n, low, mid - 1); // 左区间递归快速排序
QuickSort(n, mid + 1, hight); // 右区间递归快速排序
}
}
int main()
{
int n[100];
int count;
std::cout << "请输入要排序的数据的个数: " << std::endl;
std::cin >> count;
std::cout << "请输入要排序的数据: " << std::endl;
for (int i = 0; i < count; i++)
{
std::cin >> n[i];
}
std::cout << std::endl;
QuickSort(n, 0, count-1);
std::cout << "排序后的数组为:" << std::endl;
for (int i = 0; i < count; i++)
{
std::cout << n[i] << " ";
}
std::cout << "\n";
return 0;
}
- 输出结果

六、快速排序优化拓展(参考)
为避免出现最坏情况 (分解:划分函数需要扫描每个元素,每次扫描的元素数量都不超过 n,因此时间复杂度为 O(n)。) ,可以在选取基准元素时引入随机化策略,首先生成一个 [left,right] 区间的随机数 k,然后将 a[k] 和 a[left] 交换,其他代码保持不变。
算法代码:
cpp
int part(int* n, int low, int hight)
{ // 划分函数,引入随机化策略
int k = low + rand() % (hight - low + 1); // 生成 [left, right] 区间的随机数
std::swap(n[k], n[low]);
int i = low, j = hight, pivot = n[low];
while (i < j) {
while (n[j] > pivot && i < j) { j--; } // 找右侧小于或等于 pivot 的数
if (i < j)
{
std::swap(n[i++], n[j]);//r[i]和r[j]交换后 i 向右移动一位
}
while (n[i] < pivot && i < j) { i++; } // 找左侧大于或等于 pivot 的数
if (i < j)
std::swap(n[i], n[j--]); //r[i]和r[j]交换后 j 向左移动一位
}
n[i] = pivot; // 放到中间
return i;
}
注意:
- 选取基值时应尽量引入随机策略。选取第一个元素或最后有个元素为基值时,若序列本身有序,则会退化为最坏情况,出现超时情况;
- 在划分函数中,若引入随机策略,与pivot比较的语句不要带等号,例如while(n[i] <= pivot && i < j),当序列元素均等时,则会退化为最坏情况,出现超时的情况。
七、共勉
txt
以上是我对快速排序的理解,如果有误,烦请评论指出,一起学习,无限进步!!!
