【算法导论】第6章 堆排序 (2)


Introduction to Algorithms - Third Edition
Part II. Sorting and Order Statistics
Chapter 6. Heapsort


6.5 优先级队列

优先级队列有两种形式:最大优先级队列和最小优先级队列。
本节内容关注如何实现最大优先级队列,它基于最大堆。

优先级队列(priority queue)是一种数据结构,用于维护一个集合 S S S 中的元素,每个元素都有一个关联值,称为(key)。最大优先级队列支持以下操作:

  • INSERT ( S , x ) \texttt{INSERT}(S, x) INSERT(S,x) 将元素 x x x 插入集合 S S S,等价于操作 S = S ∪ { x } S = S \cup \{x\} S=S{x}
  • MAXIMUM ( S ) \texttt{MAXIMUM}(S) MAXIMUM(S) 返回 S S S 中具有最大关键字的元素。
  • EXTRACT-MAX ( S ) \texttt{EXTRACT-MAX}(S) EXTRACT-MAX(S) 移除并返回 S S S 中具有最大关键字的元素。
  • INCREASE-KEY ( S , x , k ) \texttt{INCREASE-KEY}(S, x, k) INCREASE-KEY(S,x,k) 将元素 x x x 的键值增加到新值 k k k,该值至少与 x x x 的当前键值一样大。

最大优先级队列的应用:对分时计算机上的作业进行调度。
最大优先级队列跟踪记录要执行的作业及其相对优先级。
当一个作业完成或中断时,调度程序通过调用 EXTRACT-MAX \texttt{EXTRACT-MAX} EXTRACT-MAX 从待处理的作业中选择优先级最高的作业。调度程序可以随时通过调用 I N S E R T \tt INSERT INSERT 将新作业添加到队列中。

最小优先级队列支持操作 INSERT \texttt{INSERT} INSERT MINIMUM \texttt{MINIMUM} MINIMUM EXTRACT-MIN \texttt{EXTRACT-MIN} EXTRACT-MIN DECREASE-KEY \texttt{DECREASE-KEY} DECREASE-KEY

最小优先级队列的可应用于基于事件驱动的模拟器。
队列中的项目是要模拟的事件,每个事件都有一个关联的发生时间作为其键。
必须按照事件发生的时间顺序对事件进行模拟,因为一个事件的模拟可能会导致将来其他事件的模拟。
模拟程序在每个步骤都调用 EXTRACT-MIN \texttt{EXTRACT-MIN} EXTRACT-MIN 以选择要模拟的下一个事件。
当产生新事件时,模拟器通过调用 I N S E R T \tt INSERT INSERT 将它们插入最小优先队列中。

HEAP-MAXIMUM ( A ) \texttt{HEAP-MAXIMUM}(A) HEAP-MAXIMUM(A)
1  return A [ 1 ] A[1] A[1]

程序 HEAP-MAXIMUM \texttt{HEAP-MAXIMUM} HEAP-MAXIMUM 实现了 MAXIMUM \texttt{MAXIMUM} MAXIMUM 操作,运行时间为 Θ ( 1 ) \Theta(1) Θ(1)

HEAP-EXTRACT-MAX ( A ) \texttt{HEAP-EXTRACT-MAX}(A) HEAP-EXTRACT-MAX(A)
1 if A . h e a p - s i z e < 1 A.heap\text-size < 1 A.heap-size<1
2  error “heap underflow”
3 m a x = A [ 1 ] max = A[1] max=A[1]
4 A [ 1 ] = A [ A . h e a p - s i z e ] A[1] = A[A.heap\text-size] A[1]=A[A.heap-size]
5 A . h e a p - s i z e = A . h e a p - s i z e − 1 A.heap\text-size = A.heap\text-size - 1 A.heap-size=A.heap-size1
6 MAX-HEAPIFY ( A , 1 ) \texttt{MAX-HEAPIFY}(A, 1) MAX-HEAPIFY(A,1)
7 return m a x max max

程序 HEAP-EXTRACT-MAX \texttt{HEAP-EXTRACT-MAX} HEAP-EXTRACT-MAX 实现 EXTRACT-MAX \texttt{EXTRACT-MAX} EXTRACT-MAX 操作,运行时间为 O ( lg ⁡ n ) O(\lg n) O(lgn)

