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

     

  • 相关阅读:
    循环图片 yi
    给大家一个经典的.net情感故事 yi
    [东邪西毒][程序员版][原版][剧情] yi
    Sqlite 使用笔记 中文显示为乱码 yi
    sql2005安装过程,(不装C盘) yi
    Visual Studio 2010 美女与程序员的爱情网剧全集 yi
    IT行业几大职业病 yi
    标准化操作
    【ActiveMQ Tuning】Serializing to Disk
    我的山寨敏捷四季之春
  • 原文地址:https://www.cnblogs.com/JunkingBoy/p/15944243.html
Copyright © 2020-2023  润新知