一、鏈表類型
二、單鏈表結構根據鏈表的構造方式的不同可以分爲:
1.單向鏈表
2.單向循環鏈表
3.雙向循環鏈表
單鏈表是構成鏈表的每個結點只有一個指向直接後繼結點的指針。
單鏈表的表示方法:
單鏈表中每個結點的結構:
單鏈表有帶頭結點結構和不帶頭結點結構兩種。我們把指向單鏈表的指針稱爲單鏈表的頭指針。頭指針所指的不存放數據元素的第一個結點稱作頭結點。存放第一個數據元素的結點稱作第一個數據元素結點,或稱首元結點。
從線性表的定義可知,線性表要求允許在任意位置進行插入和刪除。當選用帶頭結點的單鏈表時,插入和刪除操作的實現方法比不用帶頭結點單鏈表的實現方法簡單。(a) 空鏈 (b)非空鏈
設頭指針用head表示,在單鏈表中任意結點(但不是第一個數據元素結點)前插入一個新結點的方法如圖2-6所示。算法實現時,首先把插入位置定位在要插入結點的前一個結點位置,然後把s表示的新結點插入單鏈表中。
在單鏈表非第一個結點前插入結點過程
要在第一個數據元素結點前插入一個新結點,若採用不帶頭結點的單鏈表結構,則結點插入後,頭指針head就要等於新插入結點s,這和在非第一個數據元素結點前插入結點時的情況不同。另外,還有一些不同情況需要考慮。因此,算法對這兩種情況就要分別設計實現方法。
而如果採用帶頭結點的單鏈表結構,算法實現時,p指向頭結點,改變的是p指針的next指針的值,而頭指針head的值不變。因此,算法實現方法比較簡單。在帶頭結點單鏈表中第一個數據元素結點前插入一個新結點的過程如圖所示。
帶頭結點單鏈表第一個結點前插入結點過程
三、節點類
單鏈表是由一個一個結點組成的,因此,要設計單鏈表類,必須先設計結點類。結點類的成員變量有兩個:一個是數據元素,另一個是表示下一個結點的對象引用(即指針)。
設計操作:
1.頭結點的初始化
2.非頭結點的構造
3.獲取該結點指向的下個結點
4.設置該結點指向的下個結點
5.設置該結點的數據
6.獲取該結點的數據
四、單鏈表類
單鏈表類的成員變量至少要有兩個:一個是頭指針,另一個是單鏈表中的數據元素個數。但是,如果再增加一個表示單鏈表當前結點位置的成員變量,則有些成員函數的設計將更加方便。
五、實現代碼
public interface List { //插入元素 public void add(int index,Object obj) throws Exception; //刪除元素 public void delete(int index) throws Exception; //獲取某個元素 public Object get(int index) throws Exception; //判斷是否爲空 public boolean isEmpty(); //獲得集合的長度 public int size(); }
//節點類 public class Node { //數據域 Object element; //指針域 Node next; //頭結點的構造方法 public Node(Node nextval) { this.next=nextval; } //非頭結點的額構造方法 public Node(Object obj,Node nextval) { this.element=obj; this.next=nextval; } //獲取當前數據域的值 public Object getElement() { return this.element; } //獲取當前節點的後繼節點 public Node getNode() { return this.next; } //設置當前數據域的值 public void setElement(Object obj) { this.element=obj; } //設置當前節點的指針域 public void setNode(Node node) { this.next=node; } public String toString() { return this.element.toString(); } }
public class LinkedList implements List { //頭指針 private Node head; //當前節點對象 private Node current; //節點個數 int size; public LinkedList() { //初始化頭結點,讓頭指針指向頭結點,並且讓當前節點對象等於頭結點 this.head=current=new Node(null); this.size=0; } //定爲函數,將當前節點對象定爲到前一個節點 public void index(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("參數錯誤!"); } //說明在頭結點之後操作 if(index==-1) return; current=head.next; //循環變量 int j=0; while(current!=null&&j<index) { current=current.next; j++; } } //增加元素 @Override public void add(int index, Object obj) throws Exception { if(index<0||index>size) { throw new Exception("增加元素參數錯誤!"); } //定爲到操作對象的前一個對象 index(index-1); current.setNode(new Node(obj,current.next)); size++; } //刪除元素 @Override public void delete(int index) throws Exception { if(isEmpty()) { throw new Exception("鏈表爲空,刪除錯誤!"); } if(index<0||index>size) { throw new Exception("刪除元素參數錯誤!"); } index(index-1); current.setNode(current.next.next); size--; } @Override public Object get(int index) throws Exception { if(index<-1||index>size-1) { throw new Exception("參數錯誤!"); } index(index); return current.getElement(); } @Override public boolean isEmpty() { return size==0; } @Override public int size() { return size; } }
//測試類
public class Main { public static void main(String[] args) throws Exception { LinkedList list=new LinkedList(); Random random=new Random(); for (int i = 0; i < 10; i++) { int temp=random.nextInt(100); list.add(i, temp); System.out.print(temp+" "); } System.out.println(); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } System.out.println(); list.delete(5); for(int j=0;j<list.size();j++) { System.out.print(list.get(j)+" "); } } }
結果:
六、單鏈表的效率分析
在單鏈表的任何位置上插入數據元素的概率相等時,在單鏈表中插入一個數據元素時比較數據元素的平均次數爲:
刪除單鏈表的一個數據元素時比較數據元素的平均次數爲:
因此,單鏈表插入和刪除操作的時間複雜度均爲O(n)。另外,單鏈表取數據元素操作的時間複雜度也爲O(n)。
七、順序表和單鏈表的比較
順序表的主要優點是支持隨機讀取,以及內存空間利用效率高;順序表的主要缺點是需要預先給出數組的最大數據元素個數,而這通常很難準確作到。當實際的數據元素個數超過了預先給出的個數,會發生異常。另外,順序表插入和刪除操作時需要移動較多的數據元素。和順序表相比,單鏈表的主要優點是不需要預先給出數據元素的最大個數。另外,單鏈表插入和刪除操作時不需要移動數據元素。
單鏈表的主要缺點是每個結點中要有一個指針,因此單鏈表的空間利用率略低於順序表的。另外,單鏈表不支持隨機讀取,單鏈表取數據元素操作的時間複雜度爲O(n);而順序表支持隨機讀取,順序表取數據元素操作的時間複雜度爲O(1)。