链表数据结构实现

链表

介绍链表的定义以及 Golang 实现。

概念性的东西

定义

链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

简单分类

  1. 单链表,就是链表是单向的,可以一直往下找到下一个数据节点,它只有一个方向,它不能往回找。
  2. 双链表,每个节点既可以找到它之前的节点,也可以找到之后的节点,是双向的。
  3. 循环链表,就是它一直往下找数据节点,最后回到了自己那个节点,形成了一个回路。循环单链表和循环双链表的区别就是,一个只能一个方向走,一个两个方向都可以走。

GO语言实现

单链表

定义存储结构

1
2
3
4
type LinkNode struct {
data int
next *LinkNode
}

初始化

1
2
3
func InitLink() *LinkNode {
return &LinkNode{}
}

一些可能需要的函数

判断链表是否为空

因为很多时候需要判断链表是否为空,这里将其进行封装

1
2
3
4
5
6
func (l *LinkNode) IsLinkNil() bool {
if l == nil{
return true
}
return false
}
求链表的长度
1
2
3
4
5
6
7
8
func (l *LinkNode) LinkLength() int {
for i := 1; ; i++ {
if l.next == nil{
return i
}
l = l.next
}
}
打印
1
2
3
4
5
6
7
8
9
func (l *LinkNode) print() {
for true {
fmt.Printf("%d ", l.data)
if l.next == nil {
break
}
l = l.next
}
}

插入

在插入时要考虑链表是否为空,为空直接插入

1
2
3
4
func (l *LinkNode) add(data int) {
l.data = data
l.next = nil
}
头插法

因为go语言的特性,在方法中l = node的话只会在调用方生效而被调用方不生效,所以返回插入的节点使其变成头节点

对方法接收器的赋值仅传播到被调用方,不传播到调用方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (l *LinkNode) addByHead(data int) *LinkNode {
if l.IsLinkNil() {
l.add(data)
return l
}

node := &LinkNode{
data: data,
next: l,
}

return node
}

尾插法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (l *LinkNode) addByTail(data int) {
if l.IsLinkNil() {
l.add(data)
return
}

for true {
if l.next == nil {
break
}
l = l.next
}

l.next = &LinkNode{
data: data,
next: nil,
}
}
按位置插入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (l *LinkNode) addByAddr(data int, addr int) {
if l.IsLinkNil() {
l.add(data)
return
}

if addr < 2 {
fmt.Println("请选择头插法或重新选择插入位置")
return
}

for i := 1; i < addr-1; i++ {
l = l.next
}

temp := l.next
l.next = &LinkNode{
data: data,
next: temp,
}
}

查询

按值查询

查询是否有该值,并返回位置,没有则返回0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (l *LinkNode) selectByData(data int) int {
var addr = 1

if l.IsLinkNil(){
return -1
}

for true {
if l.data == data {
break
}

if l.next == nil {
return 0
}

addr++
}
return addr
}
按位置查询

查询指定位置的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func (l *LinkNode) selectByAddr(addr int) int {
if l.IsLinkNil() {
return -1
}

if l.LinkLength() < addr{
return 0
}

for i := 0; i < addr - 1; i++ {
l = l.next
}

return l.data
}

删除

主要是要判断各种情况的发生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func (l *LinkNode) moveByAddr(addr int) *LinkNode {
if l.IsLinkNil() {
return nil
}

if addr < 2 {
res := l.next
l = nil
return res
}

for i := 1; i < addr-2; i++ {
l = l.next
}

if l.next.next == nil {
temp := l.next
l.next = nil
temp = nil
return temp
}

temp := l.next.next
remove := l.next
remove = nil
l.next = temp

return remove
}

双向链表与单链表类似,只是多了个前驱节点,不进行赘述。

循环链表

go标准库的实现

定义存储结构

1
2
3
4
type Ring struct {
prev, next *Ring // 前驱后驱节点
data int
}

初始化

1
2
3
4
5
func (r *Ring) init() *Ring {
r.prev = r
r.next = r
return r
}

一些可能需要的函数

打印
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (r *Ring) print() {
if r.isRingNil() {
fmt.Println("链表为空")
return
}

head := r
fmt.Print("链表中的元素有:")
for true {
fmt.Printf("%d ", r.data)
if r.next == head {
fmt.Println()
break
}
r = r.next
}
}

插入

判断是否插入的节点为空,为空则不链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (r *Ring) Link(s *Ring) *Ring {
if r.next == nil {
return r.init()
}

n := r.Next()

if *s != (Ring{}) {
// s没有前缀节点的话则p为s本身
p := s.Prev()

r.next = s
s.prev = r
n.prev = p
p.next = n
}

return n
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func (r *Ring) Move(n int) *Ring {
if r.next == nil {
return r.init()
}

switch {
case n < 0:
for ; n < 0; n++ {
r = r.prev
}
case n > 0:
for ; n > 0; n-- {
r = r.next
}
}

return r
}

func (r *Ring) UnLink(n int) *Ring {
if n <= 0 {
return nil
}

return r.Link(r.Move(n + 1))
}

其他操作都是大同小异的。