第6章 堆排序

6.1 堆

6.1-1

在高度为h的堆中,元素个数最多是2^{h+1}-1,最少是2^{h}

6.1-2

证明:2^{h}\leqslant n\leqslant 2^{h+1}-1\Rightarrow \log_{2}{(n+1)}-1\leqslant h\leqslant \log_{2}{n}\Rightarrow\log_{2}{n}-1<h\leqslant \log_{2}{n}\Rightarrow h=\left \lfloor \lg{n} \right \rfloor

6.1-3

证明:在最大堆中,某个结点的值至多与其父结点一样大。经过递推易得,结点的值小于等于它的任一祖先。所以,在最大堆的任一子树中,该子树所包含的最大元素在该子树的根结点上。

6.1-4

假设一个最大堆的所有元素都不相同,那么该堆的最小元素应该位于它的叶结点。

6.1-5

一个已排好序的数组是一个最小堆。

6.1-6

值为<23,17,14,6,13,10,1,5,7,12>的数组不是一个最大堆,因为第4个元素小于第9个元素。

6.1-7

证明:考虑下标为\left \lfloor n/2 \right \rfloor+1的结点的左孩子的下标:2(\left \lfloor n/2 \right \rfloor+1)>2\cdot \frac{n}{2}=n,所以下标为\left \lfloor n/2 \right \rfloor+1的结点是叶结点,则下标大于等于\left \lfloor n/2 \right \rfloor+1的结点都是叶结点。考虑下标为\left \lfloor n/2 \right \rfloor的结点的左孩子的下标:2(\left \lfloor n/2 \right \rfloor)>2(\frac{n}{2}-1)=n-2,所以下标为\left \lfloor n/2 \right \rfloor的结点不是叶结点,则下标小于等于\left \lfloor n/2 \right \rfloor的结点都不是叶结点。综上:当用数组表示存储n个元素的堆时,叶结点下标分别是\left \lfloor n/2 \right \rfloor+1\left \lfloor n/2 \right \rfloor+2,...,n。

6.2 维护堆的性质

6.2-1

6.2-2

MIN-HEAPIFY(A, i)
    l = LEFT(i)
    r = RIGHT(i)
    if l ≤ A.heap-size and A[l] < A[i]
        smallest = l
    else smallest = i
    if r ≤ A.heap-size and A[r] < A[smallest]
        smallest = r
    if smallest ≠ i
        exchange A[i] with A[smallest]
        MIN-HEAPIFY(A, smallest)

MIN-HEAPIFY与MAX-HEAPIFY的运行时间相等。

6.2-3

当元素A[i]比其孩子的值都大时,调用MAX-HEAPIFY(A,i)会在比较完元素A[i]和其孩子的大小之后结束,不会进行第二次递归调用。

6.2-4

因为i>A.heap\_size/2,所以LEFT(i),RIGHT(i)>A.heap\_size,调用MAX-HEAPIFY(A,i)会在计算完LEFT(i),RIGHT(i)之后结束,不会进行第二次递归调用。

6.2-5

用循环控制结构取代递归,重写MAX-HEAPIFY代码。

MAX-HEAPIFY(A, i)
    while i ≤ A.heap-size/2
        l = LEFT(i)
        r = RIGHT(i)
        if A[l] > A[i]
            largest = l
        else largest = i
        if A[r] > A[largest]
            largest = r
        if largest ≠ i
            exchange A[i] with A[largest]
            i = largest
        else break

6.2-6

证明:对于n个结点的堆,可以通过对每个结点设定恰当的值,使得从根结点到叶结点路径上的每个结点都会递归调用MAX-HEAPIFY。因此,MAX-HEAPIFY的最坏情况运行时间等于堆的高度,即为\Omega (\lg{n})

6.3 建堆

6.3-1

6.3-2

因为每次调用MAX-HEAPIFY(A,i)后,可能会导致A[PARENT(i)]<A[i],从而不符合最大堆性质,因此要求BUILD-MAX-HEAP中第2行的循环控制变量i是从\left \lfloor A.length/2 \right \rfloor到1递减。

6.3-3

证明:

当h=0,即叶结点的个数为\left \lceil n/2 \right \rceil=\left \lceil n/2^{0+1} \right \rceil

