《算法导论3rd第十章》栈队列和链表

前言

栈、队列和链表及有树根。这些都是最最基本的数据结构。

栈和队列

栈实现一种后进先出(LIFO)策略;队列实现一种先进先出(FIFO)策略

STACK-EMPTY(S)
    if S.top==0
        return TRUE
    else
        return FALSE

PUSH(S, x)
    S.top=S.top+1
    S[S.top]=x

POP(S)
    if STACK-EMPTY(S)
        error "underflow"
    else
        S.top=S.top-1
        return S[S.top+1]

在这里插入图片描述

队列

EUQUEUE(Q, x)
    Q[Q.tail]=x
    if Q.tail==Q.length
        Q.tail=1
    else
        Q.tail=Q.tail+1

DEQUEUE(Q)
    x=Q[Q.head]
    if Q.head==Q.length
        Q.head=1
    else
        Q.head=Q.head+1
        return x 

在这里插入图片描述

练习

在这里插入图片描述

1-1

PUSH(S,4) | 4
PUSH(S,1) | 4 1
PUSH(S,3) | 4 1 3
POP(S) | 4 1
PUSH(S,8) | 4 1 8
POP(S) | 4 1

1-2

  • 第一个栈以下标为[0]为栈底
  • 第二个栈以下标为[n-1]为栈底

1-3

ENQUEUE(Q,4) | 4
ENQUEUE(Q,1) | 4 1
ENQUEUE(Q,3) | 4 1 3
DEQUEUE(Q) | 1 3
ENQUEUE(Q,8) | 1 3 8
DEQUEUE(Q) | 3 8

1-4

QUEUE-EMPTY(Q)
    if Q.head == Q.tail
        return true
    else return false

QUEUE-FULL(Q)
    if Q.head == Q.tail + 1 or (Q.head == 1 and Q.tail == Q.length)
        return true
    else return false

ENQUEUE(Q, x)
    if QUEUE-FULL(Q)
        error "overflow"
    else
        Q[Q.tail] = x
        if Q.tail == Q.length
            Q.tail = 1
        else Q.tail = Q.tail + 1

DEQUEUE(Q)
    if QUEUE-EMPTY(Q)
        error "underflow"
    else
        x = Q[Q.head]
        if Q.head == Q.length
            Q.head = 1
        else Q.head = Q.head + 1
        return x                

1-5

HEAD-ENQUEUE(Q, x)
    if QUEUE-FULL(Q)
        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 QUEUE-FULL(Q)
        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 QUEUE-EMPTY(Q)
        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 QUEUE-EMPTY(Q)
        error "underflow"
    else
        if Q.tail == 1
            Q.tail = Q.length
        else Q.tail = Q.tail - 1
        x = Q[Q.tail]
        return x   

1-6

  1. 设第一个栈a,栈底为x,栈顶为y
  2. 即第二个栈b,栈底为y,栈顶为x
  3. ENQUEUE a.y++ 即对应的b栈y变化
  4. DEQUEUE b.x++ 即对应的a栈x变化

1-7

  • 设第一个队列a,队列入口为x,队列出口为y
  • 即第二个队列b,队列入口为y,队列出口为x
  • push a.x-- 即对应的b队列x变化
  • pop b.y-- 即对应的a队列y变化

链表

在链表这种数据结构中,各对象按线性顺序排序。链表与数组不同,数组的线性序由数组下标决定,而链表中的顺序是各对象中的指针所决定的。链表可用来简单而灵活表示动态集合。

LIST-SEARCH(L, k)
    x=L.head
    while x!=null and x.key!=k
        x=x.next
    return x

LIST-INSERT(L, x)
    x.next=L.head
    if L.head!=null
        L.head.prev=x
    L.head=x
    x.prev=null

LIST-DELETE(L, x)
    if x.prev!=null
        x.prev.next=x.next
    else
        L.head=x.next
    if x.next!=null
        x.next.prev=x.prev

在这里插入图片描述

哨兵

哨兵(sentinel)是一个哑对象,其作用是简化边界条件的处理。LIST-DELETE的代码可以更简单些

LIST-DELETE(L, x)
    x.prev.next=x.next
    x.next.prev=x.prev

在这里插入图片描述

练习

在这里插入图片描述

2-1

  • 插入操作:可以。
  • 删除操作:如果需要删除指定的数据,那么需要查询操作在O(n)时间内找出这个值,然后再删除。如果按顺序删除,则只需O(1)时间,所以最坏情况下是O(n).

2-2

STACK-EMPTY(L)
    if L.head == NIL
        return true
    else return false

PUSH(L, x)
    x.next = L.head
    L.head = x

POP(L)
    if STACK-EMPTY(L)
        error "underflow"
    else
        x = L.head
        L.head = L.head.next
        return x

2-3

QUEUE-EMPTY(L)
    if L.head == NIL
        return true
    else return false


ENQUEUE(L, x)
    if QUEUE-EMPTY(L)
        L.head = x
    else L.tail.next = x
    L.tail = x
    x.next = NIL

