第10章 基本資料結構
10.1 棧和佇列
10.1-1
仿照圖10-1,畫圖表示依次執行操作PUSH(S,4)、PUSH(S,1)、PUSH(S,3)、POP(S)、PUSH(S,8)和POP(S)每一步的結果,棧S初始為空,儲存於陣列S[1..6]中。
10.1-2
在一個數組A[1..n]中實現兩個棧,使得當兩個棧的元素個數之和不為n時,兩者都不會發生上溢:第一個棧在陣列中從1向n增長,第二個棧在陣列中從n向1增長。PUSH和POP操作的執行時間為。
10.1-3
仿照圖10-2,畫圖表示依次執行操作ENQUEUE(Q,4)、ENQUEUE(Q,1)、ENQUEUE(Q,3)、DEQUEUE(Q)、ENQUEUE(Q,8)和DEQUEUE(Q)每一步的結果,佇列初始為空,儲存於陣列Q[1..6]中。
10.1-4
重寫ENQUEUE和DEQUEUE的程式碼,使之能處理佇列的下溢和上溢。
ENQUEUE(Q, x) if Q.head == Q.tail + 1 error "overflow" else Q[Q.tail] = x if Q.tail == Q.length Q.tail = 1 else Q.tail = Q.tail + 1 DEQUEUE(Q) if Q.head == Q.tail error "underflow" else x = Q[Q.head] if Q.head = Q.length Q.head = 1 else Q.head = Q.head + 1 return x
10.1-5
4個時間均為的過程,分別實現在雙端佇列的兩端插入和刪除元素的操作,該佇列是用一個數組實現的。
HEAD_ENQUEUE(Q, x) if Q.head == Q.tail + 1 error "overflow" else if Q.head == 1 Q.head = Q.length else Q.head = Q.head - 1 Q[Q.head] = x TAIL_ENQUEUE(Q, x) if Q.head == Q.tail + 1 error "overflow" else Q[Q.tail] = x if Q.tail == Q.length Q.tail = 1 else Q.tail = Q.tail + 1 HEAD_DEQUEUE(Q) if Q.head == Q.tail error "underflow" else x = Q[Q.head] if Q.head == Q.length Q.head = 1 else Q.head = Q.head + 1 return x TAIL_DEQUEUE(Q) if Q.head == Q.tail error "underflow" else if Q.tail == 1 Q.tail = Q.length else Q.tail = Q.tail - 1 x = Q[Q.tail] return x
10.1-6
用兩個棧實現一個佇列:先將兩個棧命名為A和B。入隊操作時如果棧B中有元素則先將所有現有元素壓入棧A中,再將新元素壓入棧A。出隊操作時如果棧A中有元素則先將所有現有元素壓入棧B中,再從棧B彈出元素。入隊和出隊操作的執行時間都是。
10.1-7
用兩個佇列實現一個棧:先將兩個佇列的狀態分為活躍的和不活躍的。入棧操作時將新元素插入到活躍的佇列。出棧操作時將所有現有元素除了隊尾元素從活躍的佇列插入到不活躍的佇列中,再刪除活躍佇列中的剩餘元素,然後兩個佇列轉換活躍狀態。入棧操作的執行時間是,出棧操作的執行時間都是。
10.2 連結串列
10.2-1
單鏈表上的動態集合操作INSERT能在時間內實現,DELETE操作不能。
10.2-2
用一個單鏈表L實現一個棧。操作PUSH和POP的執行時間仍為。
PUSH(S, x)
x.next = S.head
L.head = x
POP(S)
if S.head == NIL
error "underflow"
else x = S.head
S.head = x.next
return x.key
10.2-3
用一個單鏈表L實現一個佇列。操作ENQUEUE和DEQUEUE的執行時間仍為。
ENQUEUE(Q, x)
Q.tail.next = x
Q.tail = x
DEQUEUE(Q)
x = Q.head
Q.head = x.next
return x.key
10.2-4
在LIST-SEARCH'過程中的每一次迴圈迭代中省略對的檢查。
LIST-SEARCH'(L, k)
x = L.nil.next
L.nil.key = k
while x.key ≠ k
x = x.next
return x
10.2-5
使用單向迴圈連結串列實現字典操作INSERT、DELETE和SEARCH。
LIST-INSERT(L, x)
x.next = L.head
t = L.head.next
while t.next ≠ L.head
t = t.next
t.next = x
L.head = x
LIST-DELETE(L, x)
t = L.head
while t.next ≠ x
t = t.next
t.next = x.next
if L.head == x
L.head = x.next
return x.key
LIST-SEARCH(L, k)
x = L.head.next
while x ≠ L.head and x.key ≠ k
x = x.next
if x.key == k
return x
else return NIL
10.2-6
可以選用雙向迴圈連結串列,執行UNION操作時只需更新兩個雙向迴圈連結串列的首尾元素的next和prev指標,花費時間。
10.2-7
一個時間的非遞迴過程,實現對一個含n個元素的單鏈表的逆轉。除儲存連結串列本身所需的空間外,該過程只使用固定大小的儲存空間。
LIST-REVERSION(L)
x = L.head
y = NIL
while x ≠ NIL
z = x.next
x.next = y
y = x
x = z
L.head = y
10.3 指標和物件的實現
10.3-1
畫圖表示序列<13,4,8,19,5,11>,其儲存形式為多陣列表示的雙向連結串列。同樣畫出單陣列表示的形式。
10.3-2
對一組同構物件用單陣列表示法實現,寫出過程ALLOCATE-OBJECT和FREE-OBJECT。
ALLOCATE-OBJECT()
if free == NIL
error "out of space"
else x = free
free = x.next
return x
FREE-OBJECT(x)
x.next = free
free = x
10.3-3
在ALLOCATE-OBJECT和FREE-OBJECT過程的實現中,因為自由表只使用next屬性,該屬性只儲存連結串列中的next指標,所以不需要設定或重置物件的prev屬性。
10.3-4
實現過程ALLOCATE-OBJECT和FREE-OBJECT,使得該表示保持緊湊。
ALLOCATE-OBJECT()
if free == NIL
error "out of space"
else x = free
free = x.next
return x
FREE-OBJECT(x)
if x.prev ≠ NIL
x.prev.next = x.next
if x.next ≠ NIL
x.next.prev = x.prev
x.key = (free-1).key
x.prev = (free-1).prev
x.next = (free-1).next
x.prev.next = x
x.next.prev = x
(free-1).next = free
free = free - 1
10.3-5
給定連結串列L和自由表F,寫出一個過程COMPACTIFY-LIST(L,F),用來移動L中的元素使其佔用陣列中1,2,...,n中的位置,調整自由表F以保持其正確性,並且佔用陣列中n+1,n+2,...,m的位置。所寫的過程執行時間為,且只使用固定量的額外儲存空間。
COMPACTIFY-LIST(L, F)
x = L.head
y = free
z = NIL
while x ≠ NIL
// Find the element in L whose subscript is greater than n
if x ≤ n
x = x.next
else
// Find the element in F whose subscript is less than n
while y > n
z = y
y = y.next
// Swap x and y
temp = x
if z == NIL
free = x
else z.next = x
x.next = y.next
y.next = temp.next
y.prev = temp.prev
y.key = temp.key
temp.prev.next = y
temp.next.prev = y
// Reset the state of the variable to prepare for the next loop
z = x
x = y.next
y = z.next
10.4 有根樹的表示
10.4-1
畫出下列屬性表所示的二叉樹,其根結點下標為6。
下標 | key | left | right |
1 | 12 | 7 | 3 |
2 | 15 | 8 | NIL |
3 | 4 | 10 | NIL |
4 | 10 | 5 | 9 |
5 | 2 | NIL | NIL |
6 | 18 | 1 | 4 |
7 | 7 | NIL | NIL |
8 | 14 | 6 | 2 |
9 | 21 | NIL | NIL |
10 | 5 | NIL |
NIL |
10.4-2
給定一個n結點的二叉樹,寫出一個時間的遞迴過程,將該樹每個結點的關鍵字輸出。
TRAVERSE-RECURSION(x)
if x == NIL
return
else key = x.key
left = TRAVERSE(x.left)
right = TRAVERSE(x.right)
return key, left, right
10.4-3
給定一個n結點的二叉樹,寫出一個時間的非遞迴過程,將該樹每個結點的關鍵字輸出。可以使用一個棧作為輔助資料結構。
TRAVERSE-ITERATION(x)
let A[1..n] be a new array
i = 1
let S be a new stack
PUSH(S, x)
while !STACK-EMPTY(S)
x = POP(S)
A[i] = x.key
i = i + 1
if x.left ≠ NIL
PUSH(S, x.left)
if x.right ≠ NIL
PUSH(S, x.right)
return A
10.4-4
對於一個含n個結點的任意有根樹,寫出一個時間的過程,輸出其所有關鍵字。該樹以左孩子右兄弟表示法儲存。
TRAVERSE(x)
if x == NIL
return
else key = x.key
left-child = TRAVERSE(x.left-child)
right-sibling = TRAVERSE(x.right-sibling)
return key, left-child, right-sibling
思考題
10-1 連結串列間的比較
對於下表中的4種連結串列,給出所列的每種動態集合操作在最壞情況下的漸進執行時間。
未排序的單鏈表 | 已排序的單鏈表 | 未排序的雙向連結串列 | 已排序的雙向連結串列 | |
---|---|---|---|---|
SEARCH(L,k) | ||||
INSERT(L,x) | ||||
DELETE(L,x) | ||||
SUCCESSOR(L,x) | ||||
PREDECESSOR(L,x) | ||||
MINIMUM(L) | ||||
MAXIMUM(L) |
10-2 利用連結串列實現可合併堆
說明在下列前提下如何用連結串列實現可合併堆。使各操作儘可能高效。分析每個操作按動態集合規模的執行時間。
a.連結串列是已排序的。
MAKE-HEAP()
let L be a singly sorted linked list
L.head = NIL
return L
INSERT(L, x)
y = L.head
z = NIL
while y ≠ NIL and y.key < x.key
z = y
y = y.next
x.next = y
if z == NIL
L.head = x
else z.next = x
MINIMUM(L)
return L.head
EXTRACT-MIN(L)
x = L.head
L.head = x.next
return x
UNION(L1, L2)
L = MAKE-HEAP()
x1 = EXTRACT-MIN(L1)
x2 = EXTRACT-MIN(L2)
while x1 ≠ NIL or x2 ≠ NIL
if x1.key < x2.key
INSERT(L, x1)
x1 = EXTRACT-MIN(L1)
else if x1.key > x2.key
INSERT(L, x2)
x2 = EXTRACT-MIN(L2)
else INSERT(L, x1)
x1 = EXTRACT-MIN(L1)
x2 = EXTRACT-MIN(L2)
return L
b.連結串列是未排序的。
MAKE-HEAP()
let L be a singly sorted linked list
L.head = NIL
return L
INSERT(L, x)
x.next = L.head
L.head = x
MINIMUM(L)
x = L.head
min = +∞
while x ≠ NIL
if x.key < min
min = x.key
m = x
x = x.next
return m
EXTRACT-MIN(L)
x = L.head
y = NIL
min = +∞
while x ≠ NIL
if x.key < min
min = x.key
m = x
m-prev = y
y = x
x = x.next
m-prev.next = x.next
return m
UNION(L1, L2)
y = L2.head
while y ≠ NIL
x = L1.head
while x.next ≠ NIL and x.key ≠ y.key
x = x.next
if x.next == NIL and x.key ≠ y.key
x.next = y
y = y.next
x.next.next = NIL
else y = y.next
return L1
c.連結串列是未排序的,且待合併的動態集合是不相交的。
MAKE-HEAP()
let L be a singly sorted linked list
L.head = NIL
return L
INSERT(L, x)
x.next = L.head
L.head = x
MINIMUM(L)
x = L.head
min = +∞
while x ≠ NIL
if x.key < min
min = x.key
m = x
x = x.next
return m
EXTRACT-MIN(L)
x = L.head
y = NIL
min = +∞
while x ≠ NIL
if x.key < min
min = x.key
m = x
m-prev = y
y = x
x = x.next
m-prev.next = x.next
return m
UNION(L1, L2)
x = L1.head
while x.next ≠ NIL
x = x.next
x.next = L2.head
return L1
10-3 搜尋已排序的緊湊連結串列
a.證明:
- 因為呼叫RANDOM(1,n)所返回的整數序列在兩個演算法中是一樣的,所以COMPACT-LIST-SEARCH中第2~7行的迴圈體和COMPACT-LIST-SEARCH'中第2~7行的for迴圈一樣。因為COMPACT-LIST-SEARCH(L,n,k)中第2~8行的while迴圈經過了t次迭代,COMPACT-LIST-SEARCH'(L,n,k,t)中第2~7行的for迴圈也經歷了t次迭代,所以COMPACT-LIST-SEARCH'(L,n,k,t)會返回同樣的結果。
- 如果COMPACT-LIST-SEARCH是在第7行處返回了結果,則COMPACT-LIST-SEARCH'中的for迴圈經過了t次迭代後也會在第7行返回結果;如果COMPACT-LIST-SEARCH是在第10行或第11行處返回了結果,則COMPACT-LIST-SEARCH'中的for迴圈經過了t次迭代後,還會在while迴圈中經過若干次迭代才能返回結果。所以COMPACT-LIST-SEARCH'中的for迴圈和while迴圈的迭代次數之和至少為t。
因此,COMPACT-LIST-SEARCH'(L,n,k,t)會返回同樣的結果,且COMPACT-LIST-SEARCH'中的for迴圈和while迴圈的迭代次數之和至少為t。
b.證明:COMPACT-LIST-SEARCH'(L,n,k,t)中第2~7行的for迴圈的期望執行時間為,第8~9行的while迴圈的期望執行時間為,所以,COMPACT-LIST-SEARCH'(L,n,k,t)的期望執行時間為。
c.證明:根據公式(C.25),。
d.證明:。
e.證明:。
f.證明:因為,根據b小問的結論,COMPACT-LIST-SEARCH'(L,n,k,t)的期望執行時間為。
g.證明:因為COMPACT-LIST-SEARCH將COMPACT-LIST-SEARCH'的期望執行時間最小化,令,,易得:當時,取得最小值。因此,COMPACT-LIST-SEARCH的期望執行時間為。
h.證明:因為COMPACT-LIST-SEARCH只有當key[i]<key[RANDOM(1,n)]才會發生隨機跳躍,如果在一個所有元素都相同的連結串列中搜索另一個元素時,則不會發生隨機跳躍,只能一次次next遞增直至表尾。因此,當連結串列中包含重複的關鍵字時,隨機跳躍不一定能降低漸進時間。