假设对于h=i-1,结论成立。即对于任一包含n个元素的堆中,至多有\left \lceil n/2^{i} \right \rceil个高度为i-1的结点。当h=i时,对于任一包含n个元素的堆,先将其叶结点全部剪去,得到的堆有n-\left \lceil n/2 \right \rceil=\left \lfloor n/2 \right \rfloor个结点。旧堆中高度为i的结点在新堆高度为i-1,根据假设,新堆中至多有\left \lceil \left \lfloor n/2 \right \rfloor/2^{i} \right \rceil\leqslant \left \lceil n/2^{i+1} \right \rceil个高度为i-1的结点,即旧堆中至多有\left \lceil n/2^{i+1} \right \rceil个高度为i的结点。

综上,对于任一包含n个元素的堆中,至多有\left \lceil n/2^{h+1} \right \rceil个高度为h的结点。

6.4 堆排序算法

6.4-1

6.4-2

初始化:在第一次循环迭代之前,i=n,子数组A[1..n]是一个包含了数组A[1..n]中第n小元素的最大堆,而子数组A[n+1..n]包含了数组A[1..n]中已排序的0个最大元素。

保持:因为每次循环都把根结点从子数组A[1..i]中取出加入到子数组A[i+1..n]中,而且MAX-HEAPIFY维护了子数组A[1..i]是一个最大堆的性质,所以子数组A[1..i]是一个包含了数组A[1..n]中第i小元素的最大堆,而子数组A[i+1..n]包含了数组A[1..n]中已排序的n-i个最大元素。

终止:过程终止时,i=1,子数组A[1]包含了数组A[1..n]中的最小元素,而子数组A[2..n]包含了数组A[1..n]中已排序的n-1个最大元素,所以数组A[1..n]已经全部有序。

6.4-3

对于一个按升序或降序排列的包含n个元素的有序数组A来说,HEAPSORT的时间复杂度都是O(n\lg{n})

6.4-4

证明:在最坏情况下,即对一个按升序排列的包含n个元素的有序数组A进行HEAPSORT,首先调用BUILD-MAX-HEAP的时间复杂度是O(n),而n-1次调用MAX-HEAPIFY,每次的时间为O(\lg{n}),所以HEAPSORT的时间复杂度是T(n)=O(n)+(n-1)\cdot O(\lg{n})=\Omega (n\lg{n})

6.5 优先队列

6.5-1

HEAP-EXTRACT-MAX先将堆A的大小和1比较,因堆A的大小大于等于1,故不触发错误。在将A[1]即堆中的最大值的值保存在变量max中后,将A[1]替换为A[A.heap-size]即堆中的最小值。再将堆A的大小减1,然后调用MAX-HEAPIFY维护最大堆的性质,最后返回max的值。

6.5-2

MAX-HEAP-INSERT(A,10)先将堆A的大小加1,然后将A[A.heap-size]即新增的元素设为-∞,最后调用HEAP-INCREASE-KEY(A,A.heap-size,10)将元素10交换至正确的位置。

6.5-3

用最小堆实现最小优先队列。

HEAP-MINIMUM(A)
    return A[1]
HEAP-EXTRACT-MIN(A)
    if A.heap-size < 1
        error "heap underflow"
    min = A[1]
    A[1] = A[A.heap-size]
    A.heap-size = A.heap-size - 1
    MIN-HEAPIFY(A, 1)
    return min
HEAP-DECREASE-KEY(A, i, key)
    if key > A[i]
        error "new key is larger than current key"
    A[i] = key
    while i > 1 and A[PARENT(i)] > A[i]
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)
MIN-HEAP-INSERT(A, key)
    A.heap-size = A.heap-size + 1
    A[A.heap-size] = +∞
    HEAP-DECREASE-KEY(A, A.heap-size, key)

6.5-4

因为任何实数值都比-∞大,把关键字设为-∞后,调用HEAP-INCREASE-KEY时就不会触发错误。

6.5-5

初始化:在第一次循环迭代开始前,将A[i]的值设为key,因为调用HEAP-INCREASE-KEY时,A[1..A.heap-size]是满足最大堆性质的,所以如果子数组A[1..A.heap-size]违背最大堆的性质,只有一个可能:A[i]大于A[PARENT(i)]。

保持:因为在每次循环迭代开始前,都先将A[i]和A[PARENT(i)]比较大小,如果A[PARENT(i)]<A[i],则交换A[i]和A[PARENT(i)]的值,然后将i的值设为PARENT(i),此时以结点i为根结点的子树满足最大堆性质。所以如果子数组A[1..A.heap-size]违背最大堆的性质,只有一个可能:A[i]大于A[PARENT(i)]。

