挖坑法就是定义一个基准数(key),把key取出,形成一个坑
然后从右向左找一个比key小的数 ,把它挖出放到前面那个坑,这样就又形成了一个新的坑
再从左向右找一个比key大的数,把它挖出放到后面那个坑 ,然后形成一个新坑
然后又从右向左找一个比key小的数,再挖坑填坑
再从左向右找一个比key大的数,挖坑填坑
最后begin>=end挖坑就结束了,这时候把key放入最后一个坑,快排的单次排序就结束了,此时key(6)的一边比它小一边比它大,6的位置就排好了。
代码如下:
int PartSort1(int*a,int left,int right)
{
int begin = left;
int end = right;
/*int midindex = GetMidIndex(a, left, right);
Swap(&a[left], &a[midindex]);*/
int key = a[left];
int pivot = left;
while (begin < end)
{
//找小
while (begin < end && a[end] >= key)
{
end--;
}
a[pivot] = a[end];
pivot = end;
//找大
while (begin < end && a[begin] <= key)
{
begin++;
}
a[pivot] = a[begin];
pivot = begin;
}
pivot = begin;
a[pivot] = key;
return pivot;
}
当我们实现了单次排序后就可以采用分而治之的思路,把6的两边分成两个区间进行递归排序。
代码如下:
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
int keyindex = PartSort1(a, left, right);
QuickSort(a, left, keyindex - 1);//对key的左边进行递归排序
QuickSort(a, keyindex + 1, right);//对key的右边进行递归排序
}
左右指针法的思想是定义两个指针左指针和右指针,每次右指针向左移动找一个比key(key和上面的挖坑法一样)小的数,左指针向右移找一个比key大的数,然后把这个两个数交换,然后再移动交换,最后左指针和右指针相遇的时候把key和左指针交换序列就排好了。
图解:
这样我们的的单次排序就好了,然后就和挖坑法一样分成左右区间,再递归排序就好了
int PartSort2(int* a, int left, int right)
{
int begin = left;
int end = right;
int midindex = GetMidIndex(a, left, right);
Swap(&a[left], &a[midindex]);
int key = a[left];
while (begin < end)
{
//找小
while (begin < end && a[end] >= key)
{
end--;
}
//找大
while (begin < end && a[begin] <= key)
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[left]);
return begin;
}
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
//int keyindex = PartSort1(a, left, right);
int keyindex = PartSort2(a, left, right);
//int keyindex = PartSort3(a, left, right);
QuickSort(a, left, keyindex - 1);
QuickSort(a, keyindex + 1, right);
}
前后指针法和左右指针法类似,都是定义两个指针,通过指针找比key大的数,然后交换,最后交换key和prev指针,单次排序就好了,然后进行递归排序,序列就有序了。
图解:
经过上面的步骤单次排序就好了。
代码如下:
int PartSort3(int* a, int left, int right)
{
int midindex = GetMidIndex(a, left, right);
Swap(&a[left], &a[midindex]);
int prev = left, cur = left + 1;
int key = a[left];
while (cur <= right)
{
if (a[cur] < key && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[left], &a[prev]);
return prev;
}
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
//int keyindex = PartSort1(a, left, right);
int keyindex = PartSort2(a, left, right);
//int keyindex = PartSort3(a, left, right);
QuickSort(a, left, keyindex - 1);
QuickSort(a, keyindex + 1, right);
}
完整代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
void PrintArray(int* a, int n)//把数组中的元素打印出来
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
void Swap(int* p1, int* p2)//交换两个值
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//挖坑法
int PartSort1(int*a,int left,int right)
{
int begin = left;
int end = right;
int key = a[left];
int pivot = left;
while (begin < end)
{
//找小
while (begin < end && a[end] >= key)
{
end--;
}
a[pivot] = a[end];
pivot = end;
//找大
while (begin < end && a[begin] <= key)
{
begin++;
}
a[pivot] = a[begin];
pivot = begin;
}
pivot = begin;
a[pivot] = key;
return pivot;
}
//左右指针法
int PartSort2(int* a, int left, int right)
{
int begin = left;
int end = right;
int key = a[left];
while (begin < end)
{
//找小
while (begin < end && a[end] >= key)
{
end--;
}
//找大
while (begin < end && a[begin] <= key)
{
begin++;
}
Swap(&a[begin], &a[end]);
}
Swap(&a[begin], &a[left]);
return begin;
}
//前后指针法
int PartSort3(int* a, int left, int right)
{
int prev = left, cur = left + 1;
int key = a[left];
while (cur <= right)
{
if (a[cur] < key && ++prev != cur)
{
Swap(&a[cur], &a[prev]);
}
cur++;
}
Swap(&a[left], &a[prev]);
return prev;
}
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
int keyindex = PartSort1(a, left, right);//挖坑法
//int keyindex = PartSort2(a, left, right);//左右指针法
//int keyindex = PartSort3(a, left, right);//前后指针法
QuickSort(a, left, keyindex - 1);
QuickSort(a, keyindex + 1, right);
}
void TestQuickSort()
{
int a[] = { 6,1,2,7,9,3,4,5,10,8 };
QuickSort(a, 0,sizeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
TestQuickSort();
return 0;
}
递归实现快速排序虽然代码更简洁清晰,可读性更好;但其时间和空间消耗比较大、很多计算都是重复的、调用栈可能会溢出。因此,我们还需要掌握非递归实现快速排序。
非递归实现快速排序我们是用栈来实现的,每次单次排序前我们把要排的区间放入栈,再从栈中取出进行单次排序,再把key的左右区间放入栈,再对其单次排序,再取出……直到栈为空,我们就排好了。
图解:
把keyindex的两边入栈
然后再入栈,再取出排序
……(中间省略)
在最后我们可以看到,栈已经被取空了,排序就结束了
代码如下:
void PrintArray(int* a, int n)//把数组中的元素打印出来
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
void Swap(int* p1, int* p2)//交换两个值
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void QuickSortNonR(int *a,int n)
{
ST st;
StackInit(&st);
StackPush(&st, n - 1);
StackPush(&st, 0);
while (!StackEmpty(&st))
{
//把区间从栈中取出排序
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int keyindex = PartSort3(a, left, right);//此处也可以挖坑或前后指针
//把左右区间入栈
if (keyindex+1 < right)
{
StackPush(&st, right);
StackPush(&st, keyindex + 1);
}
if (keyindex-1 > left)
{
StackPush(&st, keyindex - 1);
StackPush(&st, left);
}
}
}
void TestQuickSortNonR()
{
int a[] = { 6,1,2,7,9,3,4,5,10,8 };
QuickSortNonR(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
我们知道快速排序是根据key的值采用分而治之的思路实现的,但是,如果key的值太小或太大就会出现一边倒的情况,那么快速排序的效率就会降低很多,如图:
对此,科学家们引入了三数取中
代码如下:
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (mid > left)
{
if (mid < right)
return mid;
else
return right;
}
else
{
return left;
}
}
int PartSort1(int*a,int left,int right)
{
int begin = left;
int end = right;
//三数取中
int midindex = GetMidIndex(a, left, right);
Swap(&a[left], &a[midindex]);
int key = a[left];
int pivot = left;
while (begin < end)
{
//找小
while (begin < end && a[end] >= key)
{
end--;
}
a[pivot] = a[end];
pivot = end;
//找大
while (begin < end && a[begin] <= key)
{
begin++;
}
a[pivot] = a[begin];
pivot = begin;
}
pivot = begin;
a[pivot] = key;
return pivot;
}
即让key取left、right和(left+right)之中的中间值
在均分下,对一个序列我们要递归logN次,而随着递归层数的增加我们递归调用的空间就越来越多,如图:
从上图我们可以看出,当需要排列的数足够多时,最后几层递归调用的空间是非常大的,所以在最后的几层小区间(具体大小可以自己定,但一般是十几)排序是我们不采用递归排序,而是直接用插入排序来代替,这样递归调用的空间就会大大减少,效率就提高了。
代码如下:
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
int keyindex = PartSort1(a, left, right);
//int keyindex = PartSort2(a, left, right);
//int keyindex = PartSort3(a, left, right);
//小区间优化
if (keyindex - left-1 > 10)
{
QuickSort(a, left, keyindex - 1);
}
else
{
InsertSort(a + left, keyindex - left);
}
if (right - keyindex-1 >10)
{
QuickSort(a, keyindex + 1, right);
}
else
{
InsertSort(a + keyindex + 1, right - keyindex);
}
}
快速排序在最坏的情况下时间复杂度为O(N^2),最好情况下时间复杂度为O(N*logN),但在引入三数取中后快速排序基本不会出现最坏情况。
快速排序在最好的情况下空间复杂度为O(logN),最坏为O(N)。
快速排序可能会改变两个相同元素的位置,因此快排不稳定。