數據結構與算法(四)-線性表之循環鏈表
阿新 • • 發佈:2018-09-23
log ddc 兩個 方向 http return close 單向 throw
前言:前面幾篇介紹了線性表的順序和鏈式存儲結構,其中鏈式存儲結構為單向鏈表(即一個方向的有限長度、不循環的鏈表),對於單鏈表,由於每個節點只存儲了向後的指針,到了尾部標識就停止了向後鏈的操作。也就是說只能向後走,如果走過了,就回不去了,還得重頭開始遍歷,所以就衍生出了循環鏈表
一、簡介
定義:將單鏈表中中斷結點的指針端有空指針改為指向頭結點,就使整個單鏈表形成一個環,這種頭尾詳解的單鏈表稱為單循環鏈表,簡稱循環鏈表;特性:
- 若鏈表為空,則頭結點的next結點還是指向其本身,即head.next=head;
- 尾節點的next指針指向head結點,即頭尾相連;
- 判斷是否遍歷了完,直接判斷next==head即可;
- 由單鏈表變化的循環也成為單向循環鏈表;
- 循環鏈表的特點是無須增加存儲量,僅對表的鏈接方式稍作改變,即可使得表處理更加方便靈活;
二、單向循環鏈表的實現
循環鏈表是在單鏈表(線性單鏈表)的基礎上,將尾節點的next指向了head,所以基本的結構是類似的,所以下面,直接貼代碼了;public class LoopChain<T> { //尾結點直接引用 private Node<T> tail; //鏈長度 private Integer size;LoopChain.java//初始化 LoopChain() { tail = new Node<T>(); tail.setNext(tail); } public Node<T> remove(Integer index) throws Exception { //獲取該位置的上一個節點 Node<T> s = getNode(index - 1); //獲取該位置節點的下一個節點 Node<T> next = getNode(index).getNext();//將本節點的next節點放在本節點的前一個節點的next節點位置 s.setNext(next.getNext()); return next; } public void add(T t,Integer index) throws Exception { //獲取該位置的上一個節點 Node<T> s = getNode(index - 1); //創建新節點 Node<T> p = new Node<>(); p.setObject(t); //將本節點的next節點放入新節點的next節點 p.setNext(s.getNext()); //將新節點放入本節點的next節點位置 s.setNext(p); } public T get(Integer index) throws Exception { return (T)getNode(index).getObject(); } private Node<T> getNode(Integer index) throws Exception { if (index > size || index < 0) throw new Exception("index outof length"); //取頭節點 Node<T> p = tail.next; for (int i = 0; i < index; i++) p = p.getNext(); return p; } class Node<T> { private Object object; private Node next; public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } } }
獲取元素:
public T get(Integer index) throws Exception { return (T)getNode(index).getObject(); } private Node<T> getNode(Integer index) throws Exception { if (index > size || index < 0) throw new Exception("index outof length"); //取頭節點 Node<T> p = tail.next; for (int i = 0; i < index; i++) p = p.getNext(); return p; }
插入元素:
public void add(T t,Integer index) throws Exception { //獲取該位置的上一個節點 Node<T> s = getNode(index - 1); //創建新節點 Node<T> p = new Node<>(); //將本節點的next節點放入新節點的next節點 p.setNext(s.getNext()); //將新節點放入本節點的next節點位置 s.setNext(p); }
移除元素:
public Node<T> remove(Integer index) throws Exception { //獲取該位置的上一個節點 Node<T> s = getNode(index - 1); //獲取該位置節點的下一個節點 Node<T> next = getNode(index).getNext(); //將本節點的next節點放在本節點的前一個節點的next節點位置 s.setNext(next.getNext()); return next; }在之前學習單鏈表的時候,我們使用頭結點來代表一個鏈表,可以用O(1)的時間訪問第一個節點,但是在訪問尾節點時,需要使用O(n)的時間,而循環鏈表則不同,完全可以使用O(1)的時間來訪問第一個節點和尾節點。
三、問題
判斷單鏈表中是否有環? 思考:有環的定義是,鏈表的尾節點指向了鏈表中的某個節點; 那麽怎麽判斷是否有環的存在呢? 1、使用p、q兩個指針,p總是向前走,但q每次都從頭開始走,對於每個節點,p走的步數是否與q走的步數一致,若不一致則說明存在環; 若鏈表長度為3,當p走完一圈後,會出現p的步數為4,而q的步數為1,不相等,則存在環。 2、使用p、q兩個指針,p每次向前走一步,q每次向前走兩步,若在某個時候p==q,則存在環; 使用了快慢原理來進行判斷是否存在環。 註意: ①循環鏈表中沒有NULL指針。涉及遍歷操作時,其終止條件就不再是像非循環鏈表那樣判別p或p->next是否為空,而是判別它們是否等於某一指定指針,如頭指針或尾指針等。 ②在單鏈表中,從一已知結點出發,只能訪問到該結點及其後續結點,無法找到該結點之前的其它結點。而在單循環鏈表中,從任一結點出發都可訪問到表中所有結點,這一優點使某些運算在單循環鏈表上易於實現。
本系列參考書籍:
《寫給大家看的算法書》
《圖靈程序設計叢書 算法 第4版》
數據結構與算法(四)-線性表之循環鏈表