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-size−1
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 操作。
解:
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) 操作。
解:
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-size−1
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-size−1
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. 不总是一样。例,
b. 证:因为程序调用
n
−
1
n-1
n−1 次
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=1∑nΘ(⌊lgi⌋)≥i=⌈n/2⌉∑nΘ(⌊lg⌈n/2⌉⌋)≥i=⌈n/2⌉∑nΘ(⌊lg(n/2)⌋)=i=⌈n/2⌉∑nΘ(⌊lgn−1⌋)≥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
⌊(i−2)/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
(i−1)∗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-size−1
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
r≤mn 个有限的数字。
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
(m−1)×n 或
m
×
(
n
−
1
)
m \times (n-1)
m×(n−1) 子问题来解决
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} ⎝⎜⎜⎛2816∞39∞∞412∞∞514∞∞⎠⎟⎟⎞
b. 对杨氏矩阵
Y
Y
Y 中的任意元素
Y
[
i
,
j
]
Y[i,j]
Y[i,j],
1
≤
i
≤
m
1 \le i \le m
1≤i≤m,
1
≤
j
≤
n
1\le j \le n
1≤j≤n,有
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(p−1)+Θ(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;
}