终止:过程终止时,i≤1或者A[PARENT(i)]≥A[i]。如果i≤1,此时结点i已经没有父结点,所以子数组A[1..A.heap-size]满足最大堆的性质;如果A[PARENT(i)]≥A[i],此时以结点PARENT(i)为根结点的子树满足最大堆性质,因为结点PARENT(i)及其父结点本就满足最大堆性质,所以子数组A[1..A.heap-size]满足最大堆的性质。

6.5-6

在HEAP-INCREASE-KEY的第5行的交换操作中,利用INSERTION-SORT内循环部分的思想,只用一次赋值就完成这一交换操作。

HEAP-INCREASE-KEY(A, i, key)
    if key < A[i]
        error "new key is smaller than current key"
    while i > 1 and A[PARENT(i)] < key
        A[i] = A[PARENT(i)]
        i = PARENT(i)
    A[i] = key        

6.5-7

先按照加入的先后顺序给每个结点赋予一个优先级,给先加入的结点赋予低的优先级,给后加入的结点赋予高的优先级。

  1. 使用优先队列来实现队列:使用最小优先队列组织所有结点,每次有新的结点加入时,调用MIN-HEAP-INSERT;需要弹出结点时,调用HEAP-EXTRACT-MIN。
  2. 使用优先队列来实现栈:使用最大优先队列组织所有结点,每次有新的结点加入时,调用MAX-HEAP-INSERT;需要弹出结点时,调用HEAP-EXTRACT-MAX。

6.5-8

HEAP-DELETE(A,i)操作能够将结点i从堆A中删除。对于一个包含n个元素的堆,设计能够在O(\lg{n})时间内完成的HEAP-DELETE操作。

HEAP-DELETE(A, i)
    A[i] = A[A.heap-size]
    A.heap-size -= 1
    MAX-HEAPIFY(A, i)

6.5-9

一个时间复杂度为O(n\lg{k})的算法,它能够将k个有序链表合并为一个有序链表,这里n是所有输入链表包含的总的元素个数。

MERGE-ORDER-LIST(A, n, k)
    let R be a new min-heap
    i = 0
    for j = 1 to n
        i += 1
        i %= k
        if A[i].length ≥ 1
            MIN-HEAP-INSERT(R, A[i][1])
            A[i].REMOVE-HEAD()

思考题

6-1

a.当输入数据相同的时候,BUILD-MAX-HEAP和BUILD-MAX-HEAP'生成的堆不是总是一样。例如:当输入数据为<4,1,3,2,16,9,10,14,8,7>时,BUILD-MAX-HEAP和BUILD-MAX-HEAP'生成的堆分别如下左右图所示。

b.证明:在最坏情况下,即在按倒序排列的数组上调用BUILD-MAX-HEAP'时,每次调用MAX-HEAP-INSERT向堆中插入元素时,都需将其与其父结点交换直至根结点,在数组中第i个元素执行此操作的时间复杂度为\lg{i}。所以,在最坏情况下,调用BUILD-MAX-HEAP'建立一个包含n个元素的堆的时间复杂度是T=\sum _{i=2}^{n}{\lg{i}}=\lg{(n!)}=\Theta (n\lg{n})

6-2

a.在一个数组中表示一个d叉堆。

PARENT(i)
    return ⌊i/d⌋
CHILD(i, k)
    return di + k - 1

b.包含n个元素的d叉堆的高度是\left \lfloor \log_{d}{n} \right \rfloor

c.EXTRACT-MAX在d叉最大堆上的一个有效实现,它的时间复杂度为O(d\log_{d}{n})。因为除了时间复杂度为O(d\log_{d}{n})的MAX-HEAPIFY以外,它的其他操作都是常数阶的。

MAX-HEAPIFY(A, i)
    largest = i
    for j = 1 to d
        c = CHILD(i, j)
        if c ≤ A.heap-size and A[c] > A[largest]
            largest = c
    if largest ≠ i
        exchange A[i] with A[largest]
        MAX-HEAPIFY(A, largest)

EXTRACT-MAX(A)
    if A.heap-size < 1
        error "heap underflow"
    max = A[1]
    A[1] = A[A.heap-size]
    A.heap-size = A.heap-size - 1
    MAX-HEAPIFY(A, 1)
    return max