HEAP-INCREASE-KEY ( A , i , k e y ) \texttt{HEAP-INCREASE-KEY}(A, i, key) HEAP-INCREASE-KEY(A,i,key)
1 if k e y < A [ i ] key < A[i] key<A[i]
2  error “new key is smaller than current key”
3 A [ i ] = k e y A[i] = key A[i]=key
4 while i > 1 i>1 i>1 and A [ PARENT ( i ) ] < A [ i ] A[\texttt{PARENT}(i)] < A[i] A[PARENT(i)]<A[i]
5  exchange A [ i ] A[i] A[i] with A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)]
6   i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i)

程序 HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 实现 INCREASE-KEY \texttt{INCREASE-KEY} INCREASE-KEY 操作,运行时间为 O ( lg ⁡ n ) O(\lg n) O(lgn)。数组的下标 i i i 标识优先队列中想要增加键值的元素。

MAX-HEAP-INSERT ( A , k e y ) \texttt{MAX-HEAP-INSERT}(A, key) MAX-HEAP-INSERT(A,key)
1 A . h e a p - s i z e = A . h e a p - s i z e + 1 A.heap\text-size = A.heap\text-size + 1 A.heap-size=A.heap-size+1
2 A [ A . h e a p - s i z e ] = − ∞ A[A.heap\text-size] = -\infty A[A.heap-size]=
3 HEAP-INCREASE-KEY ( A , A . h e a p - s i z e , k e y ) \texttt{HEAP-INCREASE-KEY}(A, A.heap\text-size, key) HEAP-INCREASE-KEY(A,A.heap-size,key)

程序 MAX-HEAP-INSERT \texttt{MAX-HEAP-INSERT} MAX-HEAP-INSERT 实现 INSERT \texttt{INSERT} INSERT 操作,运行时间是 O ( lg ⁡ n ) O(\lg n) O(lgn)

综上,一个堆可以在 O ( lg ⁡ n ) O(\lg n) O(lgn) 时间内,支持规模为 n n n 的集合上的优先队列的任何操作。

C++ 实现优先级队列
#include <iostream>
using namespace std;

int heap_size;

int Parent(i) {
	return (i - 1) / 2;
}

void MaxHeapify(vector<int>& A, int i) {
	while (i < heap_size) {
		int left = 2 * i + 1;
		int right = left + 1;
		int largest = i;
		if (left < heap_size && A[left] > A[i])
			largest = left;
		if (right < heap_size && A[right] > A[largest])
			largest = right;
			
		if (largest == i)
			break;
		else
			i = largest;
	}
}

int HeapMaximum(vector<int>& A) {
	return A[0];
}

int HeapExtractMax(vector<int>& A) {
	if (heap_size < 1) {
		cerr << "heap underflow" << endl;
		return -1;
	}
	int max = A[0];
	heap_size -= 1;
	A[0] = A[heap_size - 1];
	MaxHeapify(A, 0);
	return max;
}

void HeapIncreaseKey(vector<int>& A, int i, int key) {
	if (key < A[i]) {
		cerr << "new key is smaller than current key" << endl;
		return;
	}
	A[i] = key;
	while (i > 0 && A[Parent(i)] < A[i]) {
		swap(A[i], A[Parent(i)]);
		i = Parent(i);
	}
}

void MaxHeapInsert(vector<int>& A, int key) {
	heap_size += 1;
	A[heap_size - 1] = INT_MIN;
	HeapIncreaseKey(A, heap_size - 1, key);
}
练习

6.5-1 图示在堆 A = < 15 , 13 , 9 , 5 , 12 , 8 , 7 , 4 , 0 , 6 , 2 , 1 > A = < 15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1 > A=<15,13,9,5,12,8,7,4,0,6,2,1> 上的 HEAP-EXTRACT-MAX \texttt{HEAP-EXTRACT-MAX} HEAP-EXTRACT-MAX 操作。
解:
HeapExtractMax

6.5-2 图示在堆 A = < 15 , 13 , 9 , 5 , 12 , 8 , 7 , 4 , 0 , 6 , 2 , 1 > A = < 15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1 > A=<15,13,9,5,12,8,7,4,0,6,2,1> 上的 MAX-HEAP-INSERT ( A , 10 ) \texttt{MAX-HEAP-INSERT}(A, 10) MAX-HEAP-INSERT(A,10) 操作。
解:
MaxHeapInsert

