1.21 Go之链表操作

1.21 Go之链表操作

什么是链表

定义:

链表是物理存储单元上非连续、非顺序的存储结构

特点:

数据元素的逻辑顺序是通过链表中的指针链接次序实现的

概括:

链表是一种数据存储结构,通过指针指向定义它的链接次序。有点像区块链。只不过一个链表的结构体拥有的元素比区块链的区块少

链表的每个点称为节点,一个节点包括:

  1. 数据域

  2. 指针域

链表的优缺点

优点:

免在使用数组时需要预先知道数据大小的缺点

充分利用计算机内存空间,实现灵活的内存动态管理

缺点:

失去了数组随机读取的优点

空间开销比较大

链表的种类

在之前的数据结构一章讲过,链表属于线性表的一种。链表的种类有:

  • 单向链表

  • 双向链表

一个完整的链表需要包含的元素

  • 首元节点

  • 头节点

  • 指针

一个节点包括:

  • 数据

  • 指针

 

头节点在链表中的好处:

  • 首元结点的地址保存在头结点的指针域中,对链表的第一个数据元素的操作与其他数据元素相同,无需进行特殊处理。

  • 无论链表是否为空,头指针都是指向头结点的非空指针,若链表为空的话,那么头结点的指针域为空

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)
}
// 此答案有误

 

posted @ 2022-02-28 09:29  俊king  阅读(466)  评论(0)    收藏  举报