基本思想:
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
快速排序的示例:
算法的实现:
递归实现:
#include <stdio.h>
#include <iostream>
using namespace std;
static int i = 0;
void print(int a[],int i)
{
cout<<i <<" : ";
for(int j= 0; j<10; j++)
{
cout<<a[j] <<" ";
}
cout<<endl;
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int partition(int a[], int low, int high)
{
int privotKey = a[low]; //基准元素
while(low < high)
{ //从表的两端交替地向中间扫描
while(low < high && a[high] >= privotKey)
--high;
swap(&a[low], &a[high]); //从high 所指位置向前搜索,至多到low+1 位置。
//将比基准元素小的交换到低端
while(low < high && a[low] <= privotKey )
++low;
swap(&a[low], &a[high]);
}
print(a,i++);
return low;
}
void quickSort(int a[], int low, int high)
{
if(low < high){
int privotLoc = partition(a, low, high); //将表一分为二
quickSort(a, low, privotLoc -1); //递归对低子表递归排序
quickSort(a, privotLoc + 1, high); //递归对高子表递归排序
}
}
int main()
{
int H[10] = {3,1,5,7,2,4,9,6,10,8};
quickSort(H,0, 9);
return 0;
}
运行结果:
0 : 2 1 3 7 5 4 9 6 10 8
1 : 1 2 3 7 5 4 9 6 10 8
2 : 1 2 3 6 5 4 7 9 10 8
3 : 1 2 3 4 5 6 7 9 10 8
4 : 1 2 3 4 5 6 7 9 10 8
5 : 1 2 3 4 5 6 7 8 9 10
快速排序优化1:三数取中法
因为虽然快速排序整体的效率可观,但是当最坏情况发生时它的效率就会降低,为了降低最坏情况发生的概率,我们可以做如下改进。
当我们每次划分的时候选择的基准数接近于整组数据的最大值或者最小值时,快速排序就会发生最坏的情况,但是每次选择的基准数都接近于最大数或者最小数的概率随着排序元素的增多就会越来越小,我们完全可以忽略这种情况。但是在数组有序的情况下,它也会发生最坏的情况,为了避免这种情况,我们在选择基准数的时候可以采用三数取中法来选择基准数。
三数取中法:
选择这组数据的第一个元素、中间的元素、最后一个元素,这三个元素里面值居中的元素作为基准数。
代码实现:
#include <stdio.h>
#include <iostream>
using namespace std;
static int i = 0;
void print(int a[])
{
cout<<"第"<<i++ <<"次 : ";
for(int j= 0; j<10; j++)
{
cout<<a[j] <<" ";
}
cout<<endl;
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//优化1:三数取中
int GetMidIndex(int* a, int left, int right)//优化1:三数取中法
{
int mid = left + ((right - left) >> 1);//取区间中间元素的下标
if (a[left] > a[mid])//left > mid
{
if (a[left] < a[right]) { //mid < left < right
//swap(&a[left], &a[mid]);
return left;
}
else//left > mid,left > right
{
if (a[right] > a[mid]){ //mid < right < left
swap(&a[left], &a[right]);
return right;
}
else{ //right < mid < left
swap(&a[left], &a[mid]);
return mid;
}
}
}
else//left < mid
{
if (mid < right){ //left < mid < right
swap(&a[left], &a[mid]);
return mid;
}else //mid > left,mid > right
{
if (left < right){ //left < right < mid
swap(&a[left], &a[right]);
return right;
}else{ //right < left < mid
//swap(&a[left], &a[mid]);
return left;
}
}
}
}
int partition(int a[], int low, int high)
{
int privotKey = a[low]; //基准元素
while(low < high){ //从表的两端交替地向中间扫描
while(low < high && a[high] >= privotKey)
--high; //从high 所指位置向前搜索,至多到low+1 位置。
//将比基准元素小的交换到低端
swap(&a[low], &a[high]);
while(low < high && a[low] <= privotKey )
++low;
swap(&a[low], &a[high]);
}
print(a);
return low;
}
void qsort_improve(int r[ ],int low, int high)
{
if(low < high) { //长度大于k时递归, k为指定的数
GetMidIndex(r, low, high);
int pivot = partition(r, low, high); // 调用的Partition算法保持不变
qsort_improve(r, low, pivot - 1);
qsort_improve(r, pivot + 1, high);
}
}
int main()
{
int H[10] = {3,1,5,7,2,4,9,6,10,8};
qsort_improve(H, 0, 9);
return 0;
}
运行结果:
第0次 : 2 1 3 7 5 4 9 6 10 8
第1次 : 1 2 3 7 5 4 9 6 10 8
第2次 : 1 2 3 8 5 4 7 6 9 10
第3次 : 1 2 3 4 5 6 7 8 9 10
第4次 : 1 2 3 4 5 6 7 8 9 10
第5次 : 1 2 3 4 5 6 7 8 9 10
优化二:当待排序序列的长度分割到一定大小后,使用插入排序
优化原因:对于待排序的序列长度很小或是基本趋于有序时,快排的效率还是插排好。
自定义截止范围:序列长度N=10。当待排序的序列长度被分割到10时,采用快排而不是插排。
if (n <= 10)//当整个序列的大小n<=10时,采用插排
{
InsertSort(a, n);
}
优化三:取随机基数
/*随机选择枢轴的位置,区间在low和high之间*/
int SelectPivotRandom(int arr[],int low,int high)
{
//产生枢轴的位置
srand((unsigned)time(NULL));
int pivotPos = rand()%(high - low) + low;
//把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数
swap(arr[pivotPos],arr[low]);
return arr[low];
}