数据结构-单向循环链表、双向循环链表、仿真链表
一、單向循環鏈表:
1、概念:
單向循環鏈表是單鏈表的另一種形式,其結構特點是鏈表中最后一個結點的指針不再是結束標記,而是指向整個鏈表的第一個結點,從而使單鏈表形成一個環。
和單鏈表相比,循環單鏈表的長處是從鏈尾到鏈頭比較方便。當要處理的數據元素序列具有環型結構特點時,適合于采用循環單鏈表。
和單鏈表相同,循環單鏈表也有帶頭結點結構和不帶頭結點結構兩種,帶頭結點的循環單鏈表實現插入和刪除操作時,算法實現較為方便。
帶頭結點的循環單鏈表的操作實現方法和帶頭結點的單鏈表的操作實現方法類同,差別僅在于:
(1)在構造函數中,要加一條head.next = head 語句,把初始時的帶頭結點的循環單鏈表設計成上圖中(a)所示的狀態。
(2)在index(i)成員函數中,把循環結束判斷條件current != null改為current != head。
2、單鏈表的代碼實現:
1、結點類Node的實現代碼
public class Node { public Object data;//保存當前結點的數據 public Node next;//指向下一個結點 public Node(Object data){ this.data = data; } }2、循環鏈表類CircleList的實現代碼
public class CircleList { private Node head; public CircleList(){ head = new Node(0); head.next = null; } //在指定位置插入結點 public boolean insert(Object data,int pos){ boolean ret = (head != null) && (data != null) && (pos >= 0); if(ret){ Node node = new Node(data); if(head.next == null){ //插入的結點前,循環鏈表中沒有結點。 head.next = node; node.next = node; }else{ if(pos >= (Integer)head.data){ pos = (Integer)head.data; } Node currentNode = head.next; //若currentNode.next == head.next,就說明currentNode是最后一個結點 for(int i = 0;(i < pos) && (currentNode.next != head.next);i++){ currentNode = currentNode.next; } node.next = currentNode.next; currentNode.next = node; //插入位置的下標為0時 if(pos == 0){ head.next = node; } } head.data = (Integer)head.data + 1; } return ret; } //獲取鏈表中下標為pos的結點 public Object get(int pos){ Object ret = null; if(head != null && pos >= 0 && pos < (Integer)head.data){ Node node = head;//頭結點 //找到要刪除的結點 for(int i = 0;i<=pos;i++){ node = node.next; } if(node != null){ ret = node.data; } } return ret; } //刪除鏈表中下標為pos的結點 public Object delete(int pos){ Object ret = null; if(head != null && pos >= 0 && pos < (Integer)head.data){ Node node = head;//頭結點 Node currentNode = null;//要刪除的結點 //找到要刪除結點的前一個結點 for(int i = 0;i<pos;i++){ node = node.next; } currentNode = node.next; //獲取要刪除結點的數據 if(currentNode != null){ ret = currentNode.data; } //要刪除的結點是循環鏈表中的第一個結點 if(head.next == currentNode){ head.next = currentNode.next; } //刪除結點 node.next = currentNode.next; head.data = (Integer)head.data - 1; } return ret; } //清空鏈表 public void clear(){ if(head != null){ head.data = 0; head.next = null; } } //注銷鏈表 public void destroy(){ if(head != null){ head = null; } } //獲取鏈表中結點的個數 public int length(){ int ret = -1; if(head != null){ ret = (Integer)head.data; } return ret; } //打印循環鏈表中的數據 public void display(){ if(head != null){ Node node = head; for(int i = 0;i < (Integer)head.data;i++){ node = node.next; System.out.print(node.data+" "); } } } }3、測試類Test的代碼
public class Test { public static void main(String[] args) { CircleList list = new CircleList(); list.insert("結點1", 0); list.insert("結點2", 1); list.insert("結點3", 2); list.insert("結點4", 3); list.insert("結點5", 4); list.insert("結點6", 5); System.out.println("鏈表中的元素為:"); list.display(); System.out.println("\n鏈表中結點的個數:"+list.length()); System.out.println("\n獲取鏈表中下標為2的結點:"+list.get(2)); System.out.println("刪除鏈表中下標為0的結點:"+list.delete(0)); System.out.println("鏈表中的元素為:"); list.display(); System.out.println("\n鏈表中結點的個數:"+list.length()); list.clear(); list.destroy(); } }4、程序運行結果
鏈表中的元素為:
結點1 結點2 結點3 結點4 結點5 結點6
鏈表中結點的個數:6
獲取鏈表中下標為2的結點:結點3
刪除鏈表中下標為0的結點:結點1
鏈表中的元素為:
結點2 結點3 結點4 結點5 結點6
鏈表中結點的個數:5
二、雙向循環鏈表:
雙向鏈表:
雙向鏈表是每個結點除后繼指針外還有一個前驅指針。和單鏈表類同,雙向鏈表也有帶頭結點結構和不帶頭結點結構兩種,帶頭結點的雙向鏈表更為常用;另外,雙向鏈表也可以有循環和非循環兩種結構,循環結構的雙向鏈表更為常用。
雙向循環鏈表:
在雙向鏈表中,每個結點包括三個域,分別是element域、next域和prior域,其中element域為數據元素域,next域為指向后繼結點的對象引用,prior域為指向前驅結點的對象引用。下圖為雙向鏈表結點的圖示結構:
如下圖是帶頭結點的雙向循環鏈表的圖示結構。雙向循環鏈表的next和prior各自構成自己的單向循環鏈表:
在雙向鏈表中,有如下關系:設對象引用p表示雙向鏈表中的第i個結點,則p.next表示第i+1個結點,p.next.prior仍表示第i個結點,即p.next.prior == p;同樣地,p.prior表示第i-1個結點,p.prior.next仍表示第i個結點,即p.prior.next == p。下圖是雙向鏈表上述關系的圖示:
雙向循環鏈表的插入過程:
下圖中的指針p表示要插入結點的位置,s表示要插入的結點,①、②、③、④表示實現插入過程的步驟:
循環雙向鏈表的刪除過程:
下圖中的指針p表示要插入結點的位置,①、②表示實現刪除過程的步驟:
2、雙向循環鏈表的代碼實現
public class DbLinkedList<T> { //定義內部類,用作鏈表的節點 private class Node<T> { Node<T> pre; //指向前一個節點 Node<T> next; //指向后一個節點 T value; //當前節點的值 public Node(T value, Node<T> next, Node<T> pre) { this.value = value; this.next = next; this.pre = pre; } public String toString() { return this.value + ""; } } private Node<T> header; //定義頭節點 private int size; //定義鏈表的長度 public DbLinkedList() { header = new Node<T>(null, null, null);//空的頭節點,用來區分雙向循環鏈表的首尾 header.pre = header.next = header; //雙向循環鏈表,首尾相連 size = 0; } public DbLinkedList(Collection<? extends T> collection) { this(); addAll(this.size, collection); } public boolean add(T value)//在鏈表的尾巴上面加一個節點, 相當于在header節點前面加一個節點 { return add(header, value); } public boolean add(int index, T value)//指定index處加入節點 { return add(entry(index), value); } public boolean remove(Object obj)//刪除指定value的節點 { Node<T> node; //1. 從header.next往后遍歷,再到header時結束 for(node = header.next; node!=header; node=node.next) { if(node.value == obj || (obj!=null && obj.equals(node.value))) { remove(node); return true; } } //2.java.util.LinkedList實現,先區分null再遍歷,個人感覺效率差不多呀,希望有人賜教 /* if(obj==null) { for(node = header.next; node!=header; node=node.next) { if(node.value == null) { remove(node); return true; } } } else { for(node = header.next; node!=header; node=node.next) { if(node.value == obj || obj.equals(node.value)) { remove(node); return true; } } } */ return false; } public T remove(int index)//刪除指定index節點 { return remove(entry(index)); } public boolean addAll(Collection<? extends T> collection) { return addAll(this.size, collection); } //在指定index位置添加collection里的所有元素 public boolean addAll(int index, Collection<? extends T> collection) { if(collection==null || collection.size()==0) { return false; } //獲取指定位置節點,如果index==size,則在末尾添加節點,即header節點之前 //當index==size時,調用entry方法會拋異常,所以三則表達式很有必要 Node<T> node = index == this.size ? this.header : entry(index); Object[] objArray = collection.toArray(); int len = objArray.length; Node<T> preNode = node.pre; for(int i=0; i<len; i++) { //新建一個節點,新節點的next指向node,新節點的pre指向node的pre //完成指向過程node.pre←newNode→node //當第二次迭代時,preNode=newNode1(i=1創建的newNode), newNode1←newNode2(i=2創建的newNode)→node Node<T> newNode = new Node<T>((T) objArray[i], node, preNode); //維持雙向鏈表的指向,將node的pre節點的next指向新節點,完成指向過程node.pre→newNode //當第二次迭代時,newNode1→newNode2 preNode.next = newNode; //將preNode指向newNode,當第二次迭代時,preNode往后移動一位 preNode = newNode; } //迭代完成后,node的前一個節點指向preNode(即最后一次創建的newNode),preNode←node //如果len=2,完成的鏈就變成這樣preNode→←newNode1→←newNode2→←node node.pre = preNode; //長度加len this.size += len; return true; } private T remove(Node<T> node) { //node的前一個節點next指向node的下一個節點 //node的下一個節點pre指向node的前一個節點 //A→node←B改成A→←B node.pre.next = node.next; node.next.pre = node.pre; //node的前后指向null //A←node→B改成null←node→null node.pre = node.next = null; T value = node.value; node.value = null; this.size--; return value; } public T get(int index) { return entry(index).value; } private Node<T> entry(int index) //迭代至index處的節點 { rangeIndex(index); //判斷index是否越界 Node<T> node = this.header; //判斷index是否小于size的一半,如果小于就從header往后開始迭代,否則就從header往前開始迭代,提高效率 //例如有一個鏈表header→A→B→C→D→header if(index < (this.size>>1)) { //因為header是空的頭節點,所以i要小于等于index //例如index=1, 小于size的一半2 //i=0時,node=A //i=1時,node=B,然后跳出循環 for(int i=0; i<=index; i++) { node = node.next; } } else { //例如index=2,不小size的一半 //i=3, node等于header的前一個, node=D //i=2, node=C,然后跳出循環 for(int i=this.size-1; i>=index; i--) { node = node.pre; } } return node; } private void rangeIndex(int index) { if(index < 0 || index >= this.size) { throw new IndexOutOfBoundsException("index錯誤"); } } private boolean add(Node<T> node, T value) { //新建一個節點,新節點的next指向node,新節點的pre指向node的pre //完成指向過程node.pre←newNode→node Node<T> newNode = new Node<T>(value, node, node.pre); //維持雙向鏈表的指向,將node的pre節點的next指向新節點,完成指向過程node.pre→newNode node.pre.next = newNode; //node節點的前一個節點指向新節點,完成指向過程newNode←node node.pre = newNode; //上面兩行代碼不能顛倒,否則node的前一個節點會被覆蓋成新節點,會丟失node原來的前一個節點的next指向 //上述代碼完成了在node節點和node前一個節點之間加入一個新節點,并維護了雙向關系 this.size++; return true; } public void clear() { Node<T> node = header.next; //將每一個節點的雙向指向都清空,這樣每個節點都沒有被引用,可以方便垃圾回收器回收內存 while(node != header) { //將node的下一個節點臨時保存起來 Node<T> tempNode = node.next; //將node的下一個節點和上一個節點置空 node.next = node.pre = null; //將node的值也置空 node.value = null; //將node移動到下一個節點 node = tempNode; } //清空header的雙向指向null this.header.next = this.header.pre = this.header; this.size = 0; } public boolean isEmpty() { return this.size == 0; } public int size() { return this.size; } }三、仿真鏈表:
在鏈式存儲結構中,我們實現數據元素之間的次序關系依靠指針。我們也可以用數組來構造仿真鏈表。方法是在數組中增加一個(或兩個)int類型的變量域,這些變量用來表示后一個(或前一個)數據元素在數組中的下標。我們把這些int類型變量構造的指針稱為仿真指針。這樣,就可以用仿真指針構造仿真的單鏈表(或仿真的雙向鏈表)。
參考資料
- https://blog.csdn.net/tu451953337/article/details/36673671
- https://blog.csdn.net/basycia/article/details/51839431
- https://blog.csdn.net/dianzigaoshou/article/details/74546451
總結
以上是生活随笔為你收集整理的数据结构-单向循环链表、双向循环链表、仿真链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单链表简易教程
- 下一篇: 数据结构-顺序栈、链栈