6.5-3 写出程序 HEAP-MINIMUM \texttt{HEAP-MINIMUM} HEAP-MINIMUM HEAP-EXTRACT-MIN \texttt{HEAP-EXTRACT-MIN} HEAP-EXTRACT-MIN HEAP-DECREASE-KEY \texttt{HEAP-DECREASE-KEY} HEAP-DECREASE-KEY MIN-HEAP-INSERT \texttt{MIN-HEAP-INSERT} MIN-HEAP-INSERT 的伪代码,使用最小堆实现最小优先队列。
解:
HEAP-MINIMUM ( A ) \texttt{HEAP-MINIMUM}(A) HEAP-MINIMUM(A)
return A [ 1 ] A[1] A[1]

HEAP-EXTRACT-MIN ( A ) \texttt{HEAP-EXTRACT-MIN}(A) HEAP-EXTRACT-MIN(A)
if A . h e a p - s i z e < 1 A.heap\text-size < 1 A.heap-size<1
error “heap underflow”
m i n = A [ 1 ] min = A[1] min=A[1]
A [ 1 ] = A [ A . h e a p - s i z e ] A[1] = A[A.heap\text-size] A[1]=A[A.heap-size]
A . h e a p - s i z e = A . h e a p - s i z e − 1 A.heap\text-size = A.heap\text-size - 1 A.heap-size=A.heap-size1
MIN-HEAPIFY ( A , 1 ) \texttt{MIN-HEAPIFY}(A, 1) MIN-HEAPIFY(A,1)
return m i n min min

HEAP-DECREASE-KEY ( A , i , k e y ) \texttt{HEAP-DECREASE-KEY}(A, i, key) HEAP-DECREASE-KEY(A,i,key)
if k e y > A [ i ] key > A[i] key>A[i]
error “new key is larger than current key”
A [ i ] = k e y A[i] = key A[i]=key
while i > 1 i>1 i>1 and A [ PARENT ( i ) ] > A [ i ] A[\texttt{PARENT}(i)] > A[i] A[PARENT(i)]>A[i]
 exchange A [ i ] A[i] A[i] with A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)]
i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i)

MIN-HEAP-INSERT ( A , k e y ) \texttt{MIN-HEAP-INSERT}(A, key) MIN-HEAP-INSERT(A,key)
A . h e a p - s i z e = A . h e a p - s i z e + 1 A.heap\text-size = A.heap\text-size + 1 A.heap-size=A.heap-size+1
A [ A . h e a p - s i z e ] = + ∞ A[A.heap\text-size] = +\infty A[A.heap-size]=+
HEAP-DECREASE-KEY ( A , A . h e a p - s i z e , k e y ) \texttt{HEAP-DECREASE-KEY}(A, A.heap\text-size, key) HEAP-DECREASE-KEY(A,A.heap-size,key)

6.5-4 当下一步要做的是将其节点的键增加到所需的值时,为什么还要在 MAX-HEAP-INSERT \texttt{MAX-HEAP-INSERT} MAX-HEAP-INSERT 的第2行中将插入节点的键设置为 − ∞ -\infty
解: 因为任何键值都大于 − ∞ -\infty ,这样,无论插入节点的键值是多少,都能跳过 HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 的第一步,将节点插入到最大堆中适当的位置。

6.5-5 使用下面的循环不变式论证 HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 的正确性:
在 4~6 行的 while 循环中,每次迭代开始时,子数组 A [ 1.. A . h e a p - s i z e ] A[1..A.heap\text-size] A[1..A.heap-size] 满足最大堆性质,除了一个可能的例外: A [ i ] A[i] A[i] 可能大于 A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)]
可以假设, HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 被调用时,子数组 A [ 1.. A . h e a p - s i z e ] A[1..A.heap\text-size] A[1..A.heap-size] 满足最大堆性质。
解: 初始化: A [ i ] A[i] A[i] 的值增加至 k e y key key,其他不变,所以,除了 A [ i ] A[i] A[i] 可能大于 A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)],子数组 A [ 1.. A . h e a p - s i z e ] A[1..A.heap\text-size] A[1..A.heap-size] 满足最大堆性质。
保持: 如果结点 i i i 的值大于其父结点的值,交换两者的值,根据循环不变式,此时以 PARENT ( i ) \texttt{PARENT}(i) PARENT(i) 结点为根的树是最大堆。 i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i),为下一次迭代重新建立循环不变式。
终止: i = 1 i=1 i=1 时,程序终止。此时, A [ i ] A[i] A[i] 是根结点,没有父结点,根据循环不变式,子数组 A [ 1.. A . h e a p - s i z e ] A[1..A.heap\text-size] A[1..A.heap-size] 满足最大堆性质, A [ 1 ] A[1] A[1] 是堆中的最大值。