d.INSERT在d叉最大堆上的一个有效实现,它的时间复杂度为O(\log_{d}{n})

INSERT(A, key)
    A.heap-size = A.heap-size + 1
    A[A.heap-size] = -∞
    INCERASE-KEY(A, A.heap-size, key)

e.INCREASE-KEY(A,i,k)在d叉最大堆上的一个有效实现,它的时间复杂度为O(\log_{d}{n})

INCREASE-KEY(A, i, k)
    if k < A[i]
        error "new key is smaller than current key"
    A[i] = k
    while i > 1 and A[PARENT(i)] < A[i]
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)

6-3

a.包含元素为{9,16,3,2,4,8,5,14,12}的4\times 4Young氏矩阵。

b.证明: 对于一个m\times n的Young氏矩阵Y来说,\forall i\leqslant m,j\leqslant n,Y[i,j]\geqslant Y[i-1,j]\geqslant \cdots \geqslant Y[1,j]\geqslant Y[1,j-1]\geqslant \cdots \geqslant Y[1,1]。如果Y[1,1]=∞,则Y中元素都是∞即Y为空。\forall i\leqslant m,j\leqslant n,Y[i,j]\leqslant Y[i+1,j]\leqslant \cdots \leqslant Y[m,j]\leqslant Y[m,j+1]\leqslant \cdots \leqslant Y[m,n]。如果Y[m,n]<∞,则Y中所有元素都小于∞即Y为满。

c.m\times n Young氏矩阵上时间复杂度为O(m+n)的EXTRACT-MIN的算法实现。

MAX-HEAPIFY(Y, i, j)
    if j + 1 ≤ Y.column and Y[i, j + 1] < Y[i, j]
        smallest_x = i
        smallest_y = j + 1
    else smallest_x = i
        smallest_y = j
    if i + 1 ≤ Y.row and Y[i + 1, j] < Y[smallest_x, smallest_y]
        smallest_x = i + 1
        smallest_y = j
    if smallest_x ≠ i or smallest_y ≠ j
        exchange Y[i, j] with Y[smallest_x, smallest_y]
        MAX-HEAPIFY(Y, smallest_x, smallest_y)

EXTRACT-MIN(Y)
    if Y.row < 1 or Y.column < 1
        error "Young matrix underflow"
    min = Y[1, 1]
    Y[1, 1] = Y[Y.row, Y.column]
    Y[Y.row, Y.column] = ∞
    MAX-HEAPIFY(Y, 1, 1)
    return min

d.O(m+n)时间内,将一个新元素插入到一个未满的m\times n的Young 氏矩阵中。

INSERT(Y, key)
    i = Y.row
    j = Y.column
    Y[i, j] = key
    while i > 1 and j > 1
        if Y[i, j - 1] < Y[i, j]
            smallest_x = i
            smallest_y = j - 1
        else smallest_x = i
            smallest_y = j
        if Y[i - 1, j] < Y[smallest_x, smallest_y]
            smallest_x = i - 1
            smallest_y = j
        if smallest_x ≠ i or smallest_y ≠ j
            exchange Y[i, j] with Y[smallest_x, smallest_y]
            i = smallest_x
            j = smallest_y

e.先创建一个n\times n的Young 氏矩阵,将其所有元素初始化为∞。然后对n^{2}个数逐个调用INSERT插入到矩阵中即可。

f.一个时间复杂度为O(m+n)的算法,用来判断一个给定的数是否存储在m\times n的Young 氏矩阵中。

