鏈表與數組同樣同爲線性數據結構,很多編程語言也自帶了鏈表的實現,鏈表能夠存放不一樣數據類型的數據;
與數組不一樣,數組佔用內存結構必須爲連續,而鏈表則不須要內存空間爲連續的;鏈表由多個節點鏈接而成,每一個節點除了存儲當前節點的值外還存有指向鏈表中下一個節點的地址;鏈表也有多種結構:單鏈表、雙向鏈表、循環鏈表等等;
單鏈表的數據結構以下所示:node
經過上圖能夠看出鏈表的基本結構,每一個節點由值與地址兩部分組成,地址存儲鏈中下一個節點的地址,由此將全部 節點串聯起來;golang
相比數組不須要連續的內存空間,系統存在內存碎片也可以使用鏈表;
如須要申請200MB大小的數組,但當前內存中沒有足夠大連續的內存空間,就算當前可用內存有200MB也不會申請成功;
而鏈表則不一樣,只要可用內存有200MB就可以使用,不須要內存塊爲連續的它經過指針將節點(內存塊)串聯起來;
以前所說的動態數組其實只是僞動態當靜態數組滿時經過內部的resize建立一個新靜態數組進行擴容,而鏈表爲真動態;編程
一、 數組插入刪除
數組爲保持內存的連續性插入刪除須要移動N個元素,時間複雜度爲O(n)
二、 鏈表插入刪除
鏈表未使用連續內存空間則也不須要移動元素,因此速度會比數組快很多;
因爲鏈表使用的不時連續存儲空間,因此不能像數組同樣經過尋址公式就能訪問到指定的元素,須要經過一個一個遍歷每一個節點才能找到對應的節點,因此數組的隨機訪問時間複雜度爲O(1),而鏈表爲O(n);數組
插入刪除 O(n) O(1) 隨機訪問 O(1) O(n)數據結構
鏈表的插入與刪除比數組快很多,鏈表並不是使用連續的內存空間,不須要去維護內存連續性,就插入與刪除而言雙向鏈表又比單鏈表性能要好;編程語言
要在節點c後插入O節點,須要從第一個節點開始遍歷鏈表知道找到節點c而後執行以下操做:性能
O.next = c.next c.next = O
刪除節點d須要找到該節點的前一個節點,找到節點c後執行以下操做:指針
c.next = d.next d.next = null
因爲單鏈表只存儲下一個節點地址須要遍歷鏈表節點才能找到前一個節點,而雙向鏈表既存儲了下一個節點地址又存儲上一個節點地址,雙向鏈表性能比單鏈表要好;code
一、如上面所說的插入元素在c節點後插入O節點,操做時須要注意要先執行O指向c的下一個節點,當一上來就執行c.next=O此時鏈表c節點後的節點就斷開丟失了;
二、鏈表刪除時須要注意要釋放節點,如上示例:執行c.next=d.next後如未執行d.next=null此時d節點就未被釋放掉,雖然鏈表中未有節點指向該節點,但該節點並未斷開鏈接;
三、使用頭節點簡化插入、刪除操做;blog
type Node struct { e interface{} next *Node } type LinkedList struct { head *Node size int } /** 往鏈表頭添加元素 */ func (l *LinkedList) AddFirst(e interface{}) { l.Add(0, e) } func (l *LinkedList) Add(index int, e interface{}) error { if index < 0 || index > l.size { return errors.New("Add failed. Illegal index.") } prev := l.head for i := 0; i < index; i++ { prev = prev.next } prev.next = &Node{e: e, next: prev.next} l.size++ return nil } /** nil->3 2 1 查找指定索引節點 */ func (l *LinkedList) Find(index int) *Node { cur := l.head.next for i := 0; i < index; i++ { cur = cur.next } return cur } /** 刪除指定位置節點 */ func (l *LinkedList) Remove(index int) error { node := l.head.next if node != nil { if prev := l.Find(index - 1); prev != nil { node = prev.next if node != nil { prev.next = node.next node.next = nil } } } return nil }