第9章 基本数据结构
9.1 栈与队列
-
栈
-
栈是限定在一端进行插入和删除的线性表。
-
操作:INSERT称作压入(PUSH),DELETE称作弹出(POP)
-
特点:
- 只能在栈顶进行插入和删除
- 先进后出, 后进先出
- 栈底指针 bottom ,栈顶指针 top
- 栈底指针不变,栈中元素随栈顶指针的变化而动态变化
- 栈具有记忆功能
- 栈支持子程序调用
-
伪代码:
-
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]
-
-
运行时间: O ( 1 ) O(1) O(1)
-
-
队列
-
队列是指允许在一端进行插入,而在另一端进行删除的线性表。
-
操作:INSERT称作入队(ENQUEUE),DELETE称作出队(DEQUEUE)
-
特点:
- 队列只允许在队尾进行插入,而在队头进行删除
- 先进先出 ,后进后出
- 队头指针 front ,队尾指针 rear
- 队列中元素随队头指针和队尾指针的变化而动态变化
-
循环队列:计算:
- rear>front: s=rear-front
- rear<front: s= 容量 +rear-front
- rear=front: s=1 或者 s=0
-
伪代码:
-
ENQUEUE(Q, x)
Q[Q.tail] = x if Q.tail == Q.length Q.tail = 1 else Q.head = Q.head + 1 return x
-
DEQUEUE(Q)
x = Q[Q.head] if Q.head = Q.length Q.head = 1 else Q.head = Q.head + 1 return x
-
-
运行时间: O ( 1 ) O(1) O(1)
-
9.2 链表
-
双向链表:每个元素都是一个对象,每个对象都有一个关键字 k e y key key和两个指针: n e x t 和 p r e v next和prev next和prev。
- x . n e x t x.next x.next:指向它在链表中的后继元素
- x . p r e v x.prev x.prev:指向它在链表中的前驱元素
-
链表: x . p r e v = N I L x.prev=NIL x.prev=NIL,即没有前驱
- 链表的头 h e a d head head:链表的第一个元素
- 链表的尾 t a i l tail tail:链表的最后一个元素
- 特点:
- 若 L . h e a d = N I L L.head=NIL L.head=NIL,则链表为空
- 各数据结点的存储空间可以不连续
- 各数据元素的存储顺序和逻辑循序可以不一致
- 线性表的链式存储所占存储空间大于顺序存储结构
- 查找结点时链式储存要比顺序存储慢
- 链式存储插入删除元素比顺序存储灵活
- 线性链表的操作:在线性链表中进行插入与删除,不需要移动链表中的元素。
- 类别:
- 单链接:省略每个元素的 p e r v perv perv指针
- 双链接:双向链表
- 已排序:链表的线性顺序与元素中关键字的线性顺序一致
- 未排序:元素无顺序排列
-
链表的搜索:
-
采用简单的线性搜索方法
-
伪代码:LIST-SEARCH(L, k)
x = L.head while x != NIL and x.key != k x = x.next return x
-
最坏情况运行时间: O ( n ) O(n) O(n)
-
-
链表的插入
-
伪代码:LIST-INSERT(L, x)
x.next = L.head if L.head != NIL L.head.prev = x L.head = x x.prev = NIL
-
运行时间: O ( 1 ) O(1) O(1)
-
-
链表的删除
-
伪代码:LIST-DELETE(L, x)
if x.prev != NIL x.prev.next = x.next else L.head = x.next if x.next != NIL x.next.prev = x.prev
-
运行时间: O ( 1 ) O(1) O(1)
-
-
哨兵
- 哨兵是一个哑对象,其作用是简化边界的处理
- 假设在链表L中设置一个对象 L . n i l L.nil L.nil,该对象代表NIL,但具有其他对象相同的各个属性。对于链表代码中出现的每一处对NIL的引用,都代之以对哨兵 L . n i l L.nil L.nil的引用。这样调整将一个常规的双向链表转变成一个有哨兵的双向循环链表,哨兵L.nil位于表头和表尾之间。
- 属性L.nil.next指向表头,L.nil.prev指向表尾。同样表尾的next属性和表头的prev属性同时指向L.nil。
- 因为L.nil.next指向表头,我们就可以去掉属性L.head,并把对它的引用代替为对L.nil.next的引用。
- 一个空的链表只由一个哨兵组成,L.nil.next和L.nil.prev同时指向L.nil。
9.3 指针和对象的实现
-
对象的多数组表示:
- 对一组具有相同域的对象,每一个对象都可以用一个数组来表示 。
- 对于一个给定的数组下标x,三个数组项 k e y [ x ] , n e x t [ x ] , p r e v [ x ] key[x], next[x], prev[x] key[x],next[x],prev[x]一起表示链表中的一个对象。
-
对象的单数组表示:一个对象占据存储中的一组连续位置 。
-
对象的分配与释放:使用垃圾收集器来确定链表中哪些对象空间未被使用
9.4 有根树的表示
- 二叉树:利用属性 p , l e f t , r i g h t p, left, right p,left,right存放指向父节点,左子叶,右子叶的指针。属性 T . r o o t T.root T.root指向整棵树的根结点。
- 分支无限制的有根树:将二叉树的表示方法推广到多个结点的任意类型的树
- 左兄弟右兄弟表示法:减少存储空间的浪费
- 只使用两给指针:
- x . l e f t − c h i l d x.left-child x.left−child指向结点x最左边的根结点
- x . r i g h t − c h i l d x.right-child x.right−child指向x右侧相邻的根结点