DEQUEUE(L)
    if QUEUE-EMPTY(L)
        error "underflow"
    else
        x = L.head
        if L.head == L.tail
            L.tail = NIL
        L.head = L.head.next
        return x

2-4

LIST-SEARCH'(L, k)
    x = L.nil.next
    L.nil.key = k
    while x.key != k
        x = x.next
    return x

2-5

LIST-INSERT''(L, x)
    x.next = L.nil.next
    L.nil.next = x

LIST-DELETE''(L, x)
    prev = L.nil
    while prev.next != x
        if prev.next == L.nil
            error "element not exist"
        prev = prev.next
    prev.next = x.next

LIST-SEARCH''(L, k)
    x = L.nil.next
    while x != L.nil and x.key != k
        x = x.next
    return x

2-6

双向(环形)链表

LIST-UNION(L[1], L[2])
    L[2].nil.next.prev = L[1].nil.prev
    L[1].nil.prev.next = L[2].nil.next
    L[2].nil.prev.next = L[1].nil
    L[1].nil.prev = L[2].nil.prev

2-7

# p[1]-[4] 是临时变量
LIST-REVERSE(L)
    p[1] = NIL
    p[2] = L.head
    while p[2] != NIL
        p[3] = p[2].next
        p[2].next = p[1]
        p[1] = p[2]
        p[2] = p[3]
    L.head = p[1]

2-8

由题得,根据 s.np 已知尾指示可求出头指针, 已知头指示可求出尾指针

LIST-SEARCH(L, k)
    prev = NIL
    x = L.head
    while x != NIL and x.key != k
        next = prev XOR x.np
        prev = x
        x = next
    return x

LIST-INSERT(L, x)
    x.np = NIL XOR L.tail
    if L.tail != NIL
        L.tail.np = (L.tail.np XOR NIL) XOR x   // tail.prev XOR x
    if L.head == NIL
        L.head = x
    L.tail = x


LIST-DELETE(L, x)
    y = L.head
    prev = NIL
    while y != NIL
        next = prev XOR y.np
        if y != x
            prev = y
            y = next
        else
            if prev != NIL
                prev.np = (prev.np XOR y) XOR next  // prev.prev XOR next
            else L.head = next
            if next != NIL
                next.np = prev XOR (y XOR next.np)  // prev XOR next.next
            else L.tail = prev


LIST-DELETE(L, x)
    y = L.head
    prev = NIL
    while y != NIL
        next = prev XOR y.np
        if y != x
            prev = y
            y = next
        else
            if prev != NIL
                prev.np = (prev.np XOR y) XOR next  // prev.prev XOR next
            else L.head = next
            if next != NIL
                next.np = prev XOR (y XOR next.np)  // prev XOR next.next
            else L.tail = prev

LIST-REVERSE(L)
    tmp = L.head
    L.head = L.tail
    L.tail = tmp

指针和对象的实现

如果所用的语言或者环境不支持指针和对象,那我们该怎么用数组来将其转化呢?实质上可以将这个问题的本质转化为数组和链表这两种数据结构的转换,准确来说,是将链表表示的数据用数组表示。

对象的多维数组表示

对于给定的数组下标x,key[x],next[x]和prev[x]就共同表示链表中的一个对象
在这里插入图片描述

对象单数组表示

子数组A[j…k],对象的每一个域对应着0到k-j的偏移量,假设key,next,pre占位相同都为1,则key,next和prev对应的偏移量为0,1和2.
在这里插入图片描述

分配和释放对象

基于“对象的多维数组表示“,我们把自由对象保存在一个单链表中,称为自由表(free list)。自由表只使用next数组,该数组只存储表中的next指针。自由表的表头保存在全局变量free中。
在这里插入图片描述
自由表类似于一个栈:下一个被分配的对象就是最后被释放的那个。

练习

3-1 请画出序列<13,4,8,19,5,11>存储在以多重数组表示的爽链表中的形式。另画出在单数组表示下的形式

在这里插入图片描述

3-2 对一组用单数组表示的同构对象,写出其过程ALLOCATE-OBJECT和FREE-OBJECT。

ALLOCATE-OBJECT()
    if free == NIL
        error "out of space"
    else x = free
        free = A[x + 1]
        return x

FREE-OBJECT(x)
    A[x + 1] = free
    free = x

3-3 在ALLOCATE-OBJECT和FREE-OBJEC过程的实现中,为什么不需要设置重置对象的prev属性呢?

在自由表中没使用到对象的pre,所以不用担心pre没设置,对正常使用的对象产生影响

3-4 我们往往希望双向链表的所有元素在存储器中保持紧凑,例如,在多数组表示中占用前m个下标位置.(在页式虚拟存储的计算环境下,即为这种情况。)假设除指向链表本身的指针外没有其他指针指向该链表元素,试说明如何实现过程ALLOCATE-OBJECT和FREE-OBJECT,使得该表示保持紧凑。(提示,使用栈的数组实现)。

# 紧凑型,当申请空间时,只经从自由表中移出头节点就行
ALLOCATE-OBJECT()
    if STACK-EMPTY(F)
        error "out of space"
    else x = POP(F)
        return x

