第六章 堆排序习题

6.1堆

堆节点的高度:为该节点到叶节点最长简单路径上#边的个数#。(因此,根节点所在位置高度为0)

6.1-1 在高度为h的堆中,元素个数最多和最少分别是多少?

2h+112h

6.1-2 证明:含n个元素的堆的高度为 lgn

证明:1)利用上面式子反推
2)数学归纳法证明即可

6.1-3 证明:在最大堆的任一子树中,该子树所包含的最大元素在该子树的根结点上。

最大堆的性质是指除了根节点以外的所有节点i都要满足:A[PARENT(i)]>=A[i]
而根节点又是所有节点的祖先节点,故最大元素必然在该子树的根节点上。

6.1-4 假设一个最大堆的所有元素都不相同,那么该堆的最小元素应该位于哪里?

某个叶子节点上,但不确定是哪一个叶子节点。

6.1-5 一个已排好的数组是一个最小堆吗?

6.1-6 值为<23,17,14,6,13,10,1,5,7,12>的数组是一个最大堆吗?

不是,因为7是6的孩子节点,并且比6大。

6.1-7 证明:当用数组表示存储n个元素的堆时,叶节点下标分别是 n2+1,n2+2,,n

下式中n的下标表示孩子个数的节点,其中拥有一个孩子的节点最多只能有一个,这是由堆的性质决定的;而叶子节点有0个孩子节点,且必然排在最后。

n=n0+n1+n2n1=0n2+1=n0n1=1n2+n1=n0n0n2

6.2维护堆的性质

6.2-1 参照图6-2的方法,说明MAX-HEAPIFY(A,3)在数组A=<27,17,3,16,13,10,1,5,7,12,4,8,9,0>上的操作过程。

第六章 堆排序习题_第1张图片

当A.heap-size=14时,MAX-HEAPIFY(A,3)的执行过程。a)初始状态,在节点i=3处,A[3]违背了最大堆性质,因为它的值不大于它的孩子。在b)中,通过交换A[3]和A[6]的值,节点3恢复了最大堆的性质,但又导致了节点6违反了最大堆的性质。递归调用MAX-HEAPIFY(A,6),此时i=6。在c)中,通过交换A[6]和A[13]的值,节点6的最大堆的性质得到了恢复。再次递归调用MAX-HEAPIFY(A,13),此时不再有新的数据交换。

6.2-2 参考过程 MAX-HEAPIFY,写出能够维护相应最小堆的MIN-HEAPIFY(A,i)的伪代码,并比较MIN-HEAPIFY与MAX-HEAPIFY的运行时间。

MAX-HEAPIFY(A,i)
i= 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]
    MAX-HEAPIFY(A,smallest)

运行时间相同

6.2-3 当元素A[i]比其孩子的值都大时,调用MAX-HEAPIFY(A,i)会有什么结果。

一次运行完毕,不会递归。

6.2-4 当 i>A.heap-size/2 时,调用 MAX-HEAPIFY(A,i)会有什么结果?

说明i是叶子节点,也会直接结束,不会递归。

6.2-5 MAX-HEAPIFY的代码效率较高,但第10行中的递归调用可能例外,它可能使某些编译器产生低效的代码。请用循环去掉递归,重写 MAX-HEAPIFY代码。

MAX-HEAPIFY(A,i)
while(1)
{
i= LEFT(i)
r= RIGHT(i)
if l <= A.heap-size and A[l]>A[i]
    largest=l
else
    largest=i
if r <= A.heap-size and A[r]>A[largest]
    largest=r
if largest!=i
    exchange A[i] with A[largest]
else 
    return
i=largest
}

6.2-6 证明:对一个大小为n的堆,MAX-HEAPIFY的最坏情况运行时间为Ω(lgn)。(提示:对于n个节点的对,可以通过对每个几点设定恰当的值,使得从根节点到叶节点路径上的每个节点都会递归调用MAX-HEAPIFY.)

正如提示里所说,那么最坏的运行时间就是,树的深度。

6.3建堆

6.3-1 参照图6-3的方法,说明BUILD-MAX-HEAP在数组A=<5,3,17,10,84,19,6,22,9>上的操作过程。
第六章 堆排序习题_第2张图片

BUILD-MAX-HEAP的操作过程示意图,显示了在BUILD-MAX-HEAP的第三行调用MAX-HEAPIFY之前的数据结构。a)一个包括9个元素的二叉树。图中显示的是调用MAX-HEAPIFY(A,i)前,循环控制变量i指向结点4的情况。b)操作结果的数据结构。下一次迭代,循环控制变量i节点指向3.c)、d)BUILD-MAX-HEAP中执行for循环的后续迭代操作。需要注意的是,任何时候在某个节点调用MAX-HEAPIFY,该节点的两个子树都是最大堆。e)执行完BUILD-MAX-HEAP时的最大堆。

6.3-2 对于BUILD-MAX-HEAP中第2行的循环控制变量i来说,为什么我们要求它是从 A.length11A.length 递增呢?

因为要保证:任何时候在某个节点调用MAX-HEAPIFY,该节点的两个子树都是最大堆。这个条件。

