1.21 Go之链表操作
定义:
链表是物理存储单元上非连续、非顺序的存储结构
特点:
数据元素的逻辑顺序是通过链表中的指针链接次序实现的
概括:
链表是一种数据存储结构,通过指针指向定义它的链接次序。有点像区块链。只不过一个链表的结构体拥有的元素比区块链的区块少
链表的每个点称为节点,一个节点包括:
- 
数据域 
- 
指针域 
链表的优缺点
优点:
免在使用数组时需要预先知道数据大小的缺点
充分利用计算机内存空间,实现灵活的内存动态管理
缺点:
失去了数组随机读取的优点
空间开销比较大
链表的种类
在之前的数据结构一章讲过,链表属于线性表的一种。链表的种类有:
- 
单向链表 
- 
双向链表 
一个完整的链表需要包含的元素
- 
首元节点 
- 
头节点 
- 
指针 
一个节点包括:
- 
数据 
- 
指针 

头节点在链表中的好处:
- 
首元结点的地址保存在头结点的指针域中,对链表的第一个数据元素的操作与其他数据元素相同,无需进行特殊处理。 
- 
无论链表是否为空,头指针都是指向头结点的非空指针,若链表为空的话,那么头结点的指针域为空 
Go定义单链表
使用Struct关键字定义节点:
package main
/*
定义节点
 */
type Node struct {
    // 数据域
    Data int
    // 指针域
    Next *Node
    
    /*
    Data 用来存放结点中的有用数据
    Next 是指针类型的成员,它指向 Node struct 类型数据,也就是下一个结点的数据类型
     */
}
为链表赋值并遍历节点:
package main
import "fmt"
/*
定义一个遍历链表节点的方法
 */
func ShowNode(p *Node) {
    // 节点不为空就打印
    for p != nil {
        if p != nil {
            fmt.Println(*p) // '*'符号+变量名获取到变量的指针地址
            // 指针域指向下一个节点的数据域
            p = p.Next
        }
    }
}
/*
调用该方法
 */
func main() {
    // 定义头节点
    var head = new(Node)
    // 定义该节点的数据域和指针域
    head.Data = 1
    // 定义第二个节点
    var node1 = new(Node)
    // 定义数据域
    node1.Data = 2
    // 讲头节点指向第二个节点
    head.Next = node1
    // 定义第三个节点
    var node2 = new(Node)
    // 定义数据域
    node2.Data = 3
    // 将第二个节点与第三个节点连接
    node1.Next = node2
    //调用打印链表的方法
    ShowNode(head)
}
插入节点
头插法
本质:
每次都在链表的头部插入数据,相当于修改新插入的节点都是头节点。
实现:
只需要构造一个头指针,该指针只指向头节点即可。每次插入节点都将该指针指向最新的节点
package main
import (
    "fmt"
)
/*
定义节点
 */
type Node2 struct {
    // 数据域
    Data int
    // 指针域
    next *Node2
}
/*
定义遍历打印方法:
传入指针变量,每次修改指针变量进行读取
 */
func ShowNode2(p *Node2) {
    for p != nil {
        fmt.Println(*p) // 打印指针在此处的地址值
        // 移动指针指向下一个节点
        p = p.next
    }
}
/*
头插法插入链表
 */
func main() {
    // 定义头节点
    var head = new(Node2)
    // 定义头节点数据域
    head.Data = 0
    // 定义头指针,该指针只会指向头节点,这是一个指针变量,指向节点
    var top *Node2
    // 头指针指向头节点,并且始终指向头节点
    top = head
    // 循环添加节点,当每次添加节点成功了以后修改头指针的指向,让其指向头节点
    for i := 1; i < 10; i++ {
        // 新建节点
        var node = Node2{Data: i}
        // 每次添加成功都将头指针指向新增的节点
        node.next = top
        // 给头指针重新赋值
        top = &node
    }
    // 调用打印节点方法
    ShowNode2(top)
}
尾插法
本质:
每次插入数据直接修改头节点的指针值,将新加入的节点的指针赋值给头节点
实现:
package main
import (
    "fmt"
)
/*
定义节点
 */
