快速排序有递归和非递归的2种实现方法,一般我们说的快排都是指递归实现的,而快速排序在对它的左右区间进行调整时也有3种方法:①Horae法 ②挖坑法 ③前后指针法 。对于左右区间的调整就是通过选出一个基准值 (key) ,然后将key的左右两侧分成小于key的和大于key的。在快速排序中对于左右区间的划分一直都是快排的核心,接下来我们来挨个分析以上提到的3种方法及其实现的代码。【接下来我们以排升序为例】
Horae法
这种方法是由是Hoare在1962年提出的一种二叉树结构的交换排序方法。排序的主要思路: 这里以最右边 (end) 作为基准值 (key) 为例,此时左边要先走,左边先去找比key大的;然后右边再去找比key小的。找到后2者进行交换。直到左边begin和右边end相等(begin不小于end)的时候,停止循环。然后交换key和相遇点的值。
//1. Horae法
int HoraeMethod(int* a, int begin, int end)
{
//右边做key,“左边” 先移动!
int key = end;
while (begin < end)
{
//【因为end做key,左边先走】左边先走,找到比key大的
while (begin < end && a[begin] <= a[key])
{
begin++;
}
//右边再走,找到比key小的
while (begin < end && a[end] >= a[key])
{
end--;
}
if (begin < end)
{
swap(&a[begin], &a[end]);
}
}
//交换key与相遇点的值,这样key的左边都是比key小的,key的右边都是比key大的了
swap(&a[key], &a[begin]);
return begin;//返回相遇点
}
挖坑法
挖坑法的核心就是通过基准值 (key) 去保存我们最开始选择的基准值,此时由于基准值的值已经被保存了,那么原来的位置的值就可以被其他值替换了,也就相当于原来的位置就变成了一个“坑”。排序的主要思路:这里以最左边 (begin) 作为key,同时它也作为个坑。推荐从右边开始查找,右边找比key小的,找到后将它的值 赋值给 坑的位置,这一步骤就相当于将原来位置的坑填上了,然后又对现在的位置进行了挖坑。然后左边再继续找比key大的,继续填坑,挖坑。。。直到左边begin和右边end相等(begin不小于end)的时候,停止循环。最后将key的值填到它们的相遇点。
//2.挖坑法
int HoleMethod(int* a, int begin, int end)
{
int key = a[begin];
while (begin < end)
{
while (begin < end && a[end] >= key)
{
end--;
}
//把左边的坑填上,右边进行挖坑
a[begin] = a[end];
while (begin < end && a[begin] <= key)
{
begin++;
}
//把右边的坑填上,左边进行挖坑
a[end] = a[begin];
}
//相遇点填上key值
a[begin] = key;
return begin;
}
前后指针法
前后指针法顾名思义就是借助前后2个指针进行排序。排序的主要思路:我们这里以end作为key值,同时定义2个指针(下标),前指针cur和后指针prev。当cur对应的值小于key的时候,先让prev++,然后再交换prev和cur对应的值,最后cur再++;如果cur对应的值大于key,只让cur++。这样做可以让小于等于key的值永远在prev的前面,prev和cur中间的值都是大于key的。最后当cur > end时循环。
//3.前后指针法
int PrevCurMethod(int* a, int begin, int end)
{
int cur = begin;
int prev = cur - 1;
int key = a[end];
while (cur <= end)
{
//cur的值小于key,那么prev先++,然后交换cur和prev的值,cur再++
//if (a[cur] <= key)
if(a[cur] <= key && ++prev != cur)//小优化:如果++prev和cur相等,就不用换了
{
//prev++;
swap(&a[prev], &a[cur]);
}
cur++;//如果cur的值比key大,那么只有cur++;
}
return prev;
}
非递归实现快速排序
我们用非递归的意义就在于,当数据量过大的时候,我们使用递归会导致栈溢出(Stack Overflow)的问题,这是由于递归太深,栈空间不足出现的。所以我们要使用非递归,在堆区去开辟空间从而实现快速排序。而实现非递归的快速排序的核心就在于如何去模拟实现递归。这里我们需要借助(数据结构中的)栈,借助栈的后进先出的性质,这一性质和我们递归调用函数的时候非常相似,所以我们才可以用栈区模拟实现递归。
首先我们需要将整段需要快排的区间Push进去,然后再取出来,对这段区间进行单趟快排。接收返回值keyindex,然后再将它的左区间和右区间分别进行push,再分别对他们进行快排。。。(进行循环)当左区间元素只有1个时,不再push了。当栈中没有任何元素时(代表没有任何区间时),此时所有区间都已经经过快排了,完成快速排序。
//非递归版----快速排序
void quicksortNonR(int* a, int begin, int end)
{
//非递归版要借用栈---利用栈的后进先出的特性!!!
Stack st;
StackInit(&st);
StackPush(&st, begin);//左边先入
StackPush(&st, end);
//栈为空的时候就代表所有区间都是有序的了
while (!StackEmpty(&st))
{
//因为是end后入的,所以出的时候就是end先出,要用right接收
int right = StackTop(&st);
StackPop(&st);
int left = StackTop(&st);
StackPop(&st);
//这里要传left和right【代表传一个区间过去,对这个区间进行快排】
int keyindex = PrevCurMethod(a, left, right);
//不能取等【取等的时候代表这个区间就1个元素,那么这个元素就已经是有序的了】
if (left < keyindex - 1)
{
//这里也必须是左边先入,右边后入;对应开头写的右边先Pop,左边后Pop
StackPush(&st, left);
StackPush(&st, keyindex - 1);
}
if (keyindex + 1 < right)
{
StackPush(&st, keyindex + 1);
StackPush(&st, right);
}
}
StackDestory(&st);
}