6.3-3 证明:对于任一包含n个元素的堆中,至多有 n/2h+1 个高度为h的节点。

2h+1nn/2h+1

6.4堆排序算法

6.4-1 参照6-4的方法,说明HEAPSORT在数组A=<5,13,2,25,7,17,20,8,4>上的操作过程。
第六章 堆排序习题_第3张图片

6.4-2 试分析在使用下列循环不变量时,HEAPSORT的正确性:

在算法的第2~5行for循环每次迭代开始时,子数组A[1…i]是一个包含了数组A[1……n]中第i小元素的最大堆,而子数组A[i+1…n]包含了数组A[1…n]中已排序的n-i个最大元素?

i+1…n是取了n-i次最大堆顶部的结果,每次最大堆顶部的值一定是整个堆中最大的,所以子数组A[i+1…n]包含了数组已排序的n-i个最大元素,而剩下的元素一定比这些元素都小,所以包含了第i小元素。

6.4-3 对于一个按升序排列的包含n个元素的有序数组A来说,HEAPSORT的时间复杂度是多少?如果A是降序呢?

升序,最坏情况O(nlgn)
降序,不用进行第一次排序,θ(lg(n!))

6.4-4 证明:在最坏情况下,HEAPSORT的时间复杂度是Ω(nlgn)。

=hh=nlgnn2h+1+i=n1lgi

故时间富足度为nlgn

6.5 优先队列

6.5-1 试说明HEAP-EXTRACT-MAX在堆A=<15,13,9,5,12,8,7,4,0,6,2,1>上的操作过程。

第六章 堆排序习题_第4张图片

6.5-2 试说明MAX-HEAP-INSERT(A,10)在堆A=<15,13,9,5,12,8,7,4,0,6,2,1>上的操作过程。

第六章 堆排序习题_第5张图片

6.5-3 要求用最小堆实现最小优先队列,请写出HEAP-MINIMUM、HEAP-EXTRACT-MIN、HEAP-DECREASE-KEY和MIN-HEAP-INSERT的伪代码。

HEAP-MINIMUM
    return A[1]

HEAP-EXTRACT-MIN
    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
    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.heap-size=A.heap-size+1
    A[A.heap-size]=+∞
    HEAP-DECREASE-KEY(AA.heap-size,key)

6.5-4 在MAX-HEAP-INSERT的第2行,为什么我们要先把关键字设为-∞,然后又将其增加到所需的值呢?

依然保持大顶堆的性质。

6.5-5 试分析在使用下列循环不变量时,HEAP-INCREASE-KEY的正确性:
在算法的第4~6行while循环每次迭代开始的时候,子数组A[1…A.heap-size]要满足最大堆的性质。如果有违背,只有一个可能:A[i]大于A[PARENT(i)]。
这里,你可以假定在调用HEAP-INCREASE-KEY时,A[1…A.heap-size]是满足最大堆性质的。

初始化:调用HEAP-INCREASE-KEY时,A[1…A.heap-size]满足最大堆性质。
保持:若加入节点所在位置A[i]< A[PARENT(i)],那么加入节点并不需要调换,而其他节点也处于平衡状态。
终止:大顶堆并没改变。
所以如果有违背只有一个可能A[i] > A[PARENT(i)]

6.5-6 在HEAP-INCREASE-KEY的第5行的交换操作中,一般需要通过三次赋值来完成。想一想如何利用INSERTION-SORT内循环部分的思想,只用一次赋值就完成这一交换操作?

exchange A[i] with A[PARENT(i)]

while i>1 and A[PARENT(i)]A[i]=A[PARENT(i)]
    i=PARENT(i)
A[i]=key

6.5-7 试说明如何使用优先队列来实现一个先进先出队列,以及如何使用优先队列来实现栈。

栈:每次加入叶子节点,每次取叶子节点
队列:每次加入叶子节点,每次取根节点

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

HEAP-DELETE(A,i)
if i>n/2
    A[i+1~n]向前挪一位
    return
left=LEFT(A,i)
right=RIGHT(A,i)
if(A[left]>A[right])
    A[i]=A[left]
    HEAP-DELETE(A,left)
else
    A[i]=A[right]
    HEAP-DELETE(A,right)

6.5-9 请设计一个时间复杂度为O(nlgk)的算法,它能够将k个有序链表合并为一个有序链表,这里n是所有输入链表包含的总的个数。(提示:使用最小堆来完成k路归并)

思路:
1.取k个链表的第一个元素,构成一个最小堆。
2.取掉最小堆的根元素,放入result数组,若取出元素所在链表不为空,在其所在链表取出一个元素继续维持平衡。
3、若取出元素所在链表为空,堆的数量减1。
3.重复步骤2~3,直到所有链为空。

stdafx.h中

#define N 3             //规定链表的内容
#define col 8           //每个链表中的元素个数

StructChain.h中

struct  Chain
{
    int number;
    Chain * next;
};
class StructChain
{
public:
    StructChain();
    ~StructChain();
    Chain* CreateChain(int *arr,int length);
    void print(Chain * head);
};

