文本主要內容:
- 鏈表結構
- 單鏈表代碼實現
- 單鏈表的效率分析
一、鏈表結構: (物理存儲結構上不連續,邏輯上連續;大小不固定)
概念:
鏈式存儲結構是基於指針實現的。我們把一個數據元素和一個指針稱為結點。
數據域:存數數據元素信息的域。
指針域:存儲直接后繼位置的域。
鏈式存儲結構是用指針把相互直接關聯的結點(即直接前驅結點或直接后繼結點)鏈接起來。鏈式存儲結構的線性表稱為鏈表。
鏈表類型:
根據鏈表的構造方式的不同可以分為:
- 單向鏈表
- 單向循環鏈表
- 雙向循環鏈表
二、單鏈表:
概念:
鏈表的每個結點中只包含一個指針域,叫做單鏈表(即構成鏈表的每個結點只有一個指向直接后繼結點的指針)
單鏈表中每個結點的結構:
1、頭指針和頭結點:
單鏈表有帶頭結點結構和不帶頭結點結構兩種。
“鏈表中第一個結點的存儲位置叫做頭指針”,如果鏈表有頭結點,那么頭指針就是指向頭結點的指針。
頭指針所指的不存放數據元素的第一個結點稱作頭結點(頭結點指向首元結點)。頭結點的數據域一般不放數據(當然有些情況下也可存放鏈表的長度、用做監視哨等)
存放第一個數據元素的結點稱作第一個數據元素結點,或稱首元結點。
如下圖所示:
不帶頭結點的單鏈表如下:
帶頭結點的單鏈表如下圖:
關於頭指針和頭結點的概念區分,可以參考如下博客:
http://blog.csdn.net/hitwhylz/article/details/12305021
2、不帶頭結點的單鏈表的插入操作:
上圖中,是不帶頭結點的單鏈表的插入操作。如果我們在非第一個結點前進行插入操作,只需要a(i-1)的指針域指向s,然后將s的指針域指向a(i)就行了;如果我們在第一個結點前進行插入操作,頭指針head就要等於新插入結點s,這和在非第一個數據元素結點前插入結點時的情況不同。另外,還有一些不同情況需要考慮。
因此,算法對這兩種情況就要分別設計實現方法。
3、帶頭結點的單鏈表的插入操作:(操作統一,推薦)
上圖中,如果采用帶頭結點的單鏈表結構,算法實現時,p指向頭結點,改變的是p指針的next指針的值(改變頭結點的指針域),而頭指針head的值不變。
因此,算法實現方法比較簡單,其操作與對其它結點的操作統一。
問題1:頭結點的好處:
頭結點即在鏈表的首元結點之前附設的一個結點,該結點的數據域中不存儲線性表的數據元素,其作用是為了對鏈表進行操作時,可以對空表、非空表的情況以及對首元結點進行統一處理,編程更方便。
問題2:如何表示空表:
無頭結點時,當頭指針的值為空時表示空表;
有頭結點時,當頭結點的指針域為空時表示空表。
如下圖所示:
問題3:頭結點的數據域內裝的是什么?
頭結點的數據域可以為空,也可存放線性表長度等附加信息,但此結點不能計入鏈表長度值。
三、單項鏈表的代碼實現:
1、結點類:
單鏈表是由一個一個結點組成的,因此,要設計單鏈表類,必須先設計結點類。結點類的成員變量有兩個:一個是數據元素,另一個是表示下一個結點的對象引用(即指針)。
步驟如下:
(1)頭結點的構造(設置指針域即可)
(2)非頭結點的構造
(3)獲得當前結點的指針域
(4)獲得當前結點數據域的值
(5)設置當前結點的指針域
(6)設置當前結點數據域的值
注:類似於get和set方法,成員變量是數據域和指針域。
代碼實現:
(1)List.java:(鏈表本身也是線性表,只不過物理存儲上不連續)
//線性表接口 public interface List { //獲得線性表長度 public int size(); //判斷線性表是否為空 public boolean isEmpty(); //插入元素 public void insert(int index, Object obj) throws Exception; //刪除元素 public void delete(int index) throws Exception; //獲取指定位置的元素 public Object get(int index) throws Exception; }
(2)Node.java:結點類
//結點類 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 Node getNext() { return this.next; } //獲得當前結點數據域的值 public Object getElement() { return this.element; } //設置當前結點的指針域 public void setNext(Node nextval) { this.next = nextval; } //設置當前結點數據域的值 public void setElement(Object obj) { this.element = obj; } public String toString() { return this.element.toString(); } }
2、單鏈表類:
單鏈表類的成員變量至少要有兩個:一個是頭指針,另一個是單鏈表中的數據元素個數。但是,如果再增加一個表示單鏈表當前結點位置的成員變量,則有些成員函數的設計將更加方便。
代碼實現:
(3)LinkList.java:單向鏈表類(核心代碼)
1 //單向鏈表類 2 public class LinkList implements List { 3 4 Node head; //頭指針 5 Node current;//當前結點對象 6 int size;//結點個數 7 8 //初始化一個空鏈表 9 public LinkList() 10 { 11 //初始化頭結點,讓頭指針指向頭結點。並且讓當前結點對象等於頭結點。 12 this.head = current = new Node(null); 13 this.size =0;//單向鏈表,初始長度為零。 14 } 15 16 //定位函數,實現當前操作對象的前一個結點,也就是讓當前結點對象定位到要操作結點的前一個結點。 17 //比如我們要在a2這個節點之前進行插入操作,那就先要把當前節點對象定位到a1這個節點,然后修改a1節點的指針域 18 public void index(int index) throws Exception 19 { 20 if(index <-1 || index > size -1) 21 { 22 throw new Exception("參數錯誤!"); 23 } 24 //說明在頭結點之后操作。 25 if(index==-1) //因為第一個數據元素結點的下標是0,那么頭結點的下標自然就是-1了。 26 return; 27 current = head.next; 28 int j=0;//循環變量 29 while(current != null&&j<index) 30 { 31 current = current.next; 32 j++; 33 } 34 35 } 36 37 @Override 38 public void delete(int index) throws Exception { 39 // TODO Auto-generated method stub 40 //判斷鏈表是否為空 41 if(isEmpty()) 42 { 43 throw new Exception("鏈表為空,無法刪除!"); 44 } 45 if(index <0 ||index >size) 46 { 47 throw new Exception("參數錯誤!"); 48 } 49 index(index-1);//定位到要操作結點的前一個結點對象。 50 current.setNext(current.next.next); 51 size--; 52 } 53 54 @Override 55 public Object get(int index) throws Exception { 56 // TODO Auto-generated method stub 57 if(index <-1 || index >size-1) 58 { 59 throw new Exception("參數非法!"); 60 } 61 index(index); 62 63 return current.getElement(); 64 } 65 66 @Override 67 public void insert(int index, Object obj) throws Exception { 68 // TODO Auto-generated method stub 69 if(index <0 ||index >size) 70 { 71 throw new Exception("參數錯誤!"); 72 } 73 index(index-1);//定位到要操作結點的前一個結點對象。 74 current.setNext(new Node(obj,current.next)); 75 size++; 76 } 77 78 @Override 79 public boolean isEmpty() { 80 // TODO Auto-generated method stub 81 return size==0; 82 } 83 84 @Override 85 public int size() { 86 // TODO Auto-generated method stub 87 return this.size; 88 } 89 90 91 }
3、測試類:(單鏈表的應用)
使用單鏈表建立一個線性表,依次輸入十個0-99之間的隨機數,刪除第5個元素,打印輸出該線性表。
(4)Test.java:
1 public class Test { 2 3 public static void main(String[] args) throws Exception { 4 // TODO Auto-generated method stub 5 LinkList list = new LinkList(); 6 for (int i = 0; i < 10; i++) { 7 int temp = ((int) (Math.random() * 100)) % 100; 8 list.insert(i, temp); 9 System.out.print(temp + " "); 10 } 11 12 list.delete(4); 13 System.out.println("\n------刪除第五個元素之后-------"); 14 for (int i = 0; i < list.size; i++) { 15 System.out.print(list.get(i) + " "); 16 } 17 } 18 19 }
運行效果:
四、開發可用的鏈表:
對於鏈表實現,Node類是整個操作的關鍵,但是首先來研究一下之前程序的問題:Node是一個單獨的類,那么這樣的類是可以被用戶直接使用的,但是這個類由用戶直接去使用,沒有任何的意義,即:Node這個類有用,但是不能讓用戶去用,只能讓LinkList類去調用,內部類Node中完成。
於是,我們需要把Node類定義為內部類,並且在Node類中去完成addNode和delNote等操作。使用內部類的最大好處是可以和外部類進行私有操作的互相訪問。
注:內部類訪問的特點是:內部類可以直接訪問外部類的成員,包括私有;外部類要訪問內部類的成員,必須先創建對象。
1、增加數據:
- public Boolean add(數據 對象)
代碼實現:
(1)LinkList.java:(核心代碼)
1 public class LinkList { 2 private Node root; //定義一個根節點 3 4 //方法:增加節點 5 public boolean add(String data) { 6 7 if (data == null) { // 如果添加的是一個空數據,那增加失敗 8 return false; 9 } 10 11 // 將數據封裝為節點,目的:節點有next可以處理關系 12 Node newNode = new Node(data); 13 // 鏈表的關鍵就在於根節點 14 if (root == null) { //如果根節點是空的,那么新添加的節點就是根節點。(第一次調用add方法時,根節點當然是空的了) 15 root = newNode; 16 } else { 17 root.addNode(newNode); 18 19 } 20 21 return true; 22 23 } 24 25 26 //定義一個節點內部類(假設要保存的數據類型是字符串) 27 //比較好的做法是,將Node定義為內部類,在這里面去完成增刪、等功能,然后由LinkList去調用增、刪的功能 28 class Node { 29 private String data; 30 private Node next; //next表示:下一個節點對象(單鏈表中) 31 32 public Node(String data) { 33 this.data = data; 34 } 35 36 public void addNode(Node newNode) { 37 38 //下面這段用到了遞歸,需要反復理解 39 if (this.next == null) { // 遞歸的出口:如果當前節點之后沒有節點,說明我可以在這個節點后面添加新節點 40 this.next = newNode; //添加新節點 41 } else { 42 this.next.addNode(newNode); //向下繼續判斷,直到當前節點之后沒有節點為止 43 44 } 45 } 46 } 47 }
代碼解釋:
14行:如果我們第一次調用add方法,那根結點肯定是空的,此時add的是根節點。
當繼續調用add方法時,此時是往根節點后面添加數據,需要用到遞歸(42行),這個遞歸需要在內部類中去完成。遞歸這段代碼需要去反復理解。
(2)LinkListDemo.java:
public class LinkListDemo { public static void main(String[] args) { LinkList list = new LinkList(); boolean flag = list.add("haha"); System.out.println(flag); } }
運行效果:
2、增加多個數據:
- public boolean addAll(數據 對象 [] )
上面的操作是每次增加了一個對象,那么如果現在要求增加多個對象呢,例如:增加對象數組。可以采用循環數組的方式,每次都調用add()方法。
在上面的(1)LinkList.java中加入如下代碼:
1 //方法:增加一組數據 2 public boolean addAll(String data[]) { // 一組數據 3 for (int x = 0 ; x < data.length ; x ++) { 4 if (!this.add(data[x])) { // 只要有一次添加不成功,那就是添加失敗 5 return false ; 6 } 7 } 8 return true ; 9 }
3、統計數據個數:
- public int size()
在一個鏈表之中,會保存多個數據(每一個數據都被封裝為Node類對象),那么要想取得這些保存元素的個數,可以增加一個size()方法完成。
具體做法如下:
在上面的(1)LinkList.java中增加一個統計的屬性count:
private int size ; // 統計個數
當用戶每一次調用add()方法增加新數據的時候應該做出統計:(下方第18行代碼)
1 //添加節點 2 public boolean add(String data) { 3 4 if (data == null) { // 如果添加的是一個空數據,那增加失敗 5 return false; 6 } 7 8 // 將數據封裝為節點,目的:節點有next可以處理關系 9 Node newNode = new Node(data); 10 // 鏈表的關鍵就在於根節點 11 if (root == null) { //如果根節點是空的,那么新添加的節點就是根節點。(第一次調用add方法時,根節點當然是空的了) 12 root = newNode; 13 } else { 14 root.addNode(newNode); 15 16 } 17 18 this.size++; 19 return true; 20 21 }
而size()方法就是簡單的將count這個變量的內容返回:
//獲取數據的長度 public int size() { return this.size; }
4、判斷是否是空鏈表:
- public boolean isEmpty()
所謂的空鏈表指的是鏈表之中不保存任何的數據,實際上這個null可以通過兩種方式判斷:一種判斷鏈表的根節點是否為null,另外一個是判斷保存元素的個數是否為0。
在LinkList.java中添加如下代碼:
//判斷是否為空鏈表 public boolean isEmpty() { return this.size == 0; }
5、查找數據是否存在:
- public boolean contains(數據 對象)
現在如果要想查詢某個數據是否存在,那么基本的操作原理:逐個盤查,盤查的具體實現還是應該交給Node類去處理,但是在盤查之前必須有一個前提:有數據存在。
在LinkList.java中添加查詢的操作:
1 //查詢數據是否存在 2 public boolean contains(String data) { // 查找數據 3 // 根節點沒有數據,查找的也沒有數據 4 if (this.root == null || data == null) { 5 return false; // 不需要進行查找了 6 } 7 return this.root.containsNode(data); // 交給Node類處理 8 }
緊接着,在Node類之中,完成具體的查詢,查詢的流程:
判斷當前節點的內容是否滿足於查詢內容,如果滿足返回true;
如果當前節點的內容不滿足,則向后繼續查,如果已經沒有后續節點了,則返回false。
代碼實現:
1 //判斷節點是否存在 2 public boolean containsNode(String data) { // 查找數據 3 if (data.equals(this.data)) { // 與當前節點數據吻合 4 return true; 5 } else { // 與當前節點數據不吻合 6 if (this.next != null) { // 還有下一個節點 7 return this.next.containsNode(data); 8 } else { // 沒有后續節點 9 return false; // 查找不到 10 } 11 } 12 }
6、刪除數據:
- public boolean remove(數據 對象)
在LinkList.java中加入如下代碼:
1 //方法:刪除數據 2 public boolean remove(String data) { //要刪除的節點,假設每個節點的data都不一樣 3 4 if (!this.contains(data)) { //要刪除的數據不存在 5 return false; 6 } 7 8 if (root != null) { 9 if (root.data.equals(data)) { //說明根節點就是需要刪除的節點 10 root = root.next; //讓根節點的下一個節點成為根節點,自然就把根節點頂掉了嘛(不像數組那樣,要將后面的數據在內存中整體挪一位) 11 } else { //否則 12 root.removeNode(data); 13 } 14 } 15 size--; 16 return true; 17 18 }
注意第2代碼中,我們是假設刪除的這個String字符串是唯一的,不然就沒法刪除了。
刪除時,我們需要從根節點開始判斷,如果根節點是需要刪除的節點,那就直接刪除,此時下一個節點變成了根節點。
然后,在Node類中做節點的刪除:
//刪除節點 public void removeNode(String data) { if (this.next != null) { if (this.next.data.equals(data)) { this.next = this.next.next; } else { this.next.removeNode(data); } } }
7、輸出所有節點:
在LinkList.java中加入如下代碼:
1 //輸出所有節點 2 public void print() { 3 if (root != null) { 4 System.out.print(root.data); 5 root.printNode(); 6 System.out.println(); 7 } 8 }
然后,在Node類中做節點的輸出:
1 //輸出所有節點 2 public void printNode() { 3 if (this.next != null) { 4 System.out.print("-->" + this.next.data); 5 this.next.printNode(); 6 } 7 }
8、取出全部數據:
- public 數據 [] toArray()
對於鏈表的這種數據結構,最為關鍵的是兩個操作:刪除、取得全部數據。
在LinkList類之中需要定義一個操作數組的腳標:
private int foot = 0; // 操作返回數組的腳標
在LinkList類中定義返回數組,必須以屬性的形式出現,只有這樣,Node類才可以訪問這個數組並進行操作:
private String [] retData ; // 返回數組
在LinkList類之中增加toArray()的方法:
1 //方法:獲取全部數據 2 public String[] toArray() { 3 if (this.size == 0) { 4 return null; // 沒有數據 5 } 6 this.foot = 0; // 清零 7 this.retData = new String[this.size]; // 開辟數組大小 8 this.root.toArrayNode(); 9 return this.retData; 10 }
修改Node類的操作,增加toArrayNode()方法:
1 //獲取全部數據 2 public void toArrayNode() { 3 LinkList.this.retData[LinkList.this.foot++] = this.data; 4 if (this.next != null) { 5 this.next.toArrayNode(); 6 } 7 }
不過,按照以上的方式進行開發,每一次調用toArray()方法,都要重復的進行數據的遍歷,如果在數據沒有修改的情況下,這種做法是一種非常差的做法,最好的做法是增加一個修改標記,如果發現數據增加了或刪除的話,表示要重新遍歷數據。
private boolean changeFlag = true ; // changeFlag == true:數據被更改了,則需要重新遍歷 // changeFlag == false:數據沒有更改,不需要重新遍歷
然后,我們修改LinkList類中的toArray()方法:(其他代碼保持不變)
//方法:獲取全部數據 public String[] toArray() { if (this.size == 0) { return null; // 沒有數據 } this.foot = 0; // 清零 if (this.changeFlag == true) { // 內容被修改了,需要重新取 this.retData = new String[this.size]; // 開辟數組大小 this.root.toArrayNode(); } return this.retData; }
9、根據索引位置取得數據:
- public 數據 get(int index)
在一個鏈表之中會有多個節點保存數據,現在要求可以取得指定節點位置上的數據。但是在進行這一操作的過程之中,有一個小問題:如果要取得數據的索引超過了數據的保存個數,那么是無法取得的。
在LinkList類之中,增加一個get()方法:
1 //方法:根據索引取得數據 2 public String get(int index) { 3 if (index > this.size) { // 超過個數 4 return null; // 返回null 5 } 6 this.foot = 0; // 操作foot來定義腳標 7 return this.root.getNode(index); 8 }
在Node類之中配置getNode()方法:
1 //根據索引位置獲取數據 2 public String getNode(int index) { 3 if (LinkList.this.foot++ == index) { // 當前索引為查找數值 4 return this.data; 5 } else { 6 return this.next.getNode(index); 7 } 8 }
10、清空鏈表:
- public void clear()
所有的鏈表被root拽着,這個時候如果root為null,那么后面的數據都會斷開,就表示都成了垃圾:
//清空鏈表 public void clear() { this.root = null; this.size = 0; }
總結:
上面的10條方法中,LinkList的完整代碼如下:
1 /** 2 * Created by smyhvae on 2015/8/27. 3 */ 4 5 public class LinkList { 6 7 private int size; 8 private Node root; //定義一個根節點 9 10 private int foot = 0; // 操作返回數組的腳標 11 private String[] retData; // 返回數組 12 private boolean changeFlag = true; 13 // changeFlag == true:數據被更改了,則需要重新遍歷 14 // changeFlag == false:數據沒有更改,不需要重新遍歷 15 16 17 //添加數據 18 public boolean add(String data) { 19 20 if (data == null) { // 如果添加的是一個空數據,那增加失敗 21 return false; 22 } 23 24 // 將數據封裝為節點,目的:節點有next可以處理關系 25 Node newNode = new Node(data); 26 // 鏈表的關鍵就在於根節點 27 if (root == null) { //如果根節點是空的,那么新添加的節點就是根節點。(第一次調用add方法時,根節點當然是空的了) 28 root = newNode; 29 } else { 30 root.addNode(newNode); 31 32 } 33 34 this.size++; 35 return true; 36 37 } 38 39 40 //方法:增加一組數據 41 public boolean addAll(String data[]) { // 一組數據 42 for (int x = 0; x < data.length; x++) { 43 if (!this.add(data[x])) { // 只要有一次添加不成功,那就是添加失敗 44 return false; 45 } 46 } 47 return true; 48 } 49 50 //方法:刪除數據 51 public boolean remove(String data) { //要刪除的節點,假設每個節點的data都不一樣 52 53 if (!this.contains(data)) { //要刪除的數據不存在 54 return false; 55 } 56 57 if (root != null) { 58 if (root.data.equals(data)) { //說明根節點就是需要刪除的節點 59 root = root.next; //讓根節點的下一個節點成為根節點,自然就把根節點頂掉了嘛(不像數組那樣,要將后面的數據在內存中整體挪一位) 60 } else { //否則 61 root.removeNode(data); 62 } 63 } 64 size--; 65 return true; 66 67 } 68 69 //輸出所有節點 70 public void print() { 71 if (root != null) { 72 System.out.print(root.data); 73 root.printNode(); 74 System.out.println(); 75 } 76 } 77 78 79 //方法:獲取全部數據 80 public String[] toArray() { 81 if (this.size == 0) { 82 return null; // 沒有數據 83 } 84 this.foot = 0; // 清零 85 this.retData = new String[this.size]; // 開辟數組大小 86 this.root.toArrayNode(); 87 return this.retData; 88 } 89 90 91 //獲取數據的長度 92 public int size() { 93 return this.size; 94 } 95 96 //判斷是否為空鏈表 97 public boolean isEmpty() { 98 return this.size == 0; 99 } 100 101 //清空鏈表 102 public void clear() { 103 this.root = null; 104 this.size = 0; 105 } 106 107 108 //查詢數據是否存在 109 public boolean contains(String data) { // 查找數據 110 // 根節點沒有數據,查找的也沒有數據 111 if (this.root == null || data == null) { 112 return false; // 不需要進行查找了 113 } 114 return this.root.containsNode(data); // 交給Node類處理 115 } 116 117 118 //方法:根據索引取得數據 119 public String get(int index) { 120 if (index > this.size) { // 超過個數 121 return null; // 返回null 122 } 123 this.foot = 0; // 操作foot來定義腳標 124 return this.root.getNode(index); 125 } 126 127 128 //定義一個節點內部類(假設要保存的數據類型是字符串) 129 //比較好的做法是,將Node定義為內部類,在這里面去完成增刪、等功能,然后由LinkList去調用增、刪的功能 130 class Node { 131 private String data; 132 private Node next; //next表示:下一個節點對象(單鏈表中) 133 134 public Node(String data) { 135 this.data = data; 136 } 137 138 //添加節點 139 public void addNode(Node newNode) { 140 141 //下面這段用到了遞歸,需要反復理解 142 if (this.next == null) { // 遞歸的出口:如果當前節點之后沒有節點,說明我可以在這個節點后面添加新節點 143 this.next = newNode; //添加新節點 144 } else { 145 this.next.addNode(newNode); //向下繼續判斷,直到當前節點之后沒有節點為止 146 147 } 148 } 149 150 151 //判斷節點是否存在 152 public boolean containsNode(String data) { // 查找數據 153 if (data.equals(this.data)) { // 與當前節點數據吻合 154 return true; 155 } else { // 與當前節點數據不吻合 156 if (this.next != null) { // 還有下一個節點 157 return this.next.containsNode(data); 158 } else { // 沒有后續節點 159 return false; // 查找不到 160 } 161 } 162 } 163 164 165 //刪除節點 166 public void removeNode(String data) { 167 if (this.next != null) { 168 if (this.next.data.equals(data)) { 169 this.next = this.next.next; 170 } else { 171 this.next.removeNode(data); 172 } 173 } 174 175 } 176 177 //輸出所有節點 178 public void printNode() { 179 if (this.next != null) { 180 System.out.print("-->" + this.next.data); 181 this.next.printNode(); 182 } 183 } 184 185 //獲取全部數據 186 public void toArrayNode() { 187 LinkList.this.retData[LinkList.this.foot++] = this.data; 188 if (this.next != null) { 189 this.next.toArrayNode(); 190 } 191 } 192 193 194 //根據索引位置獲取數據 195 public String getNode(int index) { 196 if (LinkList.this.foot++ == index) { // 當前索引為查找數值 197 return this.data; 198 } else { 199 return this.next.getNode(index); 200 } 201 } 202 203 204 } 205 }
四、單鏈表的效率分析:
在單鏈表的任何位置上插入數據元素的概率相等時,在單鏈表中插入一個數據元素時比較數據元素的平均次數為:
刪除單鏈表的一個數據元素時比較數據元素的平均次數為:
因此,單鏈表插入和刪除操作的時間復雜度均為O(n)。另外,單鏈表讀取數據元素操作的時間復雜度也為O(n)。
2、順序表和單鏈表的比較:
順序表:
優點:主要優點是支持隨機讀取,以及內存空間利用效率高;
缺點:主要缺點是需要預先給出數組的最大數據元素個數,而這通常很難准確作到。當實際的數據元素個數超過了預先給出的個數,會發生異常。另外,順序表插入和刪除操作時需要移動較多的數據元素。
單鏈表:
優點:主要優點是不需要預先給出數據元素的最大個數。另外,單鏈表插入和刪除操作時不需要移動數據元素;
缺點:主要缺點是每個結點中要有一個指針,因此單鏈表的空間利用率略低於順序表的。另外,單鏈表不支持隨機讀取,單鏈表取數據元素操作的時間復雜度為O(n);而順序表支持隨機讀取,順序表取數據元素操作的時間復雜度為O(1)。