在說鏈表的時候,就會常說另外一個概念:數組.
數組和鏈表,常常會拿到一起做比較.這篇文章也是,咱們來說說數組和鏈表.
數組最大的一個特點就是,需要一塊連續的內存空間.假設現在內存空間剩餘了 1MB ,但是它不是連續的,這個時候申請一個大小爲 1MB 的數組,會告訴你申請失敗,因爲這個內存空間不連續.
鏈表最大的一個特點是,不需要一塊連續的內存空間.還是上面那個例子,如果申請的不是大小爲 1MB 的數組,而是鏈表,就會申請成功.
如果只是理解到了這個層面,你是不是會覺得,我以後一直用鏈表這種數據結構就可以了?不不不,數組也有它自己的優勢.
首先數組簡單易用,又因爲使用的是連續內存空間,就可以藉助 CPU 的緩存機制,預讀數組中的數據,因而訪問效率更高,所以在插入,刪除操作比較少,而查詢比較多的情況下,使用數組是比較有優勢的.
鏈表在內存中不是連續存儲,對 CPU 緩存機制不夠友好,也就沒辦法進行有效預讀.所以鏈表適用於在插入,刪除操作比較多的情況下使用.
鏈表分爲單鏈表,循環鏈表,和雙向鏈表.
對於單鏈表來說,它的第一個節點也就是頭結點記錄着鏈表的基地址,而最後一個節點也就是尾節點則指向一個空地址 NULL ,循環鏈表也可以理解成特殊的單鏈表,只不過尾節點由原來指向一個空地址 NULL 改爲了指向頭結點.
單鏈表:
循環鏈表:
但是在實際開發中,更加常用的鏈表結構是:雙向鏈表.
它的結構是這樣的:
我們能夠看到它的特點是:佔用內存較多,支持雙向遍歷.因爲它有兩個指針,所以相對單鏈表,一個數據就會多佔用一些內存.
既然它佔用內存較多,爲什麼在實際開發中還比較常用呢,這裏面有一個思想在裏面,咱們具體來講講.
我們知道,單鏈表,雙鏈表在刪除的時候,時間複雜度爲 O(1) ,但是在實際開發中它的時間複雜度並不是這樣,爲什麼呢?
這樣想,一般在做數據刪除的時候,你的操作是怎樣的?
首先,查找在節點中「值等於給定某個值」的節點,找到之後再做刪除對吧?也就是說在刪除之前,是需要做查找這個工作的.而單向鏈表和雙向鏈表在查找的時候時間複雜度爲 O(n) ,因爲它爲了找到這個要刪除的元素,需要將所有的元素都遍歷一遍.將上面過程梳理一下就是,查找時間複雜度爲 O(n) ,刪除時間複雜度爲 O(1) ,總的時間複雜度爲 O(n) .
以上過程在雙鏈表中是怎樣的呢?因爲雙鏈表支持雙向遍歷,所以查找這個操作對它來說時間複雜度爲 O(1) ,因爲它是雙向遍歷,所以在查找元素時,不需要將所有的元素進行遍歷,刪除時時間複雜度爲 O(1) ,總的時間複雜度爲 O(1) .
因爲雙向鏈表的時間複雜度爲 O(1) ,所以在開發中它是比較受歡迎的.而在這其中體現的一個最重要的思想就是:空間換時間.
當內存空間相對時間來說不是那麼重要的話,那我們是不是就可以忽略次要的因素,着重解決主要矛盾?
最後來實現一個比較常見的單鏈表操作—單鏈表反轉
首先上代碼實現:
/**
* 鏈表反轉
* @author 鄭璐璐
* @date 2019/11/16 19:55
*/
public class ReverseList {
public static class Node{
private int data;
private Node next;
public Node(int data , Node next){
this.data=data;
this.next=next;
}
public int getData(){
return data;
}
}
public static void main(String[] args){
// 初始化單鏈表
Node node5=new Node(5,null);
Node node4=new Node(4,node5);
Node node3=new Node(3,node4);
Node node2=new Node(2,node3);
Node node1=new Node(1,node2);
// 調用反轉方法
Node reverse=reverse(node1);
System.out.println(reverse);
}
/**
*單鏈表反轉
* @param list 爲傳入的單鏈表
* @author 鄭璐璐
* @date 2019/11/16 19:56
*/
public static Node reverse(Node list){
Node current=list, // 定義 current 爲當前鏈表
afterReverse=null; // 定義 afterReverse 爲轉換之後的新鏈表,初始爲 null
// 當前鏈表不爲空,進行反轉操作
while (current!=null){
// 1. 保存當前節點的 next 指針指向的鏈表
Node next=current.next;
// 2. 將當前節點的 next 指針指向反轉之後的新鏈表
current.next=afterReverse;
// 3. 保存當前的鏈表狀態到新鏈表中
afterReverse=current;
// 4. 將當前節點指針後移一位,進行下一次循環
current=next;
}
return afterReverse;
}
}
接下來斷點調試,看看每次結果:
初始狀態
第一次循環結束
第二次循環結束
第三次循環結束
第四次循環結束
第五次循環結束
在寫這篇文章的時候,特別是單鏈表反轉那一塊,考慮了很久,借鑑網上思路做出來,有的思路真的是很巧妙.
以上,感謝您的閱讀~