QuickSort 简介

一、快排的定义:

同归并排序一样,快排也使用分治法思想。

分解: 将数组A[p...r]分解成两个子数组A[p...q-1] 和A[q+1...r],使得第一个子数组元素均小于或等于A[q],第二个数组元素均大于或等于A[q].也包括计算索引q的值的过程.

解决: 递归调用快排对上面分解出来的两个子数组进行排序,直到两个数组元素个数都不多于1.

合并: 因为两个子数组均是有序的,所以合并不需要额外的操作,A[p...r]就是有序的.


下面是采用分治思想实现快排的伪代码(也称为递归版快排):

194141_KOWp_227203.png


从上面的伪代码可以知道,快排的关键在于PARTITION 函数上,PARTITION 函数的伪代码如下:

194205_zzsY_227203.png

划分(PARTITION)函数中直接用数组最后一个元素值作为"枢轴"元素来重新划分数组成两个子数组,将比"枢轴"元素值小的和比其大的分别放在两个子数组中。下图是一个数组一次划分过程的详细流程:

194238_FcH0_227203.png

函数最后两行主要用来交换"枢轴"元素和比"枢轴"元素大的最左边元素,也是两个子数组的边界位置元素,主要是把"枢轴"元素放在两个子数组中间。对应上图 h->i 变化中将8和4交换位置.


PARTITION运行时间为θ(n) 其中n=r-p+1;


二、快排的其他版本:

因为快排的关键函数式PARTITION 函数,为了优化PARTITION处理时间,可以对PARTITION进行优化,所以有了不同版本的快排算法:


1、快排的随机化版本:随机获取"枢轴"元素,关键在于随机算法的设计.

194309_3tw9_227203.png


2、霍尔快排:Hoare划分函数的伪代码如下:

194330_SeMH_227203.png


3、三者取中法获取"枢轴"元素,从数组起始,中间,末尾三个元素中获取中间值作为"枢轴"元素;

(略)

4、采用尾递归(tail recursion)优化快排堆栈深度,取消上面QUICKSORT中的第二个递归调用,修改后伪代码如下:

194401_3kMz_227203.png


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


转载于:https://my.oschina.net/amince/blog/300876

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值