6.5-6 HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 的第5行上的每个交换操作通常需要三次赋值。说明如何使用 INSERTION-SORT \texttt{INSERTION-SORT} INSERTION-SORT 内循环的思想将三个赋值减少到一个赋值。
解: HEAP-INCREASE-KEY ( A , i , k e y ) \texttt{HEAP-INCREASE-KEY}(A, i, key) HEAP-INCREASE-KEY(A,i,key)
if k e y < A [ i ] key < A[i] key<A[i]
error “new key is smaller than current key”
while i > 1 i>1 i>1 and A [ PARENT ( i ) ] < k e y A[\texttt{PARENT}(i)] < key A[PARENT(i)]<key
A [ i ] = A [ PARENT ( i ) ] A[i] = A[\texttt{PARENT}(i)] A[i]=A[PARENT(i)]
i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i)
A [ i ] = k e y A[i] = key A[i]=key

6.5-7 说明如何使用优先级队列实现一个先进先出队列。说明如何使用优先级队列实现一个栈。(队列和栈定义在章节 10.1。)
解: 将进入队列或栈的元素的时间戳作为 k e y key key 值,并将该值与相对应的元素绑定,实现先进先出队列使用最小优先级队列,实现栈使用最大优先级队列。

6.5-8 操作 HEAP-DELETE ( A , i ) \texttt{HEAP-DELETE}(A, i) HEAP-DELETE(A,i) 从堆 A A A 中删除结点 i i i 中的项。对含 n n n 个元素的最大堆,给出运行时间为 O ( lg ⁡ n ) O(\lg n) O(lgn) 的实现。
解: HEAP-DELETE ( A , i ) \texttt{HEAP-DELETE}(A, i) HEAP-DELETE(A,i)
A [ i ] = A [ h e a p - s i z e ] A[i] = A[heap\text-size] A[i]=A[heap-size]
h e a p - s i z e = h e a p - s i z e − 1 heap\text-size = heap\text-size - 1 heap-size=heap-size1
MAX-HEAPIFY ( A , i ) \texttt{MAX-HEAPIFY}(A, i) MAX-HEAPIFY(A,i)

6.5-9 给出一个时间为 O ( n lg ⁡ k ) O(n \lg k) O(nlgk) 的算法,将 k k k 个已排序链表合并为一个排序链表,其中 n n n 是所有输入链表中元素的总数目。(提示:使用一个最小堆作 k k k 路合并。)
解:

#include <iostream>
using namespace std;

//Definition for singly - linked list.
struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};
vector<ListNode*> A;	// A = {L1, L2, L3, ..., Lk}; 表示 k 个以排序列表的集合
int heap_size;

void MinHeapify(vector<ListNode*>& A, int i) {
	while (i < heap_size) {
		int left = (i << 1) + 1;
		int right = left + 1;
		int smallest = i;
		if (left < heap_size && A[left].val < A[i].val)
			smallest = left;
		if (right < heap_size && A[right].val < A[largest].val)
			smallest = right;

		if (smallest != i) {
			swap(A[i], A[smallest]);
			i = smallest;
		}
		else {
			break;
		}
	}
}

void BuildMinHeap(vector<ListNode*>& A) {
	heap_size = A.size();
	for (int i = A.size() / 2; i >= 0; --i) {
		MinHeapify(A, i);
	}
}

ListNode* MinHeapLinkMerge(int k) {
	BuildMinHeap(A);
	ListNode* head = A[0];
	ListNode* p = A[0];
	A[0] = A[0].next;
	if(A[0] == NULL) {
		A[0] = A[heap_size - 1];
		heap_size -= 1;
	}
	
	while(heap_size > 0) {
		MinHeapify(A, 0);
		p->next = A[0];
		p = p->next;
		A[0] = A[0].next;
		if(A[0] == NULL) {
			A[0] = A[heap_size - 1];
			heap_size -= 1;
		}
	}
	return head;
}

思考题

