版本一:Hoare版本
单趟排序的思想:
一般让左边做key
左边做key,右边先走(原因:保证了相遇位置比key小)(if右边做key,左边先走)
右边先走,找小的,左边再走,找大的,然后交换。重复,直至相遇,相遇的值与key交换
单趟排序的意义:
1.分割出了左右区间:左边的值比key要小,右边的值比key要大
2.key已经落到了正确的位置,即排序后的正确位置
剩下的问题:将左区间有序、将右区间有序,用递归实现
代码实现:
void QuickSort(int* a, int begin,int end)//Hoare版本
{
if (begin >= end)
return;
int left=begin;
int right=end;
int keyi = left;
while (left < right)
{
while (left < right&&a[right] >= a[keyi])
right--;
while (left < right&&a[left] <= a[keyi])
left++;
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin,keyi-1);
QuickSort(a, keyi+1, end);
}
理想状态下:时间复杂度O(N*logN)—每次的key都能移到正中间
最坏情况下:时间复杂度O(N^2)顺序和逆序都是—每次的key都是最大或最小
优化一:三数取中(中间大小的那个数)
面对接近有序或有序的情况下快排就没有优势,那么提出优化:三数取中(中间大小的那个数):最左边、最右边、中间值,取中间大小(不是最大也不是最小)的那个值,再跟最左边的数交换,最左边的数还是做key。
加入三数取中后,快排几乎不会出现最坏情况,时间复杂度:O(N*logN)
优化二:小区间优化
递归调用中,底层的调用次数太多(比如:只排10个数时,递归了10多次),太浪费、耗时、耗栈,不如此时用插入排序 ,此时减少了80%-90%的递归次数
代码如下:
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
//小区间用直接插入代替
if ((end - begin + 1) < 10)
{
InsertSort(a+begin,end-begin+1);
}
else
{
//三数取中
int mid = GetMidIndex(a, begin, end);
Swap(&a[begin], &a[mid]);
int left = begin;
int right = end;
int keyi = left;
while (left < right)
{
//左边做key,右边先走;
while (left < right&&a[right] >= a[keyi])
right--;
while (left < right&&a[left] <= a[keyi])
left++;
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
}
版本二:挖坑法
单趟排序的思想:
第一步:将第一个数据(选最左边)放在临时变量key中,形成一个坑位。
第二步:(左边做key,右边先走)右边找小,把小的放到坑里,自己的这个位置形成新坑。
第三步:左边找大,把大的放到坑里,自己的位置形成新坑。
反复第二、第三步,直至左右相遇——一定相遇在坑位
最后一步:把key放到坑位里
剩下的问题:将左区间有序、将右区间有序,用递归实现
代码实现:
void QuickSort(int* a, int begin, int end)//挖坑法
{
if (begin >= end)
return;
int left = begin;
int right = end;
int key = a[left];
int hole = left;
while (left < right)
{
while (left < right&&a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
while (left < right&&a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
QuickSort(a, begin, hole - 1);
QuickSort(a, hole + 1, end);
}
版本三:双指针法
单趟:
左边做key,一前一后两个指针,cur和prev
第一步:cur找比key小,找到小后停下
第二步:prev++,交换prev位置和cur位置的值
重复第一、二步,直至cur走完
最后一步:交换prev位置的值和key
剩下的问题:将左区间有序、将右区间有序,用递归实现
代码实现:
void QuickSort(int* a, int begin, int end)//双指针法
{
if (begin >= end)
return;
int keyi = begin;
int prev = begin;
int cur = begin + 1;
while (cur<=end)
{
//找到比key小的值时,跟++prev位置交换,小的往前翻,大的往后翻
if (a[cur] < a[keyi])
{
Swap(&a[++prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
QuickSort(a, begin, keyi- 1);
QuickSort(a, keyi + 1, end);
}