# 紧凑型,当释放空间时,我们需要将,栈头外面的空间和所以释放对象空间交换位置
# 这样自由表就可以紧凑的扩充,而不影响正在使用的数据
FREE-OBJECT(x)
    p = F.top - 1
    p.prev.next = x
    p.next.prev = x
    x.key = p.key
    x.prev = p.prev
    x.next = p.next
    PUSH(F, p)

3-5 设L是一个长度为n的双向链表,存储于长度为m的数组key,prev和next中。假设这些数组由维护双链自由表F的两个过程ALLOCATE-OBJECT和FREE-OBJECT进行管理。又假设m个元素中,恰有n个元素在链表L上,m-n个在自由表上。给定链表L和自由表F,试写出一个过程COMPACTIFY-LIST(L,F),用来移动L的元素使其占用数组中1,2,。。。n的位置,调整自由表F以保持其正确性,并且占用数组中n+1,n+2…,m的位置。要求缩写的过程运行时间应为θ(n),且只使用固定量的额外存储空间

COMPACTIFY-LIST(L, F)
    TRANSPOSE(A[L.head], A[1])
    if F.head == 1
        F.head = L.head
    L.head = 1
    l = A[L.head].next
    i = 2
    while l != NIL
        TRANSPOSE(A[l], A[i])
        if F == i
            F = l
        l = A[l].next
        i = i + 1

TRANSPOSE(a, b)
    SWAP(a.prev.next, b.prev.next)
    SWAP(a.prev, b.prev)
    SWAP(a.next.prev, b.next.prev)
    SWAP(a.next, b.next)

有根树的表示

链表的表示方法可以推广至任意的同构的数据结构上。如有根树(有向无环图)。

二叉树

用域 p,left,right 来存入指向二叉树T的 父亲,左孩子,右孩子的指针
在这里插入图片描述

分支数无限制有根数

如果将分支数扩展的任意树k,用二叉树的结构管理方式比较难。可以用左孩子右兄弟表示法:

  1. x.left-child指向结点x最左边的孩子结点;
  2. x.right-sibling指向x右侧相邻的兄弟结点
    在这里插入图片描述
    如果x没有孩子结点,则x.left-child=null,如果结点x是其父结点的最右孩子,则x.right-sibling=null。

练习

4-1画出下列属性表所示的二叉树,其根节点下标为6

在这里插入图片描述

4-2 给定一个n结点的二叉树,写出一个O(n)时间的递归过程,将该树每个结点的关键字输出

PRINT-BINARY-TREE(T)
    x = T.root
    if x != NIL
        PRINT-BINARY-TREE(x.left)
        print x.key
        PRINT-BINARY-TREE(x.right)

4-3给定一个n结点的二叉树,写出一个O(n)时间的非递归过程,将该树每个结点的关键字输出。可以使用一个栈作为辅助数据结构

PRINT-BINARY-TREE(T, S)
    PUSH(S, T.root)
    while !STACK-EMPTY(S)
        x = S[S.top]
        while x != NIL      // store all nodes on the path towards the leftmost leaf
            PUSH(S, x.left)
            x = S[S.top]
        POP(S)              // S has NIL on its top, so pop it
        if !STACK-EMPTY(S)  // print this nodes, leap to its in-order successor
            x = POP(S)
            print x.key
            PUSH(S, x.right)

4-4 给定一个n结点的任意有根树,写出一个O(n)时间的过程,将该树每个结点的关键字输出,该树以左孩子右兄弟表示法存储。

PRINT-LCRS-TREE(T)
    x = T.root
    if x != NIL
        print x.key
        lc = x.left-child
        if lc != NIL
            PRINT-LCRS-TREE(lc)
            rs = lc.right-sibling
            while rs != NIL
                PRINT-LCRS-TREE(rs)
                rs = rs.right-sibling

4-5给定一个n结点的二叉树,写出一个O(n)时间的非递归过程,将该树每个结点的关键字输出。要求除该树本身的存储空间外只能使用固定量的额外存储空间,且在过程中不得修改该树,即使是暂时的修改也不允许

PRINT-KEY(T)
    prev = NIL
    x = T.root
    while x != NIL
        if prev = x.parent
            print x.key
            prev = x
            if x.left
                x = x.left
            else
                if x.right
                    x = x.right
                else 
                    x = x.parent
        else if prev == x.left and x.right != NIL
                prev = x
                x = x.right
        else
                prev = x
                x = x.parent

4-6 任意有根树的左孩子右兄弟表示法中每个结点用到三个指针:left-child,right-sibling和parent。对于任何结点,都可以在常数时间到达其父结点,并在与其孩子数呈线性关系的时间内到达所有孩子结点。说明如何在每个结点中只使用两个指针和一个布尔值的情况下,使结点的父结点或者其所有孩子结点可以在与其孩子数呈线性关系的时间内到达。

既然少了parent指针,多了布尔值,那么布尔值正好只有ture和false两种值,可以在false的时候right-sibling指向其parent结点,其它情况正常

主要参考

算法导论第十章基本数据结构
Elementary Data Structures

おすすめ

転載: blog.csdn.net/y3over/article/details/121508270