目录
1.选择一个基准元素作为关键字(下面统一将基准元素称为关键字)
基本概念和排序方法概述:
排序:是按关键字的非递减或非递增顺序对一组记录重新进行排列的操作,简单来说,就是将一组杂乱无章的数据按一定的规律顺次排列起来。
排序的稳定性:在排序过程中宝石相等元素的相对位置不变则称排序算法具有稳定性,具体来讲,就是在元素序列中任意两个相等的元素X1和X2,他们排序的关键字相同,排序之前X1在X2之前,在排序之后,X1仍然在X2之前,即他们的相对位置不变,就说该排序具有稳定性。
内排序和外排序:内排序指在排序过程中,数据元素全部存放在内存中的排序,无需借助外部存储设备;外排序指在排序期间全部排序元素个数太多,不能同时存放在内存,必须根据排序过程的要求,不断在内,外存之间移动的排序,通常要将排序处理的数据放在读写较慢的外存储器上。
一、快速排序
快速排序是C.A.R.Hoare在1962年提出的一种排序算法。其工作原理是通过分治的方法来将一个数组排序。
二、快速排序的过程
1.选择一个基准元素作为关键字(下面统一将基准元素称为关键字)
通常是选择数组的第一个元素,也可以选择数组的最后一个元素或者随机选择一个元素。
2.根据关键字将待排序的数据分割成独立的两部分
经过一次排序之后,把所有小于关键字的数据交换到前面,把所有大于关键字的数据交换到后面,关键字放在这两个独立部分的中间,同时,这也是关键字最终的正确位置。
3.重复对这两组独立的数据进行快速排序
当所有元素都在正确位置上时,排序完成
快速排序实现框架:
void quick_sort(int *a, int left, int right){
if(right >= left) return; //子序列长度小于1,返回
int pos = parttition(a, left, right); //对序列进行划分操作
quick_sort(a, left, pos-1); //对前半部分递归调用排序操作
quick_sort(a, pos+1, right); //对后半部分递归调用排序操作
}
基本划分操作:
int parttition(int* a, int left, int right) {
int i = left - 1, j = right;//左右指针赋值
int e = a[right];//最右端元素为初始元素
while (1) {
while (a[i++] <= e) {//自左向右寻找
while (e <= a[j--]) {//自右向左寻找
if (j == left)
break;
}
if (i >= j)//确定划分位置
break;
swap(a[i], a[j]);//交换元素
}
}
swap(a[i], a[right]);
return i;
}
快速排序图:
left为区间开始地址,right为区间结束地址,key为关键字
第一趟排序:
初始关键字:
进行第一次交换,将key与right比较,若right<key,则将这个比key小的数放在左边去,若right>key,我们只需要将right--,right--之后与key进行比较,直到right<key交换元素为止
1.key<right ==> right--
2.key>right ==> 将right与key进行交换,left++
3.key>left ==> left++
4.key<left ==> 将left与key交换,right--
5.key>right ==> 将right与key进行交换,left++
6.key<left ==> 将left与key交换,right--
7.key<right ==> right-- ==>left=right ==> 第一趟排序结束
第一趟排序结果:
重复以上操作,对左右两部分进行排序
第二趟排序结果:
第三趟排序结果:
排序结束
三、快速排序的改进算法
1.针对小序列的改进
虽然快速排序已经是一种效率很高的算法,但是对于规模很小的序列,快速排序的速度并不能体现出来。所以,对于小规模序列(在5~25范围内)进行快速排序时,直接插入算法显得更快。所以在进行排序时,我们可以作出判断,数据范围在5~25时,我们优先选择直接插入排序。
算法具体实现:
void quick_sort(int* a, int left, int right) {
if (right - left <= M) {//M为我们所定义的范围
insert_sort(a, left, right);
}
else {
int pos = partition(a, left, right);//partition为基本划分操作,上面有具体实现
quick_sort(a, left, pos - 1);
quick_sort(a, pos + 1, right);
}
}
2.对划分元素(关键字)的选择
我们对于划分元素的选择,尽量不要选择过大或过小的元素,这样在分配过程中,会使得左右两边的子序列的元素个数规模相差太大,反而会降低快速排序的效率。
3.三路划分的快速排序算法
我们在排序过程中,经常可以遇到的问题是,一组数据有大量的相同元素,在进行排序时,不断的和相同的元素比较,这样就会使得快速排序的效率大大降低。
我们有一个直接的想法,就是将其划分成三个部分,一部分是比划分元素小的,一部分是比划分元素相等的,一部分是比划分元素大的:
还有一种三路划分更为巧妙的方法,其具体实现过程:在划分过程中,扫描时将遇到的左子序列中的数据与划分元素的元素放到序列的最左边,将遇到的右序列中的数据划分元素相等的元素放到序列的最右边,于是出现以下情况:
于是,当两个扫描指针相遇是数据相等的元素的确切位置就知道了,然后我们只要将所有的数据与划分元素等值的元素与扫描指针指向元素开始依次交换,我们将获得三路划分的结果。
优点:
1.在每次划分过程在,都可以将与划分元素相等的元素分割出来,使得这些相同的元素不会重复参加多次划分,避免了快速排序相同元素多次划分。
2..在没有重复元素时,三路划分仍然可以保持快速排序的性能。
更多介绍:[排序算法] 如何解决快速排序特殊情况效率低的问题------三路划分_组合排序效率低下-CSDN博客
具体算法实现:
//#include <iostream>
//#include <vector>
#include<bits/stdc++.h>
using namespace std;
// 辅助函数,用于三路划分
void threeWayPartition(vector<int>& a, int& lt, int& gt, int v, int left, int right) {
//le和gt表示索引,分别指向小于基准值v的最后一个元素的后面,
//大于基准值的第一个元素的前面
int i = left;
int ltIdx = left; // 小于v的元素的索引
int gtIdx = right + 1; // 大于v的元素的索引
while (i < gtIdx) {
if (a[i] < v) {
swap(a[i], a[ltIdx]);
i++;
ltIdx++;
}
else if (a[i] > v) {
swap(a[i], a[gtIdx - 1]);
gtIdx--;
}
else {
i++; // 等于v的元素保持不变,i向前移动
}
}
// 更新lt和gt的值
lt = ltIdx;
gt = gtIdx;
}
// 三路划分快速排序
void quickSort(vector<int>& a, int left, int right) {
if (left >= right) return;
int lt = left, gt = right, v = a[left]; // 基准值v选择为数组的第一个元素
threeWayPartition(a, lt, gt, v, left, right);
// 递归地对小于、等于和大于v的部分进行排序
quickSort(a, left, lt - 1); // 小于v的部分
quickSort(a, gt, right); // 大于v的部分
// 等于v的部分已经是有序的,不需要再次排序
}
int main() {
vector<int> a = { 10, 7, 8, 9, 1, 5, 7, 8, 9, 10 };
quickSort(a, 0, a.size() - 1);
for (int i : a) {
cout << i << " ";
}
cout << endl;
return 0;
}
本文是基于数据结构与算法关于快速排序的一点点小总结,目的是加深本人对快速排序的理解,若有错误,希望大佬可以指出