算法导论学习(二)——排序和顺序统计量

总表:(来源:http://blog.csdn.net/zhangyifei521/article/details/50381728
这里写图片描述
希尔排序 最坏运行时间:O(n^1.5) 期望运行时间:O(nlg2n) 是原址排序
冒泡排序 最坏运行时间:O(n^2) 期望运行时间:O(n^2) 是原址排序
顺序统计量:一个n个数的集合的第i个顺序统计量就是集合中第i小的数。

排序方法是否稳定的简单判断方法:排序前后,值相同的元素的相对位置是否和排序前相同。
上述提到的集中排序算法,希尔排序,快速排序,堆排序都不是稳定排序。

一、堆排序
1 堆
(二叉)堆是一个数组,它可以被看成一个近似的完全二叉树(只有最后一层不满,最后一层的填充方式是从左到右)。如图所示,是以层序遍历的方式储存在数组中的。
这里写图片描述
对二叉堆的每一个元素而言,计算它父节点、左孩子、右孩子都很容易。使用计算机的左移右移操作即可。

PARENT(i)
    return floor(i/2)
LEFT(i)
    return 2i
RIGHT(i)
    return 2i+1

二叉堆可以分为两种形式:最大堆和最小堆。在这两种堆中,结点的值都要满足堆的性质。最大堆的节点的值不能比父节点大,最小堆节点的值不能比父节点小。在堆排序算法中,我们使用的是最大堆。最小堆通常用于构造优先队列。
定义一个堆中节点的高度是该节点到叶节点最长简单路径上边的数目。对结构上一些基本操作的运行时间至多与树的高度成正比。包含n个元素的堆,高度是O(lgn)。

堆的基本过程: (针对最大堆)
MAX-HEAPIFY过程:时间复杂度为O(lgn),是维护最大堆性质的关键,功能是:对某一个左右子树都满足最大堆性质而自身不满足最大堆性质的节点,让它逐级下降,直到满足最大堆性质。
BUILD-MAX-HEAP过程:具有线性时间复杂度,功能是:从无序的输入数组中构造一个最大堆。
HEAPSORT过程:时间复杂度为O(nlgn),功能是对一个数组进行原址排序。
MAX-HEAP-INSERTHEAP-EXTRACT-MAXHEAP-INCREASE-KEYHEAP-MAXIMUM过程:时间复杂度为O(lgn),功能是利用堆实现一个优先队列。

2 维护堆的性质
MAX-HEAPIFY:输入是一个数组和下标i。LEFT(i)和RIGHT(i)的二叉树都是最大堆,但是A[i]有可能小于其孩子,违背了最大堆的性质。该过程让A[i]的值在最大堆中逐级下降,从而使得以下标i为根节点的子树重新遵循最大堆的性质。

MAX-HEAPIFY(A,i)
    l=LEFT(i)
    r=RIGHT(i)
    if l<=A.heap.size and A[l]>A[i]
        largest=l
    else largest=i
    if i<=A.heap.size and A[r]>A[largest]
        largest=r
    if largest!=i
        exchange A[i] with A[largest] 
        //交换值,largest还是指示原来largest的位置
        MAX-HEAPIFY(A,largest)

3 建堆
可以用自底向上的方法建堆。对于有n个元素的数组(编号1~n),叶节点是第floor(n/2)+1到第n个。对其他节点自下而上地每个调用一次MAX-HEAPIFY就是用BUILD-MAX-HEAP建堆的过程。时间复杂度是O(n)

BUILD-MAX-HEAP(A)
    A.heap.size=A.length
    for i=floor(A.length/2) downto 1
        MAX-HEAPIFY(A,i)

这里写图片描述

也可以用插入的方法建堆,中心思想是:每次在当前堆的尾部添加一个节点,让该节点与它的父节点比较,如果新节点值更大,就让它和父节点交换位置,这样不断向上交换,直到它的父节点没有它小为止。它的时间复杂度是O(nlgn)

MAX-HEAP-INSERT(A)
    for A.heap.size=1 to A.length
        i=A.heap.size
        while i>1 and A[PARENT(i)]<A[i]
            exchange A[i] and A[PARENT(i)]
            i=PARENT(i) 

关于时间复杂度大小的推导,移步http://shmilyaw-hotmail-com.iteye.com/blog/1776612 ,笔者数学不行。

4 堆排序算法
我们已经知道,对最大堆而言,根节点A[1]是最大的节点。因此先把根节点和最小的元素交换,再将堆的大小减一,这样根节点就从堆中脱离开来,而新的堆不满足最大堆的性质(且只有根节点不满足),这时再调用前面提到的MAX-HEAPIFY使得新堆满足最大堆的性质,以此帮助我们找到数组中第二大的值(即新堆的根节点)。以此类推。

HEAPSORT(A)
    BUILD-MAX-HEAP(A)
    for i=A.length downto 2
        exchange A[1] with A[i]
        A.heap.size=A.heap.size-1
        MAX-HEAPIFY(A,1)

这里写图片描述
这里写图片描述

5 优先队列
优先队列(priority queue)是一种用来维护一组数组元素构成的集合S的数据结构,其中的每个元素都有一个相关的值,成为关键字(key),一个最大优先队列支持以下操作:
INSERT(S,x):把元素x插入集合S中,用MAX-HEAP-INSERT实现,时间复杂度O(lgn)
MAXIMUM(S):返回S中具有最大键字的元素,直接返回根节点即可,时间复杂度O(1)
EXTRACT-MAX(S):去掉并返回S中具有最大键字的元素,将根节点与数组的最后一个元素交换,执行一次MAX-HEAPIFY即可(其实相当于只进行BUILD-MAX-HEAP的第一次迭代),时间复杂度O(lgn)
INCREASE-KEY(S,x,k):将元素x的关键值增加到k(k>=x),中心思想与MAX-HEAP-INSERT的一次迭代类似,将被修改的元素与父节点比较,向上移动,直到父节点的值没有它小为止,时间复杂度O(lgn)

二、快速排序
对于包含n个数的输入数组来说,快速排序的最坏时间复杂度为O(n^2),但是通常快速排序是实际排序应用中最好的选择,它的期望时间复杂度是O(nlgn),而且其中隐含的常数因子很小。它的最好情况的时间复杂度为O(nlgn)
采用了分治的思想,选取一个主元(pivot element),让排在当前元素前的所有元素都比它小,让排在当前元素后的所有元素都比它大。
一次划分的伪代码如下:

PARTITION(A,p,r) //要划分的区域是A的第p到第r个元素
    x=A(r) //将第r个元素固定为主元
    i=p-1 //i指向的是最近一次经过移动的位置
    for j=p to r-1
        if A[j]<=x
            i=i+1
            exchange A[i] with A[j] 
            //将比主元小的元素移到前面
    exchange A[i+1] with A[r] //将主元移动到分界点
    return i+1

快排的伪代码为:

QUICKSORT(A,p,r)
    if p<r
        q=PARTITION(A,p,r)
        QUICKSORT(A,p,q-1)
        QUICKSORT(A,q+1,r)

这里写图片描述

快速排序有一个随机化版本,与之前每次都选取最后一个元素作为主元不同,随机化版本用随机抽样(random sampling)的方式生成主元,这样对输入数组的划分比较平衡。随机化版本的处理策略是,先生成一个随机位置,与最后一个元素交换,接下来就和前面的版本的处理方法一样了。

三、线性时间排序
之前提出的两种方法都是比较排序。接下来介绍的排序方法并不是比较排序。
1 计数排序
计数排序假设n个输入元素每一个都是在0到k区间内的一个整数,其中k为某个整数。计数排序的基本思想是,对每一个输入元素x,确定小于x的元素个数。

COUNTING-SORT(A,B,k)
    let C[0..k] be a new array
    for i=0 to k
        C[i]=0
    for j=1 to A.length
        C[A[j]]=C[A[j]]+1
    //统计每个值出现的次数
    for i=1 to k
        C[i]=C[i]+C[i-1]
    //统计比当前值小的元素有多少个
    for j=A.length downto 1
        B[C[A[j]]]=A[j]
        C[A[j]]=C[A[j]]-1

这里写图片描述
该方法需要两个数组,一个存放排序的输出,一个是临时空间。(其实不需要存放排序输出的数组也可以,有了a图的C,只要从大到小已从把C中数组下标对应的元素往A里填把A覆盖掉就行了)。
总的时间复杂度为O(k+n)。当k=O(n)时,时间复杂度为O(n)。
计数排序是稳定的

2 基数排序
基数排序按照最低有效位进行排序。如果需要排序的n个数是d维数,每一位有k个可能的取值,那么计数排序的时间复杂度为O(d(n+k))。尽管基数排序执行的循环轮数会比快速排序少,但是每一轮所耗费的时间长得多。
当主存空间宝贵时,倾向于快速排序这样的原址排序。

RADIX-SORT(A,d)
    for i=1 to d
        use a stable sort to sort array A on digit i
        //比如计数排序

这里写图片描述

3 桶排序
桶排序(bucket sort)假设输入数据服从均匀分布,平均情况下它的时间代价为O(n)。桶排序假设输入由一个随机过程产生,该过程将元素均匀、独立地分布在[0,1)区间上。
桶排序将[0,1)的区间划分为n个相同大小的自取件,称为
需要一个临时数组存放链表(即桶)。

BUCKET-SORT(A)
    n=A.length
    let B[0..n-1] be a new array
    for i=0 to n-1
        make B[i] an empty list
    for i=1 to n
        insert A[i] into list B[floor(nA[i])]
    for i=0 to n-1
        sort list B[i] with insertion sort
    concatenate the list B[0],B[1],...,B[n-1] together in order.

这里写图片描述

四、插入排序和希尔排序
插入排序:
这里写图片描述
参考资料:http://www.cnblogs.com/jingmoxukong/p/4303279.html
希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种威力加强版。(但是插入排序是稳定的,希尔排序是不稳定的。)
希尔排序的基本思想是:把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
这里写图片描述

五、归并排序
参考资料:http://blog.csdn.net/morewindows/article/details/6678165/
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。首先考虑下如何将将二个有序数列合并。只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
归并排序的一次迭代时间复杂度为O(lgn),是稳定的排序方式。最好,最坏,平均时间复杂度均为O(nlgn)。
这里写图片描述

六、中位数和顺序统计量
第i个顺序统计量(order statistic)是该集合中第i小的元素。在一个集合中,最小值是第1个顺序统计量,最大值是第n个顺序统计量。一个中位数是集合的重点元素,i=floor((n+1)/2)处是下中位数,i=ceil((n+1)/2)是上中位数。本文用下中位数。
对一个有n个元素的集合,需要比较n次可以找到最小值或最大值
如果要同时找到最小值和最大值,最多只需要比较3*floor(n/2)次。具体方法是:对输入元素成对处理,让它们相互比较,然后较小的和最小值比较,较大的和最大值比较,对每两个元素进行三次比较。
1 期望运行时间为线性的选择算法
用随机化版本的快速排序,但是每次只处理划分的一边。
2 最坏运行时间为线性的选择算法
修改快速排序中的PARTITION算法,将划分的主元作为输入参数。主元既不固定为最后一个元素,也不随机选择,而是由SELECT算法给出。
如果想要求第i小的元素,SELECT算法行为如下:
① 将输入数组的n个元素划分为floor(n/5)组,每组5个元素,最多有一组有剩下的n%5个元素组成;
② 寻找这ceil(n/5)组元素每一组的中位数:首先对每组元素进行插入排序,然后确定每组有序元素的中位数;
③ 对②中的中位数,再调用SELECT找出其中位数;
④ 利用修改过的PARTITION版本,按照SELECT找出的中位数的中位数x作为主元,对整个数组进行划分。
⑤ 如果主元的位置i==k,那么x就是我们要找的元素,如果i

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值