IS-IN-MATRIX(Y, key)
    i = 1
    j = 1
    while i ≤ A.row and Y[i, j] ≤ key
        if Y[i, j] == key
            return true
        i = i + 1
    i = i - 1
    j = j + 1
    while j ≤ A.column and Y[i, j] ≤ key
        if Y[i, j] == key
            return true
        j = j + 1
    return false

 

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、本书的内容 目前,市面上有关计算机算法的书很多,有些叙述严谨但不全面,另外一些则是容量很大但不够严谨。本书将叙述的严谨性以及内容的深度和广度有机地结合了起来。第1版推出后,即在世界范围内受到了广泛的欢迎,被各高等院校用作多种课程的教材和业界的标准参考资料。它深入浅出地介绍了大量的算法及相关的数据结构,以及用于解决一些复杂计算问题的高级策略(如动态规划、贪心算法、平摊分析等),重点在于算法的分析和设计。对于每一个专题,作者都试图提供目前最新的研究成果及样例解答,并通过清晰的图示来说明算法的执行过程。. 本书是原书的第2版,在第1版的基础之上增加了一些新的内容,涉及算法的作用、概率分析和随机化算法、线性规划,以及对第1版中详尽的、几乎涉及到每一小节的修订。这些修订看似细微,实际上非常重要。书中引入了“循环不变式”,并贯穿始终地用来证明算法的正确性。在不改动数学和分析重点的前提下,作者将第1版中的许多数学基础知识从第一部分移到了附录中。 二、本书的特点 本书在进行算法分析的过程中,保持了很好的数学严谨性。书中的分析和设计可以被具有各种水平的读者所理解。相对来说,每一都可以作为一个相对独立的单元来教授或学习。书中的算法以英语加伪代码的形式给出,只要有一点程序设计经验的人都能读懂,并可以用任何计算机语言(如C/C++和Java等)方便地实现。在书中,作者将算法的讨论集中在一些比较现代的例子上,它们来自分子生物学(如人类基因项目)、商业和工程等领域。每一小节通常以对相关历史素材的讨论结束,讨论了在每一算法领域的原创研究。 本书的特点可以概括为以下几个方面: 1.概念清晰,广度、深度兼顾。 本书收集了现代计算机常用的数据结构和算法,并作了系统而深入的介绍。对涉及的概念和背景知识都作了清晰的阐述,有关的定理给出了完整的证明。 2.“五个一”的描述方法。 本书以相当的深度介绍了许多常用的数据结构和有效的算法。编写上采用了“五个一”,即一介绍一个算法、一种设计技术、一个应用领域和一个相关话题。.. 3.图文并茂,可读性强。 书中的算法均以通俗易懂的语言进行说明,并采用了大量插图来说明算法是如何工作的,易于理解。 4.算法的“伪代码”形式简明实用。 书中的算法均以非常简明的“伪代码”形式来设计,可以很容易地把它转化为计算机程序,直接应用。 注重算法设计的效率,对所有的算法进行了仔细、精确的运行时间分析,有利于进一步改进算法。 三、本书的用法 本书对内容进行了精心的设计和安排,尽可能考虑到所有水平的读者。即使是初学计算机算法的人,也可以在本书中找到所需的材料。 每一都是独立的,读者只需将注意力集中到最感兴趣的节阅读。 1.适合作为教材或教学参考书。 本书兼顾通用性与系统性,覆盖了许多方面的内容。本书不但阐述通俗、严谨,而且提供了大量练习和思考题。针对每一节的内容,都给出了数量和难度不等的练习题。练习题用于考察对基本内容的掌握程度,思考题有一定的难度,需进行精心的研究,有时还通过思考题介绍一些新的知识。 前言回到顶部↑本书提供了对当代计算机算法研究的一个全面、综合性的介绍。书中给出了多个算法,并对它们进行了较为深入的分析,使得这些算法的设计和分析易于被各个层次的读者所理解。力求在不牺牲分析的深度和数学严密性的前提下,给出深入浅出的说明。. 书中每一都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。我们强调将算法的效率作为一种设计标准,对书中的所有算法,都给出了关于其运行时间的详细分析。 本书主要供本科生和研究生的算法或数据结构课程使用。因为书中讨论了算法设计中的工程问题及其数学性质,因此,本书也可以供专业技术人员自学之用。 本书是第2版。在这个版本里,我们对全书进行了更新。所做的改动从新增了若干,到个别语句的改写。 致使用本书的教师 本书的设计目标是全面、适用于多种用途。它可用于若干课程,从本科生的数据结构课程到研究生的算法课程。由于书中给出的内容比较多,只讲一学期一般讲不完,因此,教师们应该将本书看成是一种“缓存区”或“瑞典式自助餐”,从中挑选出能最好地支持自己希望教授的课程的内容。 教师们会发现,要围绕自己所需的各个节来组织课程是比较容易的。书中的各都是相对独立的,因此,你不必担心意想不到的或不必要的各之间的依赖关系。每一都是以节为单位,内容由易到难。如果将本书用于本科生的课程,可以选用每一的前面几节内容;在研究生课程中,则可以完整地讲授每一。 全书包含920多个练习题和140多个思考题。每一节结束时给出练习题,每一结束时给出一些
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值