10.1スタックとキュー
スタックとキューは動的なコレクション、スタック(スタック)ですLIFOキュー(待ち行列)がFIFOです。
スタック
スタックは、同等のベースプレートで、プレートは、キャビネットにあなただけの上から取るようになったときに、プレートを取り出したいたびに配置することができます。つまり、LIFO
以下、配列Sであってもよい[1..nの]は、n個の要素のスタックを達成するまで収容することができる。この配列は、プロパティS.top、新しく挿入された要素をポイントし、スタックS [1を含む要素を有しますS [1]はスタック要素の下で..S.top]は、S [S.top]は、スタックの最上位である。S.top = 0、スタックは任意の要素が含まれていない場合に、スタックが空である。あなたがしようとした場合空のスタックを行うポップ動作、およびスタックアンダーフロー(アンダーフロー)、S.topがN超える場合、スタックオーバーフロー(オーバーフロー)。
メインスタック動作はStack_Empty(空気かどうかクエリ)/プッシュ(押し)/ポップ(POP)を有しています
擬似コード:
クエリが空であるかどうか
Stack_Empty(S) の場合 S.top == 0 復帰真の 他の リターン偽
スタック
プッシュ(S、X) であれば S.top!= N S.top + = 1つの S [S.top] = X さもなければエラー" オーバーフロー"
スタックがのみ最上位の要素をポップすることができますので、要素に指定されていないスタックの飛び出し
ポップ(S) の場合Stack_Emypty(S) エラー" アンダーフロー" 他 S.top - = 1 リターン S [S.top + 1 ]
キュー
アクセスのために、同じようにキュー待ち、ストアキューは、n個の要素キュー[n]を用いて実装することができる、しているプロパティはqueue.headキューポイントをヘッド要素に、要素の次の点はqueue.tail挿入位置であることが。キュー位置queue.headに格納された要素、queue.head + 1、...、queue.tail-1。キューが空であるかどうかを決定するための鍵となり、ヘッド==尾部は、空であってもよい場合ため、またはかもしれませんそれはいっぱいです。私たちは私たちが判断しやすくするために、事前にタグを定義することができるようにします。
主操作キューQueue_Empty(キューが空であるかどうか)/エンキュー(エンキュー)/デキュー(デキュー)であります
擬似コード
キューが空であるかどうかを判断
Queue_Empty(Q) であればヘッド==尾&& タグ リターンFalseの 他の 場合はヘッド==尾&&!タグ リターントゥルー
チーム
n個の長さのQであります エンキュー(Q、X) if head == tail && tag return "overflow" else { Q[tail++] = x tail %= n tag = 1 }
出栈
n 为 Q的长度 Dequeue(Q) if head == tail && !tag return "underflow" else { head = (head+1) % n tag = 0 }
10.2链表
链表其实和数组很像, 但是与数组不同的是, 链表的顺序是由各个对象里的指针决定的. 链表中每一个对象都由一个关键字key和两个指针: next 和 prev. 具体说明, 我们假设 x 为链表中的一个元素, 那么x.next 就指向下一个元素, x.prev 指向前一个元素. 如果 x.next指向为空, 说明x为链表的尾(tail); 同理, 如果x.prev指向为空, 那么x为链表的头(head).
链表的形式有单链接/双链接/已排序/未排序/循环和非循环的. 其中, 单链接的链表省略每个元素中的prev指针; 循环链表表头元素的prev指针指向表尾元素, 而表尾元素的next指针则指向表头元素.
基本操作有List_Search(搜索)/List_Insert(插入)/List_Delete(删除)
搜索, 这里就采用简单的线性搜索方法, 对于List_Search(L, k) , 查找链表L中, 第一个关键字为k的元素, 并返回指向该元素的指针.
List_Search(L, k) x = L.head // 从头开始查找 while x != Null && x.key != k x = x.next // 没有找到且不是最后一个元素就一直往下找 return x
如果链表中有n个对象, 时间复杂度最坏情况下为O(n), 需要遍历所有元素
插入(只考虑插入到前端的情况) 时间复杂度O(1)
List_Insert(L, x) x.next = L.head if L.head != Null L.head.prev = x L.head = x x.prev = Null
删除
将一个元素x 从链表中移除, 需要给定一个指向x的指针, 然后通过修改一些指针, 将x"删除出"该链表. 如果要删除具有给定关键字值的元素, 则必须先调用List_Search 找到该元素. 以下伪代码, 省略了查找的过程.
List_Delete(L, x) if x.prev != Null // 如果x的前驱不为空, 那么就让它指向x的后驱元素 x.prev.next = x.next else L.head = x.next if x.next != null // 如果x的后驱不为空, 那么就让它指向x的前驱元素 x.next.prev = x.prev
如上可以看到在进行插入和删除操作的时候我们都要考虑表头和表尾的边界条件, 代码看起来就会有些繁琐. 下面就引入哨兵(sentinel)的概念, 来简化边界条件的处理.
我们在链表L中设置一个对象L.nil, 它代表为Null, 但是也具有和其他对象相同的属性. 对于链表代码中出现的每一处对Null的引用, 都代之以对哨兵L.nil的引用. 这样就可以将常规的双向链表转变为一个有哨兵的双向循环链表, 哨兵位于表头和表尾之间, L.nil.next 指向表头, L.nil.prev指向表尾, 类似的, 表尾的next 属性和表头的prev属性都指向L.nil.
以下为加了哨兵改动过的代码:
搜索
List_Search'(L, k) x = L.nil.next // 从头开始查找 while x != L.nil && x.key != k x = x.next // 没有找到且不是最后一个元素就一直往下找 return x
插入
List_Insert'(L, x) x.next = L.nil.next L.nil.next.prev = x L.nil.next = x x.prev = L.nil
删除
List_Delete'(L, x) x.prev.next = x.next x.next.prev = x.prev
注意:
哨兵基本上不能降低数据结构相关操作的渐近时间界也就是时间复杂度, 它可以降低的是常数因子. 循环语句中使用哨兵的好处在于可以使代码简洁, 而非提高速度.
然而, 我们应当慎用哨兵. 因为如果在很多很短的链表中使用哨兵, 哨兵所占用的额外存储空间会造成严重的存储浪费.