算法思想:1.先从数组中取出一个元素作为枢轴,一般情况下选取数组的第一个。
2.首尾相向遍历,从左边选取一个比枢轴大的数,从右边选择一个比枢轴小的数,然后交换这两个数;
3.重复步骤2,直到在枢轴的左边都比枢轴小,枢轴右边的数都比枢轴大。
时间复杂度:O(nlogn)
//快速排序代码实现
#include<iostream>
#include<vector>
using namespace std;
int Division(int a[], int left, int right) {//切分
int i = left +1; //i为左侧哨兵
int j = right;//j为右侧哨兵
int temp = a[left];//暂缓元素,作为比较元素
while (i <= j) {//左哨兵没有追上右哨兵地时候 左右相向而行
//从左边选取一个比枢轴大的数,从右边选择一个比枢轴小的数,然后交换这两个数;
while (a[i] < temp) i++;
while (a[j] > temp) j--;
if (i < j)
swap(a[i++], a[j--]);
else i++;
}
swap(a[j],a[left]);
return j;
}
void QuickSort(int a[], int left, int right) {//递归地思想来实现分支的策略
if (left > right) return;//输入错误 返回
int hole = Division(a,left,right);//划分点
QuickSort(a,left,hole -1);//枢轴左
QuickSort(a, hole+1, right);//枢轴右
}
int main() {
int a[] = { 9,6,7,6,5,4,3,2,1 };
QuickSort(a ,0, 8);
for (int i = 0; i < 9; i++) {
cout << a[i];
}
}
优化算法—>
1.随机选取法
引入原因:在待排序列是部分有序时,固定选取枢轴使快排效率底下,要缓解这种情况,就引入了随机选取枢轴
思路:使用随机数生成函数生成一个随机数rand,随机数的范围为[left, right],并用此随机数为下标对应的元素a[rand]作为中轴,并与最后一个元素a[right]交换,然后进行与选取最后一个元素作为中轴的快排一样的算法即可。
失灵情况 在整个数组数字全相等时,仍然是最坏情况,时间复杂度是O(n^2)。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
int random(int left,int right){
return rand() % (right - left + 1) + left;
}
void Qsort(int *a,int left,int right){
if (left >= right)
{
return;
}
//随机选取一个元素作为枢轴,并和最后一个元素进行交换
int ic = random(left,right);
swap(a[ic],a[right]);
int midIndex = data[right];
int i = left;
int j = right - 1;
while(true){
while(a[i++] < midIndex);
while(a[j] > midIndex && j >= left){
j--;
}
if (i < j) {
swap(a[i++],a[j--]);
}
else{
break;
}
}
swap(a[i],midIndex); //将枢轴放在正确的位置
Qsort(a,left,i -1);
Qsort(a,i+1,right);
}
2.三数取中(median-of-three)
引入的原因:虽然随机选取枢轴时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取枢轴
思路:假设数组被排序的范围为left和right,center=(left+right)/2,对a[left]、a[right]和a[center]进行适当排序,取中值为中轴,将最小者放a[left],最大者放在a[right],把中轴元与a[right-1]交换,并在分割阶段将i和j初始化为left+1和right-2。然后使用双向描述法,进行快排。
分割好处:
1.将三元素中最小者被分到a[left]、最大者分到a[right]是正确的,因为当快排一趟后,比中轴小的放到左边,而比中轴大的放到右边,这样就在分割的时候把它们分到了正确的位置,减少了一次比较和交换。
2.在前面所说的所有算法中,都有双向扫描时的越界问题,而使用这个分割策略则可以解决这个问题。因为i向右扫描时,必然会遇到不小于中轴的数a[right-1],而j在向左扫描时,必然会遇到不大于中轴的数a[left],这样,a[right-1]和a[left]提供了一个警戒标记,所以不需要检查下标越界的问题。
分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数
int Median(int *a,int left,int right){
int midIndex = (left + right)>>1;
if (a[left] > a[midIndex])
{
swap(a[left],a[midIndex]);
}
if (a[left] > a[right])
{
swap(a[left],a[right]);
}
if (a[midIndex] > a[right])
{
swap(a[midIndex],a[right]);
}
swap(a[midIndex],a[right-1]);
return a[right-1]; //返回中轴
}
3.当待排序序列长度分割到一定大小后,使用插入排序,对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。
void QSort(int arr[],int low,int high)
{
int pivotPos = -1;
if (high - low + 1 < 10)
{
InsertSort(arr,low,high);
return;
}
while(low < high)
{
pivotPos = Partition(arr,low,high);
QSort(arr,low,pivot-1);
low = pivot + 1;
}
}
参考文献
http://blog.sina.com.cn/s/blog_5a3744350100jnec.html
http://www.blogjava.net/killme2008/archive/2010/09/08/331404.html
http://www.cnblogs.com/cj723/archive/2011/04/27/2029993.html
http://blog.youkuaiyun.com/zuiaituantuan/article/details/5978009
https://www.cnblogs.com/vipchenwei/p/7460293.html