6-1 使用插入方法建堆
可以通过重复调用 MAX-HEAP-INSERT \texttt{MAX-HEAP-INSERT} MAX-HEAP-INSERT 向堆中插入元素,来建一个堆。考虑 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP 过程的以下变形:
BUILD-MAX-HEAP ′ ( A ) \texttt{BUILD-MAX-HEAP}'(A) BUILD-MAX-HEAP(A)
1 A . h e a p - s i z e = 1 A.heap\text-size = 1 A.heap-size=1
2 for i = 2 i = 2 i=2 to A . l e n g t h A.length A.length
3   MAX-HEAP-INSERT ( A , A [ i ] ) \texttt{MAX-HEAP-INSERT}(A, A[i]) MAX-HEAP-INSERT(A,A[i])
a. 当输入数组相同时,程序 BUILD-MAX-HEAP \texttt{BUILD-MAX-HEAP} BUILD-MAX-HEAP BUILD-MAX-HEAP ′ \texttt{BUILD-MAX-HEAP}' BUILD-MAX-HEAP 创建的堆是否总是一样的?如果是,请证明;否则请给出一个反例。
b. 证明:在最坏情况下, BUILD-MAX-HEAP ′ \texttt{BUILD-MAX-HEAP}' BUILD-MAX-HEAP 需要 Θ ( n lg ⁡ n ) \Theta(n \lg n) Θ(nlgn) 时间来建一个包含 n n n 个元素的堆。

解: a. 不总是一样。例,
BuildMaxHeap

b. 证:因为程序调用 n − 1 n-1 n1 MAX-HEAP-INSERT \texttt{MAX-HEAP-INSERT} MAX-HEAP-INSERT,每次花费时间为 O ( lg ⁡ n ) O(\lg n) O(lgn),得出程序运行时间的上界 O ( n lg ⁡ n ) O(n \lg n) O(nlgn)
对于下界 Ω ( n lg ⁡ n ) \Omega(n\lg n) Ω(nlgn),考虑情况:输入数组严格递增。每次对 MAX-HEAP-INSERT \texttt{MAX-HEAP-INSERT} MAX-HEAP-INSERT 的调用都会使 HEAP-INCREASE-KEY \texttt{HEAP-INCREASE-KEY} HEAP-INCREASE-KEY 一直上升到根。由于节点 i i i 的深度是 ⌊ lg ⁡ i ⌋ \lfloor \lg i \rfloor lgi 的,因此总时间为
∑ i = 1 n Θ ( ⌊ lg ⁡ i ⌋ ) ≥ ∑ i = ⌈ n / 2 ⌉ n Θ ( ⌊ lg ⁡ ⌈ n / 2 ⌉ ⌋ ) ≥ ∑ i = ⌈ n / 2 ⌉ n Θ ( ⌊ lg ⁡ ( n / 2 ) ⌋ ) = ∑ i = ⌈ n / 2 ⌉ n Θ ( ⌊ lg ⁡ n − 1 ⌋ ) ≥ n / 2 ⋅ Θ ( lg ⁡ n ) = Ω ( n lg ⁡ n ) \sum_{i=1}^{n} \Theta(\lfloor \lg i \rfloor) \ge \sum_{i=\lceil n/2 \rceil}^{n} \Theta(\lfloor \lg \lceil n/2 \rceil\rfloor) \\ \ge \sum_{i=\lceil n/2 \rceil}^{n} \Theta(\lfloor \lg (n/2)\rfloor) = \sum_{i=\lceil n/2 \rceil}^{n} \Theta(\lfloor \lg n - 1\rfloor) \\ \ge n/2 \cdot \Theta(\lg n) = \Omega(n \lg n) i=1nΘ(lgi)i=n/2nΘ(lgn/2)i=n/2nΘ(lg(n/2))=i=n/2nΘ(lgn1)n/2Θ(lgn)=Ω(nlgn)

综上,在最坏情况下, BUILD-MAX-HEAP ′ \texttt{BUILD-MAX-HEAP}' BUILD-MAX-HEAP 需要 Θ ( n lg ⁡ n ) \Theta(n \lg n) Θ(nlgn) 时间来建一个包含 n n n 个元素的堆。