StructChain.cpp中

Chain* StructChain::CreateChain(int *arr,int length)
{
    //根据传入的数组创建一个有头节点的数组
    Chain*head = (struct Chain *)malloc(sizeof(struct Chain));
    Chain *h;
    h=head;
    for (int i = 0; i < length; i++)
    {
        Chain* p = (struct Chain *)malloc(sizeof(struct Chain));
        p->number = arr[i];
        h->next = p;
        h = p;
    }
    h->next = NULL;
    return head;
}

//输出链表
void StructChain::print(Chain* head)
{
    Chain *p = head->next;
    while (p!=NULL)
    {
        printf("%d\t", p->number);
        p = p->next;
    }
}

Heap.h中

struct HeapNode
{
    int number;
    int fromChain;
};
class Heap
{
public:
    Heap();
    ~Heap();
    void CreateHeap(Chain *head[]);//创建一个堆
    int * GetResult(Chain *head[]);//得到链表合并的结果
    void CreateSmallHeap(int n);//构建小顶堆
    void Exchange(int i, int j);//调换两个节点
    int GetNode(Chain *head[]);//得到一个节点,并让堆继续保持平衡
    void MinHeapify(int i);     //重新归为最小堆
    void AddHeapify(int i);         //加入一个节点维持大顶堆的平衡
    int n;                      //堆节点的大小
    HeapNode Node[N];           //建立与链表数目相同的节点
    int result[N*col];
};

Heap.cpp中

//构建初始堆;
void Heap::CreateHeap(Chain *head[])
{
    n = N;
    for (int i = 0; i < N; i++)
    {
        Node[i].number = head[i]->next->number;
        head[i]->next = head[i]->next->next;
        Node[i].fromChain = i;
    }
}

int * Heap::GetResult(Chain * head[])
{
    //构建堆
    CreateHeap(head);
    //构建小顶堆
    CreateSmallHeap(N);
    //构造结果集
    for (int i = 0; i < N*col; i++)
    {
        result[i] = GetNode(head);
    }
    return result;
}

void Heap::CreateSmallHeap(int n)
{
    int pNodeNumber = n / 2;
    for (int i = (N/2-1); i >=0; i--)
    {
        if ((i + 1) * 2 - 11) * 2 - 1].number < Node[i].number)//左侧子节点与父节点比较
        {
            Exchange((i + 1) * 2 - 1, i);
        }
        if ( (i + 1) * 21) * 2].number.number)//右侧子节点与父节点比较
        {
            Exchange((i + 1) * 2, i);
        }
    }
}
//调换两个节点的位置
void Heap::Exchange(int i, int j)
{
    HeapNode tmp;
    tmp = Node[i];
    Node[i] = Node[j];
    Node[j] = tmp;
}

int Heap::GetNode(Chain * head[])
{
    HeapNode tmp = Node[0];
    //重新达到小顶堆的平衡状态
    Node[0] = Node[n - 1];
    MinHeapify(0);
    //将堆中放入新值
    if (head[tmp.fromChain]->next != NULL)
    {
        Node[n - 1].number = head[tmp.fromChain]->next->number;
        head[tmp.fromChain]->next = head[tmp.fromChain]->next->next;
        Node[n-1].fromChain = tmp.fromChain;
        //继续维持小顶堆的平衡 
        AddHeapify(n - 1);
    }
    else
    {
        n=n-1;
    }
    return tmp.number;
}

void Heap::MinHeapify(int i)
{
    if ((i + 1) * 2 - 1<(N-1) && Node[(i + 1) * 2 - 1].number < Node[i].number)//左侧子节点与父节点比较
    {
        Exchange((i + 1) * 2 - 1, i);
        MinHeapify((i + 1) * 2 - 1);
    }
    if ((i + 1) * 2<(N-1) &&Node[(i + 1) * 2].number.number)//右侧子节点与父节点比较
    {
        Exchange((i + 1) * 2, i);
        MinHeapify((i + 1) * 2);
    }
}

void Heap::AddHeapify(int i )
{
    for (; i >= 0; i--)
    {
        if ((i + 1) / 2 - 1 >= 0 && Node[(i + 1) / 2 - 1].number> Node[i].number)
        {
            Exchange((i + 1) / 2 - 1, i);
            i = (i + 1) / 2 - 1;
        }
        else
        {
            break;
        }
    }
}

main函数

int main()
{
    //规定给定的N个链表
    int a[][col] = { {1,3,5,7,9,11,13,17} ,{1,2,3,4,5,6,7,8},{2,4,6,8,10,12,14,16} };
    StructChain chain;
    //构造链表
    Chain *head[N];
    for (int i = 0; i < N; i++)
    {
        head[i] = chain.CreateChain(a[i], sizeof(a[0]) / sizeof(a[0][0]));
    }
    int *result;
    //传入head的值,构建最小堆
    Heap  heap;
    result = heap.GetResult(head);
    for (int i=0; i < N*col; i++)
    {
        printf("%d\t", result[i]);
    }
    return 0;
}

你可能感兴趣的:(算法导论第三版答案(自己写))