type Node2 struct {
    // 数据域
    Data int
    // 指针域
    next *Node2
}
/*
定义遍历打印方法:
传入指针变量,每次修改指针变量进行读取
 */
func ShowNode2(p *Node2) {
    for p != nil {
        fmt.Println(*p) // 打印指针在此处的地址值
        // 移动指针指向下一个节点
        p = p.next
    }
}
/*
头插法插入链表
 */
func main() {
    // 定义头节点
    var head = new(Node2)
    // 定义头节点数据域
    head.Data = 0
    // 定义头指针,该指针只会指向头节点,这是一个指针变量,指向节点
    var top *Node2
    // 头指针指向头节点,并且始终指向头节点
    top = head
    // 尾插法插入节点数据
    for i := 1; i < 10; i++ {
        // 新建节点
        var node = Node2{Data: i}
        // 每次添加成功都修改头节点的指针域的值,将新插入的节点指针值赋值给头节点
        (*top).next = &node
        // 重新给头节点赋值
        top = &node
    }
    // 调用打印节点方法
    ShowNode2(top)
}
访问元素,链表没有数组高效。因为数组的存储地址是连续的。
新增和删除元素,链表比数组高效很多。因为链表本身的地址就是不连续的。
循环链表
本质:
链表最后的节点的指针域指向头节点的数据域。实现的时候就需要固定一个头节点
实现:
package main
import "fmt"
/*
定义节点
 */
type Node3 struct {
    // 数据域
    Data int
    
    //指针域
    next *Node3
}
/*
循环打印指针节点内容方法
 */
func ShowNode3(p *Node3) {
    // 如果p不为空就打印内容
    for p != nil {
        fmt.Println(*p)
        // 移动指针指向下一个节点
        p = p.next
    }
}
/*
双指针方法定位循环链表结构
 */
func main() {
    // 定义头节点
    var head = new(Node3)
    // 赋值头节点的数据域
    head.Data = 0
    // 定义两个指针,一个作为头指针一个作为移动指针
    var headTop *Node3
    var moveNode *Node3
    headTop = head
    moveNode = head
    // 循环添加节点,使用尾插法
    for i := 1; i < 10; i++ {
        // 新建节点
        var node = Node3{Data: i}
        // 每次修改移动指针的指针域指向新建的节点的地址
        (*moveNode).next = &node
        // 重新给移动节点赋值
        moveNode = &node
        if i == 9 {
            // 将移动节点的指针域指向头节点
            (*moveNode).next = headTop
        }
    }
    ShowNode3(headTop)
}
双向链表
本质:
链表的一个节点不止有后继指针,还有前驱指针。前驱指针指向前一个节点。后继指针指向下一个节点
特点:
优点:
- 
可以支持双向遍历 
缺点:
- 
需要额外的两个空间来存储后继结点和前驱结点的地址。存储同样多的数据,双向链表要比单链表占用更多的内存空间 
package main
import "fmt"
/*
定义双向链表节点信息
 */
type Node4 struct {
    // 前驱节点
    prev *Node4
    // 数据域
    Data int
    // 后继节点
    next *Node4
}
/*
遍历节点的方法
 */
func ShowNode4(p *Node4) {
    for p != nil {
        fmt.Println(*p)
        // 打印前驱节点如果存在
        if p.prev != nil {
            fmt.Println(*p.prev)
        }
        // 将指针移向下一个节点
        p = p.next
    }
}
/*
遍历双向链表
 */
func main() {
    // 定义头节点
    var head = new(Node4)
    // 定义头节点的数据域
    head.Data = 0
    // 定义头节点的指针域
    head.prev = nil
    // 定义头指针
    var headTop *Node4
    // 添加节点使用尾插法
    for i := 1; i < 10; i++ {
        // 新建节点
        var node = Node4{Data: i}
        // 修改头指针指向新建节点
        (*headTop).next = &node
        // 修改新建的节点的前驱指针指向头指针
        node.prev = headTop
        // 更新头指针
        headTop = &node
    }
    ShowNode4(headTop)
}
// 此答案有误
    It's a lonely road!!!
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号