6-2 d d d 叉堆的分析
d d d 叉堆与二叉堆类似,但(一个很可能的例外)非叶子节点有 d d d 个子节点,而不是 2 个。
a. 如何在一个数组中表示一个 d d d 叉堆?
b. n n n 个元素的堆的高度是多少?用 n n n d d d 表示。
c. 给出 d d d 叉最大堆的 EXTRACT-MAX \texttt{EXTRACT-MAX} EXTRACT-MAX 一个高效的实现。分析它的运行时间,用 n n n d d d 表示。
d. 给出 d d d 叉最大堆的 INSERT \texttt{INSERT} INSERT 一个高效的实现。分析它的运行时间,用 n n n d d d 表示。
e. 给出 d d d 叉最大堆的 INCREASE-KEY ( A , i , k ) \texttt{INCREASE-KEY}(A, i, k) INCREASE-KEY(A,i,k) 一个高效的实现,如果 k < A [ i ] k < A[i] k<A[i],表示出现错误,否则,设置 A [ i ] = k A[i] = k A[i]=k,然后相应更新 d d d 叉最大堆结构。分析它的运行时间,用 n n n d d d 表示。

解: a. A [ 1.. n ] A[1..n] A[1..n]
结点 i i i 的父结点:
PARENT ( i ) \texttt{PARENT}(i) PARENT(i)
return ⌊ ( i − 2 ) / d ⌋ + 1 \lfloor (i-2)/d \rfloor + 1 (i2)/d+1
结点 i i i 的第 x x x 个孩子结点:
CHILD ( i , x ) \texttt{CHILD}(i, x) CHILD(i,x)
return ( i − 1 ) ∗ d + x + 1 (i-1)*d+x+1 (i1)d+x+1

b. 高度: h = ⌊ log ⁡ d n ⌋ h = \lfloor \log_d n \rfloor h=logdn

c. D-ARY-HEAP-EXTRACT-MAX ( A ) \texttt{D-ARY-HEAP-EXTRACT-MAX}(A) D-ARY-HEAP-EXTRACT-MAX(A)
if A . h e a p - s i z e < 1 A.heap\text-size < 1 A.heap-size<1
error “heap underflow”
m a x = A [ 1 ] max = A[1] max=A[1]
A [ 1 ] = A [ A . h e a p - s i z e ] A[1] = A[A.heap\text-size] A[1]=A[A.heap-size]
A . h e a p - s i z e = A . h e a p - s i z e − 1 A.heap\text-size = A.heap\text-size - 1 A.heap-size=A.heap-size1
i = 1 i = 1 i=1
while i < A . h e a p - s i z e i < A.heap\text-size i<A.heap-size
l a r g e s t = i largest = i largest=i
for j = 1 j=1 j=1 to d d d
  if CHILD ( i , j ) ≤ A . h e a p - s i z e \texttt{CHILD}(i, j) \le A.heap\text-size CHILD(i,j)A.heap-size and A [ CHILD ( i , j ) ] > A [ l a r g e s t ] A[\texttt{CHILD}(i, j)] > A[largest] A[CHILD(i,j)]>A[largest]
    l a r g e s t = CHILD ( i , j ) largest = \texttt{CHILD}(i, j) largest=CHILD(i,j)
if i ≠ l a r g e s t i \ne largest i=largest
   i = l a r g e s t i = largest i=largest
else break
return m a x max max

运行时间: O ( h d ) = O ( d log ⁡ d n ) O(hd) = O(d \log_d n) O(hd)=O(dlogdn)

d. D-ARY-MAX-HEAP-INSERT ( A , k ) \texttt{D-ARY-MAX-HEAP-INSERT}(A, k) D-ARY-MAX-HEAP-INSERT(A,k)
A . h e a p - s i z e = A . h e a p - s i z e + 1 A.heap\text-size = A.heap\text-size + 1 A.heap-size=A.heap-size+1
A [ A . h e a p - s i z e ] = k A[A.heap\text-size] = k A[A.heap-size]=k
i = A . h e a p - s i z e i = A.heap\text-size i=A.heap-size
while i > 1 i>1 i>1 and A [ PARENT ( i ) ] < A [ i ] A[\texttt{PARENT}(i)] < A[i] A[PARENT(i)]<A[i]
 exchange A [ i ] A[i] A[i] with A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)]
i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i)

运行时间: log ⁡ d n \log_d n logdn

