快速排序
时间复杂度:平均为O(nlog(n)) 最差情况为O(n2)
空间复杂度:O(log(2n)) ~ O(n)
稳定性:不稳定
算法原理
首先给出几个定义
(1)子数组的头指针和尾指针:在一个数组中,假设有一个子数组。这个子数组的第一个值在原数组的下标,我们定义为子数组的头指针。而子数组的最后一个值在原数组的下标,我们定义为子数组的尾指针。
例子:
原序列
{2 3 7 4 3 8 7}
在其中随便取出一个子序列
{2 3 (7 4 3) 8 7}
得到
{7 4 3}
则子数组{7 4 3}的头指针为2(数组首地址为0),尾指针为4。
(2)参考值和参考指针:对于任意一个子数组,首先选定子数组的第一个元素为参考值,参考值在原数组中的位置被称为初始参考指针。
例子:
原序列
{2 3 7 4 3 8 7}
取出一个子序列
{7 4 3}
初始参考值是子数组的第一个元素7,参考指针为2。此时的参考指针和子序列的头指针是一样的(但注意,我们是要排序的,排序后就不一定一样了)。
快速排序都干了啥?
以从小到大排序为例。
快速排序本质是对参考值与数组的其他值进行一系列交换操作,使得交换后子序列满足如下条件
(1)位于参考值前的数据都小于参考值
(2)位于参考值后的数据都大于参考值
(3)对参考值前的所有数据(即小于参考值的数据)没有顺序要求
(4)对参考值后的所有数据(即大于参考值的数据)没有顺序要求
先不要管一系列交换操作都干了什么,先来理解一下快速排序的大局思路。
按照如下操作:
(1)先对整个数组做上述交换操作
(2)取参考值前的所有数据作为一个子数组,我们给他起个名字叫左数组
(3)取参考值后的所有数据作为一个子数组,我们给他起个名字叫右数组
(4)分别对左数组和右数组重复上述交换操作
那么什么时候停止计算呢?
答曰:当子数组为空或者只有一个元素时,停上述操作
于是按照上述步骤就实现了排序。以数组{4 3 7 2 5 1 6}
为例,排序过程如下图所示。注意:我们在操作子数组的时候都直接在原数组中操作,这样操作结束后原数组就是有序的了。
接下来看看如何实现上述的一系列交换操作的。
这里就需要用到头指针和尾指针和参考指针了
具体思路为:
首先进行尾指针和参考指针的相关操作
(1)判断尾指针里的数据是否小于参考值。
(2)如果小于,交换参考值和尾指针里的数值(尾指针不变),参考指针移动到尾指针处,结束尾指针的操作。
(3)如果大于或者等于,尾指针向前移动一位。
(4)判断尾指针是否和头指针重合,不重合时重复上述操作,否则结束尾指针的操作。
之后进行头指针和参考指针的相关操作
因为有尾指针的操作,这时参考指针已经和头指针脱离开了。
(1)判断头指针里的数据是否大于参考值。
(2)如果大于,交换参考值和头指针里的数值(头指针不变),参考指针移动到头指针处,结束头指针的操作。
(3)如果小于或者等于,头指针向前移动一位。
(4)判断尾指针是否和头指针重合,不重合时重复上述操作,否则结束尾指针的操作。
进入头指针操作时尾指针和参考指针是重合的,但经过头指针操作二者又脱离开了。
最后判断头指针和尾指针是否重合
如果没重合,重新进行尾指针操作和头指针操作,一直到头尾指针重合。
关于上述操作的理解
这样操作可以保证,头指针左侧的数都一定小于参考数,尾指针右侧的数都一定大于参考数!!!
因为:尾指针中的数大于参考数时才会向左移动,等于或者小于的时候则不会动;头指针中的数小于参考数时才会向右移动,等于或者大于的时候则不会动。
从小到大排序的代码如下:
#include <iostream>
#include <vector>
using namespace std;
//快速排序
//从小到大
void fastSort(vector<int> &numVec, int left, int right)
{
//如果为空数组或者数组长度为1,则不需要排序了,直接返回
//这个判断十分重要,它可以使递归过程结束。不加这个逻辑会进入递归的死循环。
if (!(left < right))
{
return;
}
//参考指针(里面存着参考数)
int rPtr = left;
//头指针
int sPtr = left;
//尾指针
int ePtr = right;
//排序部分
//若头指针和尾指针没有重合
while (sPtr < ePtr)
{
//尾指针操作部分
//尾指针不可以超过头指针
while (sPtr < ePtr)
{
//判断尾指针里的数据是否大于参考数据
if (numVec[rPtr] <= numVec[ePtr])//从大到小改为>=
{
//如果大于:
//尾指针向前移动一位
ePtr--;
}
else
{
//如果小于:
//(1)交换参考指针与尾指针的数值
//(2)参考指针位置改变到尾指针处
//(3)跳出循环
swap(numVec[rPtr], numVec[ePtr]);
rPtr = ePtr;
break;
}
}
//头指针操作部分
//头指针不可以超过尾指针
while (sPtr < ePtr)
{
//判断头指针里的数据是否小于参考数据
if (numVec[rPtr] >= numVec[sPtr])//从大到小改为<=
{
//如果小于:头指针向后移动一位
sPtr++;
}
else
{
//如果大于:
//(1)交换参考指针与头指针的数值
//(2)参考指针位置改变到尾指针处
//(3)跳出循环
swap(numVec[rPtr], numVec[sPtr]);
rPtr = sPtr;
break;
}
}
}
//排序结束
//排序结果为:
//参考数位于数组的最中间
//参考数前的所有数都小于参考数
//参考数后的所有数都大于参考数
//递归部分:
//对参考数前的所有数再次执快速排序
fastSort(numVec, left, rPtr - 1);
//对参考数后的所有数再次执快速排序
fastSort(numVec, rPtr + 1, right);
}
int main()
{
vector<int> numVec{ 4,3,7,2,5,1,6 };
//numVec 待排序数组
//0 排序下标范围的起始点
//numVec.size() - 1 排序下标范围的结束点
//首先取整个数组为子数组
fastSort(numVec, 0, numVec.size() - 1);
//打印排序结果
for (int i = 0; i < numVec.size(); i++)
{
cout << numVec[i] << " ";
}
cout<<endl;
return 0;
}