Hot 0

Www Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes LJ's Blog

Www Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes

Jun 9

search Www p Themes c Quickwpthemes l Quickwpthemes r Quick C Szh % Www B%D Szh %Csearch% 7oitjht885 CE Themes % Www 4%7www.moxiu.comE9 Quick 9search% 7oitjht885 6aE1% Themes 0searchA Szh %search5Asia%20Uncensored%20Authorship%20Seed%20%D1%C7%D6%DE%D4%AD%B4%B4%C7%F888a9wp-color-tag-cloud% Www 88search%searchA Themes % Szh 69 Quickwpthemes %search0% Szh 7A0 7oitjht885 8searchFA Quick C Shared % Www 7tagsearchcsearcho Quickwpthemes d 7oitjht885 search Themes searchsearchJesse%20Jane%20Fuck%20Fantasy%20%28%C3%CE%CF%EB%B2%D9%C3%C0%C5%AE%29 Themes search Shared searchsearch1 Quickwpthemes

algorithm 0 Comments

 快速排序详细分析

 

注:REF[n]为参考资料,列于文章结尾。

看了编程珠玑Programming Perls第11章关于快速排序的讨论,发现自己长年用库函数,已经忘了快排怎么写。于是整理下思路和资料,把至今所了解的快排的方方面面记录与此。

 

纲要

  1. 算法描述
  2. 时间复杂度分析
  3. 具体实现细节
    1. 划分
      1. 选取枢纽元
        1. 固定位置
        2. 随机选取
        3. 三数取中
      2. 分割
        1. 单向扫描
        2. 双向扫描
        3. Hoare的双向扫描
        4. 改进的双向扫描
        5. 双向扫描的其他问题
    2. 分治
      1. 尾递归
  4. 参考文献

一、算法描述(Algorithm Description)

快速排序由C.A.R.Hoare于1962年提出,算法相当简单精炼,基本策略是随机分治。
首先选取一个枢纽元(pivot),然后将数据划分成左右两部分,左边的大于(或等于)枢纽元,右边的小于(或等于枢纽元),最后递归处理左右两部分。
分治算法一般分成三个部分:分解、解决以及合并。快排是就地排序,所以就不需要合并了。只需要划分(partition)和解决(递归)两个步骤。因为划分的结果决定递归的位置,所以Partition是整个算法的核心。

对数组S排序的形式化的描述如下(REF[1]):

  1. 如果S中的元素个数是0或1,则返回
  2. 取S中任意一元素v,称之为枢纽元
  3. 将S-{}(S中其余元素),划分成两个不相交的集合:S1={}|x<=v} 和 S2={}|x>=v}
  4. 返回{}

二、时间复杂度分析(Time Complexity)

快速排序最佳运行时间O(nlogn),最坏运行时间O(N^2),随机化以后期望运行时间O(nlogn),关于这些任何一本算法数据结构书上都有证明,就不写在这了,一下两点很重要:

  1. 选取枢纽元的不同, 决定了快排算法时间复杂度的数量级;
  2. 划分方法的划分方法总是O(n), 所以其具体实现的不同只影响算法时间复杂度的系数。

所以诉时间复杂度的分析都是围绕枢纽元的位置展开讨论的。

三、具体实现细节(Details of Implementaion)

1、划分(Partirion)

void QuickSort(T A[], int p, int q)
{}
}

划分又分成两个步骤:选取枢纽元按枢纽元将数组分成左右两部分

a.选取枢纽元(Pivot Selection)

固定位置

同样是为了方便,将选取枢纽元单独提出来成一个函数:select_pivot(T A[], int p, int q),该函数从A[p...q]中选取一个枢纽元并返回,且枢纽元放置在左端(A[p]的位置)。

int select_pivot(T A[], int p, int q)
{}

但是实际应用中,数据往往是部分有序的,如果仍用两端的元素最为枢纽元,则会产生很不好的划分,使算法退化成O(n^2)。所以要采用一些手段避免这种情况,我知道的有“随机选取法”和“三数取中法”。

随机选取
int select_pivot_random(T A[], int p, int q)
{}

 

其中randInt(p, q)随机返回[p, q]中的一个数,C/C++里可由stdlib.h中的rand函数模拟。

三数取中
即取三个元素的中间数作为枢纽元,一般是取左端、右断和中间三个数,也可以随机选取。(REF[1])
int select_pivot_median3(T A[], int p, int q)
{}

b.按枢纽元将数组分成左右两部分

虽然说分割方法只影响算法时间复杂度的系数,但是一个好系数也是比较重要的。这也就是为什么实际应用中宁愿选择可能退化成O(n^2)的快速排序,也不用稳定的堆排序(堆排序交换次数太多,导致系数很大)。

常见的分割方法有三种:

单向扫描
int partition(T A[], int p, int q)
{}
 swap(A[p], A[j]);
 return j;
}

双向扫描可以正常处理所有元素相同的情况,而且交换次数比单向扫描要少。

Hoare的双向扫描
int partition(T A[], int p, int q)
{} 
}

需要注意的是,返回值j并不是枢纽元的位置,但是仍然保证了A[p..j] <= A[j+1...q]。这种方法在效率上于双向扫描差别甚微,只是代码相对更为紧凑,并且用A[p]做哨兵元素减少了内层循环的一个if测试。

改进的双向扫描

kWww Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes LJ's Blogv i Quick Wp Themes Sites hWww Quickwpthemes Quick Wp Themes Szh Shared 7oitjht885 Quick Wp Themes LJ's Blogn o g Dating r Quick Wp Themes t t TIC+B5125+ A