递归方法
1、hoare版本
思想:取数组第一个元素为key,right指针从最右端开始向左移动(必须是与key反方向指针先走),right寻找比key小的数(升序),如果找到比key小的数,停止移动。
之后left从最左端开始向右移动,left寻找比key大的数,找到后停止,right的数字和left的数字进行交换,right继续寻找...
直到left与right相遇,相遇位置的数字与key交换,这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数
然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。
纵向展开图:
int PartSort1(int* a, int left, int right) //一次的操作
{
int keyi = left;
while(left<right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
swap(&a[right], &a[left]);
}
swap(&a[left], &a[keyi]);
return left;
}
void QuickSort(int* a,int left,int right) //排序递归
{
if (left >= right)
{
return;
}
int new = PartSort1(a, left, right);
QuickSort(a, 0, left);
QuickSort(a, left+1, right);
}
2、挖坑版本
思想:将数组第一个元素拷贝,命名key
现在可视数组第一个元素是个坑,下标为hole=0(因为第一个元素已经拷贝,原数据可被覆盖,不会损失数据)
right指针从最右端向左移动,找到比key小的数,将该数放入坑位下标为hole的位置,并更新坑位,hole=right
之后left指针从最左端向右移动,找到比key大的数将该数放入坑位下标为hole的位置,并更新坑位,hole=left
直到left与right相遇,此时坑位也在它们相遇的地方,将key数据放入坑位,这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数
然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。
过程图:
int PartSort2(int* a, int left, int right)
{
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;
return hole;
}
void QuickSort(int* a,int left,int right)
{
if (left >= right)
{
return;
}
int new = PartSort2(a, left, right);
QuickSort(a, 0, left);
QuickSort(a, left+1, right);
}
3、前后指针法
思想:初识位置key,prev在数组最左端,cur为prev+1的位置
之后cur和key的数进行比较,如果cur小于key,则prev++后与cur交换数据
不断重复该过程,直到cur>=n后key和prev交换数据
这样以后相遇位置的数为key,左边是比key小的数,右边是比key大的数
然后将key左边和右边再分别进行该操作递归,直到只剩一个数字。
过程图:
前期prev++==cur,等于自己和自己交换数据,没有改变数组
int PartSort3(int* a, int left, int right)
{
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[keyi])
{
prev++;
int tmp = a[prev];
a[prev] = a[cur];
a[cur] = tmp;
}
cur++;
}
int tmp = a[left];
a[left] = a[prev];
a[prev] = tmp;
return prev;
}
void QuickSort(int* a,int left,int right)
{
if (left >= right)
{
return;
}
int new = PartSort3(a, left, right);
QuickSort(a, 0, left);
QuickSort(a, left+1, right);
}
优化——三数取中(中间)
当排升序时,原数组为降序,快排的速度会非常慢,上面的三种版本第一趟最终的结果都是:左边是比key小的数,右边是比key大的数,而降序直接导致key在边缘位置,每趟只会向内挪动一个位置,时间复杂度直接是O(N^2),和冒泡排序一样
所以我们可以控制取key的大小,在最左端、最右端和中间的数值进行比较,取中间大的数为key
int GetMidIndex(int* a, int left, int right)
{
int mid = (right + left) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
return mid;
else if (a[left] < a[right])
return right;
else
return left;
}
else
{
if (a[mid] > a[right])
return mid;
else if (a[left] > a[right])
return right;
else
return left;
}
}
int PartSort1(int* a, int left, int right)
{
int midi = GetMidIndex(a, left, right);
swap(&a[left], &a[midi]); //最左端的值和三数取中的值交换后再排序
int keyi = left;
while(left<right)
{
while (left < right && a[right] >= a[keyi])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
swap(&a[right], &a[left]);
}
swap(&a[left], &a[keyi]);
return left;
}
void QuickSort(int* a,int left,int right)
{
if (left >= right)
{
return;
}
int new = PartSort1(a, left, right);
QuickSort(a, 0, left);
QuickSort(a, left+1, right);
}
优化——三数取中(随机)
传统的三数取中比较数值已被定死,容易被针对,所以三数中,左右数不变,把中间的数的取值给为随机下标取值可以解决该问题
int GetMidIndex(int* a, int left, int right)
{
//int mid = (right + left) / 2;
int mid = left + rand() % (right - left);
if (a[left] < a[mid])
{
if (a[mid] < a[right])
return mid;
else if (a[left] < a[right])
return right;
else
return left;
}
else
{
if (a[mid] > a[right])
return mid;
else if (a[left] > a[right])
return right;
else
return left;
}
}
优化——三路划分
当待排序数组全是一个值时(如,全是2),效率也会非常慢,之前三个思路都是寻找比key大的和小的值,二路划分,在这种情况会十分棘手,未解决这一问题,我们可以使用三路划分的思路去解决
二路划分
三路划分
思想(假设数组名为a):起始位置(下标):left最左端,right最右端,cur=left+1(以key=a[left]为例)
当a[cur]<key:交换cur和left的值,cur++,left++;
当a[cur]>key:交换cur和right的值,right--;
当a[cur]==key:cur++;
直到cur>right为止为一趟
[left,right]为等于key的数
void QuickSort_ThreeWay(int* a, int begin, int end)
{
if (begin >= end)
return;
int left = begin;
int right = end;
int cur = left + 1;
int key = a[begin];
while (cur <= right)
{
if (a[cur] > key)
{
swap(&a[cur], &a[right]);
right--;
}
else if (a[cur] < key)
{
swap(&a[cur], &a[left]);
cur++;
left++;
}
else
{
cur++;
}
}
QuickSort_ThreeWay(a, begin, left-1);
QuickSort_ThreeWay(a, right + 1, end);
}
非递归方法
用非递归可以用栈(先进后出)去实现,传入开始下标和结束坐标,然后写一个函数去接受它们去排序
一周期:push 0和9,pop 0和9 同时push 6和9 0和4,pop0和4 同时push 3和4 0和1,pop 0和1
注意:栈push是从栈底开始,pop是从栈顶开始
首先,先写一个栈
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps)
{
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
int tmp = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
if (ps->_top == ps->_capacity)
{
STDataType* p = (STDataType*)realloc(ps->_a, sizeof(STDataType)*tmp);
ps->_a = p;
ps->_capacity = tmp;
}
ps->_a[ps->_top] = data;
ps->_top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->_a[ps->_top-1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
return ps->_top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = 0;
}
void QuickSortNonR(int* a, int begin, int end)
{
Stack st;
StackInit(&st);
StackPush(&st, end); //入栈原数组的尾下标
StackPush(&st, begin); //入栈原数组的头下标
while (!StackEmpty(&st)) //栈不为空的话,出栈 入栈
{
STDataType left = StackTop(&st); //left相当于begin
StackPop(&st);
STDataType right = StackTop(&st); //right相当于end
StackPop(&st);
int keyi = PartSort3(a,left,right); //在该区间段进行排序(任取上边三种排序思路)
if(keyi + 1 < right) //压栈right
{
StackPush(&st, right);
StackPush(&st, keyi + 1);
}
if (keyi - 1 > left) //压栈left
{
StackPush(&st, keyi - 1);
StackPush(&st, left);
}
}
StackDestroy(&st);
}
时间复杂度:O(nlogn)
空间复杂度:O(logn)
稳定性:不稳定