排序 – 快速排序
文章目录
一、快速排序(Hoare版本)
1.排序思想
快速排序Hoare版本的思想是:选择待排序序列中某元素为基准值key,进行排序,将序列分成两个子序列,要求左子序列的元素全部小于key,右边子序列的元素全部大于key,然后在左右子序列重复该过程,直到整个序列的元素都在相应的位置上。
单躺排序如上图所示,先选出一个key,一般选最左边或最右边的元素,然后左右两个指针对剩下的元素进行选择,右边选小,左边选大,然后左右两元素交换,就进行下一次选择,直到left和right两指针相遇,此时交换key和left的值,结束单躺排序。
单躺排序完成后,key就在正确的位置上,key就将原序列分割为两个子序列,如果左右两序列都有序,则整个序列有序,可以利用递归,分治左右区间。
左边做key,右边先走;
原因:因为要保证相遇位置的值小于key,right先走,right停下来后,left去遇right,相遇位置就是right停下来的位置,一定小于key。
2.代码示例
代码如下:
//交换
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//(Hoare版本)
//begin是序列的开头下标,end是序列的结尾下标
int PartSort1(int* a, int begin, int end)
{
//单躺
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[keyi], &a[left]);//交换key和最左边的数据,key就在正确的位置上了
keyi = left;//更新keyi
return keyi;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)//如果区间不存在或者只有一个数据是,直接返回
{
return;
}
int keyi = PartSort1(a, begin, end);
//递归调用[begin, keyi - 1], [keyi + 1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
3.特性总结
1.快排的综和性能和使用场景都是比较好的;
2.时间复杂度:O(N * logN);
3.空间复杂度:O(logN);
4.稳定性:不稳定。
二、快速排序(挖坑法)
1.排序思想
快排挖坑法的基本思想是:与Hoare法相似,选取最左边或最右边的数作为key,将其取出来形成一个坑位,然后L和R分别找大和找小,R先走,找到小后,将其填入坑位,R所在位置形成新的坑位,此时L找大,找到后再填入坑位,如此往复,直到L和R相遇,将key放到此时的坑位中,key就到了正确的位置,单躺排序结束。
如上图所示,挖坑法思想与Hoare法大致相同,只是数据交换的细节有所差别。
2.代码示例
代码如下:
//挖坑法
int PartSort2(int* a, int begin, int end)
{
int key = a[begin];
int piti = begin;
int left = begin;
int right = end;
while (left < right)
{
//右边先走,找小
while (left < right && a[right] >= key)
{
right--;
}
//把right选到的数放到坑里,更新piti
a[piti] = a[right];
piti = right;
//左边再走,找大
while (left < right && a[left] <= key)
{
left++;
}
//把left选到的数放到坑里,更新piti
a[piti] = a[left];
piti = left;
}
//将key放入坑位中
a[piti] = key;
return piti;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)//如果区间不存在或者只有一个数据是,直接返回
{
return;
}
int keyi = PartSort2(a, begin, end);
//递归调用[begin, keyi - 1], [keyi + 1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
三、快速排序(前后指针法)
1.排序思想
快排前后指针法的基本思想:与Hoare法相似,选取最左边或最右边的数作为key。创建两个指针:prev和cur,初始时,prev指向序列开头,cur指向prev的下一个元素,让cur找小与key的元素,若cur指向的元素小于key,先将prev++,再交换cur与prev所指向的元素,然后cur++;若cur找到的元素大于key,则直接cur++。
此番操作,前面在遇到小于key的元素时,cur与prev都指向同一个元素,相当于自己交换,位置不变,而当遇到大于key的元素时,直接cur++,prev不动,此时prev与cur拉开了差距,prev的下一个元素就是大于key的元素,当cur再找到小于key的元素时,就会将两者交换。最终,当cur遍历完数组元素后,将key与prev的位置交换,单躺排序结束。排序的结果是小于key的都在左边,大于key的都在右边,key到了正确的位置。
2.代码示例
代码如下:
//交换
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//前后指针法
int PartSort3(int* a, int begin, int end)
{
int prev = begin;
int cur = prev + 1;
int keyi = begin;
while (cur <= end)
{
//当cur位置小于keyi位置,且++prev不等于cur时,交换
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[keyi], &a[prev]);
return prev;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)//如果区间不存在或者只有一个数据是,直接返回
{
return;
}
int keyi = PartSort3(a, begin, end);
//递归调用[begin, keyi - 1], [keyi + 1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
四、快速排序(非递归)
1.排序思想
快排非递归的基本思想:在快速排序的基础上,将递归改为非递归,其算法思想是一致的,只是实现方式不同。
非递归法的实现方式为:借助栈这一先入后出的数据结构,在每次单躺排序后,都将分割好的子区间入栈,然后进行循环,下次单躺排序时,再从栈中将子区间取出来,不断循环。
2.代码示例
代码如下:
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
StackInit(&st);
StackPush(&st, end);//先把区间入栈
StackPush(&st, begin);
while (!StackEmpty(&st))//当栈不为空时,循环
{
int left = StackTop(&st);//取出左边界
StackPop(&st);
int right = StackTop(&st);//取出右边界
StackPop(&st);
int keyi = PartSort3(a, left, right);//单躺排序
if (keyi + 1 < right)//入栈右区间
{
StackPush(&st, right);
StackPush(&st, keyi + 1);
}
if (keyi - 1 > left)//入栈左区间
{
StackPush(&st, keyi - 1);
StackPush(&st, left);
}
}
StackDestory(&st);
}
总结
快速排序是一种效率很高的排序算法,本文主要介绍了快速排序的基本写法及挖坑法、前后指针法、非递归法等多种方式的实现。