首先要知道为什么引入链表,以及什么情况下使用链表,链表有哪些缺点?
我们使用链表就是为了避免插入和删除数据时带来的开销,同时链表可以不连续(就是在内存中的地址不一定是连续的),所以对于频繁的增加和删除节点,链表是不需要进行大量的数据迁移(相对于数组),但是对于链表的访问,时间复杂度是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
}
到了这里,单链表的基本操作就已经结束了,我们可以结合这些基本的操作来完成复杂的操作,比如,链表的反转,有兴趣的小伙伴可以试试哦, 觉得我写的有问题的小伙伴也可以评论我们来聊聊,共同进步呢。
其实在这里写博客,也算是对自己的一个监督吧,加油,争取周更。