e. D-ARY-HEAP-INCREASE-KEY ( A , i , k e y ) \texttt{D-ARY-HEAP-INCREASE-KEY}(A, i, key) D-ARY-HEAP-INCREASE-KEY(A,i,key)
if k e y < A [ i ] key < A[i] key<A[i]
error “new key is smaller than current key”
A [ i ] = k e y A[i] = key A[i]=key
while i > 1 i>1 i>1 and A [ PARENT ( i ) ] < A [ i ] A[\texttt{PARENT}(i)] < A[i] A[PARENT(i)]<A[i]
 exchange A [ i ] A[i] A[i] with A [ PARENT ( i ) ] A[\texttt{PARENT}(i)] A[PARENT(i)]
i = PARENT ( i ) i = \texttt{PARENT}(i) i=PARENT(i)
运行时间: O ( log ⁡ d n ) O(\log_d n) O(logdn)

C++ 实现

#include <iostream>
using namespace std;

int heap_size;
int d;

int Parent(int i) {
	return (i - 1) / d;
}

int Child(int i, int x) {
	return (i - 1)*d + x;
}

void d_ary_MaxHeapify(vector<int>& A, int i) {
	while (i < heap_size) {
		int largest = i;
		for (int j = 0; j < d; ++j) {
			if (Child(i, j) < heap_size && A[Child(i, j)] > A[largest]) {
				largest = Child(i, j);
			}
		}
		if (largest == i)
			break;
		else
			i = largest;
	}
}

int d_ary_HeapExtractMax(vector<int>& A) {
	if (heap_size < 1) {
		cerr << "heap underflow" << endl;
		return -1;
	}
	int max = A[0];
	heap_size -= 1;
	A[0] = A[heap_size - 1];
	d_ary_MaxHeapify(A, 0);
	return max;
}

void d_ary_HeapIncreaseKey(vector<int>& A, int i, int key) {
	if (key < A[i]) {
		cerr << "new key is smaller than current key" << endl;
		return;
	}
	A[i] = key;
	while (i > 0 && A[Parent(i)] < A[i]) {
		swap(A[i], A[Parent(i)]);
		i = parent;
	}
}

void d_ary_MaxHeapInsert(vector<int>& A, int key) {
	heap_size += 1;
	A[heap_size - 1] = INT_MIN;
	d_ary_HeapIncreaseKey(A, heap_size - 1, key);
}

6-3 杨氏矩阵
一个 m × n m \times n m×n杨氏矩阵(Young tableaus)是一个矩阵,其中每一行数据都从左到右排序,每一列数据都从上到下排序。杨氏矩阵的有些数据项可能是 ∞ \infty ,表示不存在的元素。因此,杨氏矩阵可以存储 r ≤ m n r \le mn rmn 个有限的数字。
a. 画出一个 4 × 4 4 \times 4 4×4 杨氏矩阵,包含元素 { 9 , 16 , 3 , 2 , 4 , 8 , 5 , 14 , 12 } \{9, 16, 3, 2, 4, 8, 5, 14, 12\} {9,16,3,2,4,8,5,14,12}
b. 论证一个 m × n m \times n m×n 杨氏矩阵 Y Y Y 为空,如果 Y [ 1 , 1 ] = ∞ Y[1,1] = \infty Y[1,1]=。论证 Y Y Y 是满的(包含 m × n m \times n m×n 个元素),如果 Y [ m , n ] < ∞ Y[m,n] < \infty Y[m,n]<
c. 给出一个算法,在非空的 m × n m \times n m×n 的杨氏矩阵上实现 EXTRACT-MIN \texttt{EXTRACT-MIN} EXTRACT-MIN,运行时间为 O ( m + n ) O(m + n) O(m+n)。你的算法应该使用一个递归子程序,通过递归解决 ( m − 1 ) × n (m-1) \times n (m1)×n m × ( n − 1 ) m \times (n-1) m×(n1) 子问题来解决 m × n m \times n m×n 问题。(提示:考虑 MAX-HEAPIFY \texttt{MAX-HEAPIFY} MAX-HEAPIFY。)定义 T ( p ) T(p) T(p) 是在任何 m × n m \times n m×n 杨氏矩阵上的 EXTRACT-MIN \texttt{EXTRACT-MIN} EXTRACT-MIN 的最大运行时间,其中 p = m + n p = m + n p=m+n。给出并解决,界为 O ( m + n ) O(m+n) O(m+n) 时间的 T ( p ) T(p) T(p) 的递归式。
d. 说明如何在 O ( m + n ) O(m+n) O(m+n) 时间内,将一个新元素插入一个未满的 m × n m \times n m×n 杨氏矩阵。
e. 不使用其他排序算法作为子程序,说明如何使用 n × n n \times n n×n 杨氏矩阵在 O ( n 3 ) O(n^3) O(n3) 时间内,对 n 2 n^2 n2 个数字进行排序。
f. 给出一个 O ( m + n ) O(m+n) O(m+n) 时间的算法,确定一个给定的数字是否存在于一个给定的杨氏矩阵。

