首先要知道爲什麼引入鏈表,以及什麼情況下使用鏈表,鏈表有哪些缺點?
我們使用鏈表就是爲了避免插入和刪除數據時帶來的開銷,同時鏈表可以不連續(就是在內存中的地址不一定是連續的),所以對於頻繁的增加和刪除節點,鏈表是不需要進行大量的數據遷移(相對於數組),但是對於鏈表的訪問,時間複雜度是O(n),不像數組可以根據下表以時間複雜度O(1)來訪問。對於時間複雜度,可能leetcode刷的比較多的同學深有體會,時間複雜度往往決定了你的代碼運行的快慢,超過了百分之多少的人,這個很有成就感的,哈哈。
我們來看一下單鏈表長什麼樣子:
有一個頭指針,指向第一個元素,每個節點中包含指向下一個節點的地址(這個很重要),以此類推,最後一個節點指向爲空。這就構成了一個單鏈表,其中節點的地址不一定是連續的。
這裏提一下爲什麼要有頭指針(Head),頭指針其實也是一個節點,這個節點中可以不包含數據,但是一定要有指向首元節點的指針,試想一下,如果沒有頭指針,我們在a1前插入節點和刪除a1就會比較麻煩,所以引入頭指針對我們操作整個單鏈表會非常的方便。
對於go語言我們應該怎麼使用代碼描述一個單鏈表呢?
//定義一個節點
type Node struct {
e interface{} //節點的數據
next *Node //下一個節點的地址
}
//定義一個單鏈表
type LinkList struct {
head *Node //鏈表的頭指針
size int //鏈表的長度
}
對於單鏈表,我們來實現下面幾種簡單的操作(鏈表複雜的操作,也是以這幾種爲基礎的)
//初始化單鏈表
func (L *LinkList) Init() *LinkList{}
//獲取鏈表的長度
func (L *LinkList) GetSize() int{}
//判斷鏈表是否爲空
func (L *LinkList) IsEmpty() bool{}
//單鏈表插入數據
func (L *LinkList) Insert(e interface{}, index int){}
//單鏈表刪除數據
func (L *LinkList) Delete(index int){}
//單鏈表查詢元素
func (L *LinkList) FindEle(e interface{}) (index int ){}
下面我們來依次講解這幾種操作:
初始化鏈表:
初始化鏈表就是生成頭指針,有沒有數據都可以,但是一定要有地址能表示這個頭指針。
//初始化鏈表 func (L *LinkList) Init() *LinkList{ L.size = 0 //生成的單鏈表沒有數據,因此size爲0 L.head = new(Node) //我們生成一個空Node來表示 return L }
獲取鏈表的長度:
直接返回鏈表的size即可 。
func (L *LinkList) GetSize() int{
return L.size
}
判斷鏈表是否爲空:
這個也是很簡單的,就不解釋了
func (L *LinkList) IsEmpty() bool{
return L.size == 0
}
單鏈表插入數據:
單鏈表插入數據, 需要經過兩步,一:把待插入節點的next地址指向插入位置的後一個節點;二:把待插入位置的前一個節點的next地址指向我們的待插入數據,這樣就完成了一個節點的插入,覺得我說的不清楚的可以去看書本的描述。
//單鏈表插入數據
func (L *LinkList) Insert(e interface{}, index int){
if index<0 || index>L.size{
panic("out of range") //確保插入的位置有效
}
preNode := L.head
for i:=0 ; i<index;i++{
preNode = preNode.next
} //移動到待插入位置
curNode := &Node{e,preNode.next} //生成插入節點,執行步驟一
preNode.next = curNode //執行步驟二
L.size++ //鏈表的大小加一
}
單鏈表的刪除:
我們刪除一個節點,只要將這個節點的前一個節點的next地址指向這個節點的next地址,即指向下一個節點。如圖所示,直接把a.next指向c即可完成b節點的刪除。
func (L *LinkList) Delete(index int){
if index<0 || index>L.size{
panic("out of range") //確保刪除位置有效
}
preNode := L.head
for i:=0;i<index-1;i++{
preNode = preNode.next //移動到待刪除節點的前一個節點
}
preNode.next = preNode.next.next //將待刪除節點的前一個節點的next指向待刪除節點的後一個節點
L.size--
}
查找元素:
查找元素,我們採用遍歷節點並判斷是否相等 的方法
func (L *LinkList) FindEle(E interface{}) int{
var index = -1 //用來存儲返回的位置,沒這個數據返回-1
preNode := L.head
for i:=0;i<L.size;i++{ //遍歷整個單鏈表
if preNode.next != nil && preNode.next.e == E{ //判斷鏈表是否遍歷完,同時判斷節點的值與要查的值是否相等
index = i
return index
}
preNode = preNode.next
}
return index
}
到了這裏,單鏈表的基本操作就已經結束了,我們可以結合這些基本的操作來完成複雜的操作,比如,鏈表的反轉,有興趣的小夥伴可以試試哦, 覺得我寫的有問題的小夥伴也可以評論我們來聊聊,共同進步呢。
其實在這裏寫博客,也算是對自己的一個監督吧,加油,爭取周更。