快速排序算法是一种利用了分治思想的排序算法,基本思想是在数组中找到一个“主元”,将所有小于“主元”的元素放在它的左边,将所有大于“主元”的元素放在它的右边,然后以“主元”为分界,将原来的数组分成两个,然后在这两个数组中重复这个过程,直到最后整个数组都是有序的。
本文首先引述了《算法导论》中的算法描述,然后给出了递归和非递归两种版本的实现。
1、算法描述
下面是《算法导论》中对于快速排序算法的描述:
上面的描述已经很清楚了,下面是通俗的描述,方便对照理解。
1、实际上这个过程中“主元”的位置不是一开始就固定的,选择数组的最后一个元素作为“主元”,然后从数组左边向右进行遍历,一直到数组倒数第二个元素。
2、如果遇到了比“主元”小的元素,就尽可能的向左移动,如果遇到了比“主元”大的元素,则不进行操作。
3、遍历结束后整个数组一定有一个位置,这个位置左侧的所有元素都比“主元”小,那么就把“主元”和这个位置的元素进行交换,交换后还可以保证“主元”右侧的元素都比“主元”大。
4、然后将“主元”作为分界,对“主元”左侧和右侧的两个数组分别进行同样的排序即可,直到整个数组排序完成。
2、递归实现
从上面的过程可以看出来,快速排序是一个典型的递归过程,首先给出递归的实现。
#include<stdlib.h>
#include<stdio.h>
void quicksort(float * arr, int p, int r)
{
if (p < r)
{
static float ex = 0;
int i = p - 1;
float pivot = arr[r];
for (int j = p; j < r; ++j)
{
if (arr[j] < pivot)
{
i++;
ex = arr[i];
arr[i] = arr[j];
arr[j] = ex;
}
}
arr[r] = arr[i + 1];
arr[i + 1] = pivot;
quicksort(arr, p, i);
quicksort(arr, i + 2, r);
}
}
3、非递归实现
然后给出非递归的版本,非递归的版本实现主要借助了栈来实现。
void quicksort_non_recurrence(float * arr, int p, int r)
{
if (p < r)
{
int *stack = (int*)malloc(sizeof(int) * (r - p + 1));
int pointer = -1;
stack[++pointer] = p;
stack[++pointer] = r;
float ex = 0;
int left, right = 0;
while (pointer != -1)
{
right = stack[pointer--];
left = stack[pointer--];
int i = left - 1;
float pivot = arr[right];
for (int j = left; j < right; ++j)
{
if (arr[j] < pivot)
{
i++;
ex = arr[i];
arr[i] = arr[j];
arr[j] = ex;
}
}
arr[right] = arr[i + 1];
arr[i + 1] = pivot;
if (left < i)
{
stack[++pointer] = left;
stack[++pointer] = i;
}
if (i + 2 < right)
{
stack[++pointer] = i + 2;
stack[++pointer] = right;
}
}
free(stack);
}
}
void main()
{
float arr[] = { 2,5,3,6,1,0 };
quicksort_non_recurrence(arr, 0, 5);
for (int i = 0; i < 6; ++i)
{
printf("%f\n", arr[i]);
}
}
上面代码中借助栈实现的时候,在这行代码中 int *stack = (int*)malloc(sizeof(int) * (r - p + 1)),申请的内存为(r-p+1)个int,实际上这里的栈是一个int数组,长度等于待排序的数组长度,记为n。
之所以栈的总空间为n个int,是因为栈里需要存储所有等待排序的子数组的左右下标,当n为偶数的时候,极端情况下,这个栈里会存储n个下标,当n为奇数时,这个栈里会存储n-1个下标,因此申请存储n个int的空间就足够了。具体过程可以借助树型结构来分析,极端情况下,会把原来的数组每两个相邻元素分成一个子数组,不断的放进栈里,直到最后两个元素下标入栈,因此需要的最大下标数量就等于n。