解: a. ( 2 3 4 5 8 9 12 14 16 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ) \begin{pmatrix} 2 & 3 & 4 & 5 \\ 8 & 9 & 12 & 14 \\ 16 & \infty & \infty & \infty \\ \infty & \infty & \infty & \infty \end{pmatrix} 281639412514

b. 对杨氏矩阵 Y Y Y 中的任意元素 Y [ i , j ] Y[i,j] Y[i,j] 1 ≤ i ≤ m 1 \le i \le m 1im 1 ≤ j ≤ n 1\le j \le n 1jn,有
Y [ i , j ] ≤ Y [ i + 1 , j ] Y[i, j] \le Y[i+1,j] Y[i,j]Y[i+1,j](若 Y [ i + 1 , j ] Y[i+1,j] Y[i+1,j] 存在) 且 Y [ i , j ] ≤ Y [ i , j + 1 ] Y[i,j] \le Y[i, j+1] Y[i,j]Y[i,j+1](若 Y [ i , j + 1 ] Y[i,j+1] Y[i,j+1] 存在)。

c.

int m, n;

void YoungKeep(vector<vector<int>>& Y, int i, int j) {
	int x = i, y = j;
	if (i + 1 < m && Y[i + 1][j] < Y[x][y]) {
		x = i + 1;
		y = j;
	}
	if (j + 1 < n && Y[i][j + 1] < Y[x][y]) {
		x = i;
		y = j + 1;
	}
	if (x != i || y != j) {
		swap(Y[i][j], Y[x][y]);
		YoungKeep(Y, x, y);
	}
}

int YoungExtractMin(vector<vector<int>>& Y) {
	int min = Y[0][0];
	Y[0][0] = INT_MAX;
	YoungKeep(Y, 0, 0);
	return min;
}

最大运行时间: T ( p ) = T ( p − 1 ) + Θ ( 1 ) T(p) = T(p-1) + \Theta(1) T(p)=T(p1)+Θ(1),解递归式,得 T ( p ) = Θ ( p ) T(p) = \Theta(p) T(p)=Θ(p)

d.

void YoungInsert(vector<vector<int>>& Y, int val) {
	Y[m - 1][n - 1] = val;
	int i = m - 1, j = n - 1;
	while (i - 1 >= 0 && Y[i - 1][j] > Y[i][j]) {
		swap(Y[i - 1][j], Y[i][j]);
		--i;
	}
	while (j - 1 >= 0 && Y[i][j - 1] > Y[i][j]) {
		swap(Y[i][j - 1], Y[i][j]);
		--j;
	}
}

e.

vector<int> YoungSort(vector<vector<int>>& Y) {
	vector<int> ans;
	vector<vector<int>> visited(n, vector<int>(n, 0));
	vector<pair<int, int>> vpoints;
	vpoints.push_back(make_pair(0, 0));
	visited[0][0] = 1;
	while (!vpoints.empty()) {
		int i = vpoints[0].first;
		int j = vpoints[0].second;
		int index = 0;
		for (int k = 1; k < vpoints.size(); ++k) {
			int a = vpoints[k].first;
			int b = vpoints[k].second;
			if (Y[a][b] < Y[i][j]) {
				i = a;
				j = b;
				index = k;
			}
		}
		ans.push_back(Y[i][j]);
		vpoints.erase(vpoints.begin() + index);
		if (i + 1 < n && visited[i + 1][j] == 0) {
			vpoints.push_back(make_pair(i + 1, j));
			visited[i + 1][j] = 1;
		}
		if (j + 1 < n && visited[i][j + 1] == 0) {
			vpoints.push_back(make_pair(i, j + 1));
			visited[i][j + 1] = 1;
		}
	}
	return ans;
}

f.

bool YoungFind(vector<vector<int>>& Y, int val) {
	int i = 0, j = n - 1;
	while (i < m && j >= 0) {
		if (Y[i][j] == val) {
			return true;
		}
		else if (Y[i][j] > val) {
			--j;
		}
		else {
			++i;
		}
	}
	return false;
}

【算法导论】目录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值