一、快排的定义:
同归并排序一样,快排也使用分治法思想。
分解: 将数组A[p...r]分解成两个子数组A[p...q-1] 和A[q+1...r],使得第一个子数组元素均小于或等于A[q],第二个数组元素均大于或等于A[q].也包括计算索引q的值的过程.
解决: 递归调用快排对上面分解出来的两个子数组进行排序,直到两个数组元素个数都不多于1.
合并: 因为两个子数组均是有序的,所以合并不需要额外的操作,A[p...r]就是有序的.
下面是采用分治思想实现快排的伪代码(也称为递归版快排):
从上面的伪代码可以知道,快排的关键在于PARTITION 函数上,PARTITION 函数的伪代码如下:
划分(PARTITION)函数中直接用数组最后一个元素值作为"枢轴"元素来重新划分数组成两个子数组,将比"枢轴"元素值小的和比其大的分别放在两个子数组中。下图是一个数组一次划分过程的详细流程:
函数最后两行主要用来交换"枢轴"元素和比"枢轴"元素大的最左边元素,也是两个子数组的边界位置元素,主要是把"枢轴"元素放在两个子数组中间。对应上图 h->i 变化中将8和4交换位置.
PARTITION运行时间为θ(n) 其中n=r-p+1;
二、快排的其他版本:
因为快排的关键函数式PARTITION 函数,为了优化PARTITION处理时间,可以对PARTITION进行优化,所以有了不同版本的快排算法:
1、快排的随机化版本:随机获取"枢轴"元素,关键在于随机算法的设计.
2、霍尔快排:Hoare划分函数的伪代码如下:
3、三者取中法获取"枢轴"元素,从数组起始,中间,末尾三个元素中获取中间值作为"枢轴"元素;
(略)
4、采用尾递归(tail recursion)优化快排堆栈深度,取消上面QUICKSORT中的第二个递归调用,修改后伪代码如下:
code:
/******************************************************************************
* runs in O(nlgn) time, sorts an array in place.
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
/*quickSort interface*/
void quick_sort(int * array, int start, int end);
void tail_recursive_quick_sort(int * array, int start, int end);
/*partition function*/
int partition(int * array, int start, int end);
int hoare_partition(int * array, int start, int end);
/*common function*/
void array_dump(int * array, int start, int end);
void swap_array_element(int * array, int src, int dst);
int main(void)
{
int i = 0;
int array[] = {2, 8, 7, 1, 3, 5, 6, 4};//{13,19,9,5,12,8,7,4,11,2,6,21};
int array_length = sizeof(array)/sizeof(array[0]);
cout << "Before sort : " << endl;
array_dump(array, 0, array_length-1);
tail_recursive_quick_sort(array, 0, array_length-1);
cout << "After sort : " << endl;
array_dump(array, 0, array_length-1);
return 0;
}
/******************************************************************************
* The key to the algorithm is the Partition procedure, which rearrange the
* subarray A[p...r] in place. what it do:
* 1.Choose one element as the pivot element.
* 2.divide array into two subarray, array[start...i] all element less equal to pivot
* element, and array[j...end] greater or equal to pivot element.
******************************************************************************/
int partition(int * array, int start, int end)
{
int i = 0, j = 0, pivot = 0, tmp = 0;
pivot = array[end];
i = start-1;
for(j=start; j<end; j++)
{
if(array[j] <= pivot)
{
i++;
swap_array_element(array, i, j);
}
}
swap_array_element(array, i+1, end);
return i+1;
}
/******************************************************************************
* Hoare partition version, the original partition algorithm, due to C.A.R.Hoare
******************************************************************************/
int hoare_partition(int * array, int start, int end)
{
int i = 0, j = 0, tmp = 0;
int pivot = 0;
pivot = array[start];
i = start;
j = end;
while(1)
{
while(array[j] > pivot)
j--;
while(array[i] < pivot)
i++;
if(i < j)
{
swap_array_element(array, i, j);
}
else
{
return j;
}
}
}
/******************************************************************************
* quickSort recursive procedure
******************************************************************************/
void quick_sort(int * array, int start, int end)
{
int pivot = 0;
cout << "Enter quick_sort start : " << start << " end : " << end << endl;
if(start < end)
{
pivot = hoare_partition(array, start, end); //replace by partition
quick_sort(array, start, pivot-1);
quick_sort(array, pivot+1, end);
}
}
/******************************************************************************
* tail recursive quickSort
******************************************************************************/
void tail_recursive_quick_sort(int * array, int start, int end)
{
int i = 0;
int pivot = 0;
cout << "Enter tail_recursive_quick_sort start : " << start << " end : " << end << endl;
for(i = start; i <= end; i++)
cout << array[i] << " ";
cout << endl;
while(start < end)
{
pivot = hoare_partition(array, start, end); //replace by partition
tail_recursive_quick_sort(array, start, pivot-1);
start = pivot+1;
cout << "start = "<< start << endl;
}
}
/******************************************************************************
* just for test, dump array all value
******************************************************************************/
void array_dump(int * array, int start, int end)
{
int i = 0;
for(i = start; (i <= end)&&(start < end); i++)
{
cout << array[i] << " ";
}
cout << endl;
}
/******************************************************************************
* common swap function
******************************************************************************/
void swap_array_element(int * array, int src, int dst)
{
int tmp = 0;
tmp = array[src];
array[src] = array[dst];
array[dst] = tmp;
}
*后语:算法短论上的几种快排都好理解,除了最后一个采用尾递归优化的方案,对于尾递归,涉及到函数堆栈详细原理等,理解起来有一定难度。我认为上面尾递归快排还是会导致堆栈溢出,对"尾递归不会对堆栈有任何积累"理解的的XDJM可以指点下。
reference:
Introduction to algorithms(算法导论第三版)
http://blog.youkuaiyun.com/heyabo/article/details/8920738
尾递归的理解:
http://zh.wikipedia.org/wiki/%E5%B0%BE%E9%80%92%E5%BD%92
http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html
GCC支持优化尾递归,将尾递归优化成循环.
http://www.kuqin.com/developtool/20080525/8909.html
函数调用堆栈变化:
http://blog.chinaunix.net/uid-20718384-id-3418279.html
快速排序中的堆栈深度:
http://blog.youkuaiyun.com/zhanglei8893/article/details/6236792
函数式编程同命令式编程区别:
http://www.cnblogs.com/lisperl/archive/2011/11/21/2257360.html