所用栈相关知识请参考:>数据结构之栈的基本操作<
快速排序基本思想
快速排序是一种划分交换的方法,它采用分治法进行排序。其基本思想是:
- 先从数列中取出一个数作为基准数
- 分区过程,将比基准值大的数全放到它的右边,小于或等于它的数全放到它的左边,基准值则在中间
- 再对左右区间重复第二步,直到各区间只有一个元素或没有元素
方法一:(交换法)
上图是对这个数组进行的第一次的划分,将这个数组分成了左子序列和右子序列(紫色的那部分),我们再对这两部分进行同样的操作,直到划分的序列中只有一个元素或者没有元素的时候停止。所以这种算法用递归来解是非常简单的。
///////////////////////////////////////////////////////////
//快速排序
//时间复杂度:最坏O(N^2),序列是完全逆序的
// 平均O(N*logN)
//空间复杂度:O(logN)
//////////////////////////////////////////////////////////
//交换法
int64_t Partion1(int array[], int64_t beg, int64_t end){
//1.先定义好区间的边界
int64_t left = beg;
int64_t right = end - 1;
//2.取最后一个元素作为基准值
int key = array[right];
while(left < right){
//3.从左到右找到一个大于基准值的元素
while(left < right && array[left] <= key){
++left;
}
//4.从右到左找到一个小于基准值的元素
while(left < right && array[right] >= key){
--right;
}
//5.进行交换
if(left < right){
Swap(&array[left], &array[right]);
}
}
//6.此时是将left指向的值和最后一个元素(基准值)进行交换
// 此时的left指向的值一定大于等于基准值
// a)如果是因为 ++left 导致的循环退出,由于 right 在上一次循环的交换中已经指向一个大于等于基准值的元素
// b)如果是因为 --right 导致的循环退出,由于在刚刚的 left 查找过程中 left 已经找到了一个大于等于基准值的元素
// 因此,最终的结论就是 left 指向的值一定大于等于基准值
Swap(&array[left], &array[end - 1]);
return left;
}
void _QuickSort(int array[], int64_t beg, int64_t end){
if(end - beg <= 1){
return;
}
//Partion 函数的作用,是对当前[beg, end)区间进行调整
//整理成以某个基准值为中心,左侧元素小于等于基准值,右侧元素大于等于基准值
//返回值表示的含义是基准值所在的下标
int64_t mid = Partion1(array, beg, end);
_QuickSort(array, beg, mid);
_QuickSort(array, mid + 1, end);
}
void QuickSort(int array[], int64_t size){
_QuickSort(array, 0, size);
}
注意:
- 从left开始找大于基准值的值的时候的判断条件必须是小于等于,因为有可能会出现重复的值。
- left必须从基准值的下标开始。否则的话:如果第一个元素就是最小的值,则后面的值都比基准值大,则在left和right相遇处的值与基准值再交换的话就会产生错误。
方法二:(挖坑法)
//挖坑法
int64_t Partion2(int array[], int64_t beg, int64_t end){
//1.定义好区间边界
int64_t left = beg;
int64_t right = end - 1;
//2.取最后一个元素作为基准值
int key = array[right]; //right指向的位置,就可以被覆盖了
while(left < right){
//从左到右找到一个大于基准值的元素
while(left < right && array[left] <= key){
++left;
}
if(left < right){
//将找到的这个大于基准值的元素,填到right指向的坑里
//随着填坑动作的完成,left指向的位置也就可以被别人覆盖
//left也就成了一个坑
array[right--] = array[left];
}
//4.从右到左找到一个小于基准值的元素
while(left < right && array[right] >= key){
--right;
}
if(left < right){
array[left++] = array[right];
}
}
array[left] = key;
return left;
}
void _QuickSort(int array[], int64_t beg, int64_t end){
if(end - beg <= 1){
return;
}
//Partion 函数的作用,是对当前[beg, end)区间进行调整
//整理成以某个基准值为中心,左侧元素小于等于基准值,右侧元素大于等于基准值
//返回值表示的含义是基准值所在的下标
int64_t mid = Partion2(array, beg, end);
_QuickSort(array, beg, mid);
_QuickSort(array, mid + 1, end);
}
void QuickSort(int array[], int64_t size){
_QuickSort(array, 0, size);
}
方法三:
//双指针前移法
int64_t Partion3(int array[], int64_t beg, int64_t end){
int64_t cur = beg;
int64_t pre = beg - 1;
int key = array[end - 1];
while(cur < end){
if(array[cur] < key && ++pre != cur){
Swap(&array[cur], &array[pre]);
}
++cur;
}
if(++pre != end){
Swap(&array[pre], &array[end - 1]);
}
return pre;
}
void _QuickSort(int array[], int64_t beg, int64_t end){
if(end - beg <= 1){
return;
}
//Partion 函数的作用,是对当前[beg, end)区间进行调整
//整理成以某个基准值为中心,左侧元素小于等于基准值,右侧元素大于等于基准值
//返回值表示的含义是基准值所在的下标
int64_t mid = Partion1(array, beg, end);
_QuickSort(array, beg, mid);
_QuickSort(array, mid + 1, end);
}
void QuickSort(int array[], int64_t size){
_QuickSort(array, 0, size);
}
性能分析
快速排序是一种快速的划分交换的算法,它是已知的最快的排序算法,其平均运行时间为O(N * 1ogN) 。它的速度主要归功于一个非常紧凑的并且高度优化的内部循环。但是他也是一种不稳定的排序,当基准数选择的不合理的时候他的效率又会变成O(N * N)。
快速排序的最好情况:
快速排序的最好情况是每次都划分后左右子序列的大小都相等,其运行的时间就为O(N*logN)。
快速排序的最坏情况:
快速排序的最坏的情况就是当分组重复生成一个空序列的时候,这时候其运行时间就变为O(N*N)。
快速排序的平均情况:
平均情况下是O(N*logN)。
改进
因为虽然快速排序整体的效率可观,但是当最坏情况发生时它的效率就会降低,为了降低最坏情况发生的概率,我们可以做如下改进。
当我们每次划分的时候选择的基准数接近于整组数据的最大值或者最小值时,快速排序就会发生最坏的情况,但是每次选择的基准数都接近于最大数或者最小数的概率随着排序元素的增多就会越来越小,我们完全可以忽略这种情况。但是在数组有序的情况下,它也会发生最坏的情况,为了避免这种情况,我们在选择基准数的时候可以采用三数取中法来选择基准数。
三数取中法:
选择这组数据的第一个元素、中间的元素、最后一个元素,这三个元素里面值居中的元素作为基准数。
非递归版本
利用栈来模拟快速排序递归算法的过程。
//快速排序的非递归版本
void QuickSortByLoop(int array[], int64_t size){
if(size <= 1){
return;
}
SeqStack stack;
SeqStackInit(&stack);
int64_t beg = 0;
int64_t end = size;
SeqStackPush(&stack, beg);
SeqStackPush(&stack, end);
while(stack.size > 0){
SeqStackTop(&stack, &end);
SeqStackPop(&stack);
SeqStackTop(&stack, &beg);
SeqStackPop(&stack);
if(end - beg <= 1){
continue;
}
int64_t mid = Partion1(array, beg, end);
SeqStackPush(&stack, beg);
SeqStackPush(&stack, mid);
SeqStackPush(&stack, mid + 1);
SeqStackPush(&stack, beg);
}
}