1. 程式人生 > >第10章 基本資料結構

第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操作的執行時間為O(1)

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個時間均為O(1)的過程,分別實現在雙端佇列的兩端插入和刪除元素的操作,該佇列是用一個數組實現的。

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彈出元素。入隊和出隊操作的執行時間都是O(n)

10.1-7

用兩個佇列實現一個棧:先將兩個佇列的狀態分為活躍的和不活躍的。入棧操作時將新元素插入到活躍的佇列。出棧操作時將所有現有元素除了隊尾元素從活躍的佇列插入到不活躍的佇列中,再刪除活躍佇列中的剩餘元素,然後兩個佇列轉換活躍狀態。入棧操作的執行時間是O(1),出棧操作的執行時間都是O(n)

10.2 連結串列

10.2-1

單鏈表上的動態集合操作INSERT能在O(1)時間內實現,DELETE操作不能。

10.2-2

用一個單鏈表L實現一個棧。操作PUSH和POP的執行時間仍為O(1)

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的執行時間仍為O(1)

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'過程中的每一次迴圈迭代中省略對x\neq L.nil的檢查。

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指標,花費O(1)時間。

10.2-7

一個\Theta (n)時間的非遞迴過程,實現對一個含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的位置。所寫的過程執行時間為\Theta (n),且只使用固定量的額外儲存空間。

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結點的二叉樹,寫出一個O(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結點的二叉樹,寫出一個O(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個結點的任意有根樹,寫出一個O(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) O(n) O(n) O(n) O(n)
INSERT(L,x) O(1) O(n) O(1) O(n)
DELETE(L,x) O(n) O(n) O(1) O(1)
SUCCESSOR(L,x) O(1) O(1) O(1) O(1)
PREDECESSOR(L,x) O(n) O(n) O(1) O(1)
MINIMUM(L) O(n) O(1) O(n) O(1)
MAXIMUM(L) O(n) O(n) O(n) O(n)

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迴圈的期望執行時間為O(t),第8~9行的while迴圈的期望執行時間為O(E[X_{t}]),所以,COMPACT-LIST-SEARCH'(L,n,k,t)的期望執行時間為O(t+E[X_{t}])

c.證明:根據公式(C.25),E[X_{t}]=\sum _{r=1}^{n}{Pr\{X_{t}\geqslant r\}}=\sum _{r=1}^{n}{(\frac{n-r}{n})^{t}}\leqslant \sum _{r=1}^{n}{(1-r/n)^{t}}

d.證明:\sum _{r=0}^{n-1}{r^{t}}\leqslant \int _{0}^{n}{r^{t}dr}=n^{t+1}/(t+1)

e.證明:E[X_{t}]\leqslant \sum _{r=1}^{n}{(1-r/n)^{t}}=\sum _{r=0}^{n-1}{(r/n)^{t}}=\frac{\sum _{r=0}^{n-1}{r^{t}}}{n^{t}}\leqslant n/(t+1)

f.證明:因為t+E[X_{t}]\leqslant t+n/(t+1)\leqslant t+n/t,根據b小問的結論,COMPACT-LIST-SEARCH'(L,n,k,t)的期望執行時間為O(t+n/t)

g.證明:因為COMPACT-LIST-SEARCH將COMPACT-LIST-SEARCH'的期望執行時間最小化,令f(t)=t+n/tf'(t)=1-n/t^{2},易得:當t=\sqrt{n}時,f(t)取得最小值2\sqrt{n}。因此,COMPACT-LIST-SEARCH的期望執行時間為O(\sqrt{n})

h.證明:因為COMPACT-LIST-SEARCH只有當key[i]<key[RANDOM(1,n)]才會發生隨機跳躍,如果在一個所有元素都相同的連結串列中搜索另一個元素時,則不會發生隨機跳躍,只能一次次next遞增直至表尾。因此,當連結串列中包含重複的關鍵字時,隨機跳躍不一定能降低漸進時間。