当前位置: 首页 > article >正文

Go语言中的链表与双向链表实现

链表基础

链表是一种由有限元素组成的数据结构,其中每个元素至少使用两个内存空间:一个存储实际数据,另一个存储指向下一个元素的指针,从而形成一个元素序列构成链表。链表的第一个元素称为头结点,而最后一个元素通常被称为尾结点。为了操作链表,保持对头结点的引用非常重要,因为头结点是我们访问整个链表的唯一入口。如果丢失了指向头结点的指针,将无法再次找到链表的其他元素。

链表的操作

在链表中,移除节点时,主要操作是调整要删除节点的前一个节点的指针,使其指向要删除节点的下一个节点。

Go中的链表实现

我们通过Go语言的链表实现来更好地理解链表的操作过程。

package main

import (
    "fmt"
)

// 定义链表节点结构
type Node struct {
    Value int
    Next  *Node
}

// 全局变量root,保存链表的头结点
var root = new(Node)

在以上代码中,定义了链表节点的结构,包含一个Value用于存储节点的值,一个Next指针指向链表的下一个节点。此外,还定义了一个全局变量root,用于保存链表的头结点。

添加节点

链表通常不允许重复元素,并且当链表未排序时,新节点通常添加到链表末尾。以下是添加节点的代码实现:

func addNode(t *Node, v int) int {
    if root == nil {
        t = &Node{v, nil}
        root = t
        return 0
    }
    if v == t.Value {
        fmt.Println("节点已存在:", v)
        return -1
    }
    if t.Next == nil {
        t.Next = &Node{v, nil}
        return -2
    }
    return addNode(t.Next, v)
}

addNode函数中,首先检查链表是否为空,如果为空,则将新节点作为头结点插入。接着,检查链表中是否已有待插入的值,避免重复元素。如果当前节点的Next指针为空,说明已到达链表末尾,便将新节点添加到链表的末尾。若以上情况均不满足,则递归地继续检查下一个节点。

遍历链表

遍历链表的代码如下:

func traverse(t *Node) {
    if t == nil {
        fmt.Println("-> 空链表!")
        return
    }
    for t != nil {
        fmt.Printf("%d -> ", t.Value)
        t = t.Next
    }
    fmt.Println()
}

traverse函数通过循环遍历链表,并输出每个节点的值,直到遍历到最后一个节点。

查找节点与计算链表长度

func lookupNode(t *Node, v int) bool {
    if root == nil {
        t = &Node{v, nil}
        root = t
        return false
    }
    if v == t.Value {
        return true
    }
    if t.Next == nil {
        return false
    }
    return lookupNode(t.Next, v)
}

func size(t *Node) int {
    if t == nil {
        fmt.Println("-> 空链表!")
        return 0
    }
    i := 0
    for t != nil {
        i++
        t = t.Next
    }
    return i
}

lookupNode用于检查链表中是否存在某个值,而size函数用于计算链表的长度,即节点的数量。通过递归或循环,分别遍历链表的每个节点,返回结果。

主函数测试

func main() {
    fmt.Println(root)
    root = nil
    traverse(root)
    addNode(root, 1)
    addNode(root, -1)
    traverse(root)
    addNode(root, 10)
    addNode(root, 5)
    addNode(root, 45)
    traverse(root)
    if lookupNode(root, 100) {
        fmt.Println("节点存在!")
    } else {
        fmt.Println("节点不存在!")
    }
    fmt.Println("链表长度:", size(root))
}

输出结果:

&{0 <nil>}
-> 空链表!
1 -> -1 ->
1 -> -1 -> 10 -> 5 -> 45 ->
节点不存在!
链表长度: 5

链表的优势

链表的优势在于其灵活性和实现的简单性,适合处理各种数据类型。链表在进行顺序查找时效率较高,特别是在动态数据管理(如插入和删除)时优于数组等静态数据结构。此外,链表可以动态增长,且删除节点的操作较为简单,尤其是有序链表。

双向链表

双向链表是一种特殊的链表结构,每个节点不仅有一个指向下一个节点的指针,还有一个指向前一个节点的指针。这样可以实现双向遍历。双向链表的头节点的前一个节点为nil,尾节点的下一个节点也为nil

Go中的双向链表实现

双向链表的节点定义如下:

type Node struct {
    Value    int
    Previous *Node
    Next     *Node
}

该结构体中有两个指针字段:一个指向前一个节点,一个指向下一个节点。

添加节点

func addNode(t *Node, v int) int {
    if root == nil {
        t = &Node{v, nil, nil}
        root = t
        return 0
    }
    if v == t.Value {
        fmt.Println("节点已存在:", v)
        return -1
    }
    if t.Next == nil {
        temp := t
        t.Next = &Node{v, temp, nil}
        return -2
    }
    return addNode(t.Next, v)
}

与单链表类似,双向链表的节点通常添加到链表末尾。

遍历与反向遍历

func traverse(t *Node) {
    if t == nil {
        fmt.Println("-> 空链表!")
        return
    }
    for t != nil {
        fmt.Printf("%d -> ", t.Value)
        t = t.Next
    }
    fmt.Println()
}

func reverse(t *Node) {
    if t == nil {
        fmt.Println("-> 空链表!")
        return
    }
    temp := t
    for t != nil {
        temp = t
        t = t.Next
    }
    for temp.Previous != nil {
        fmt.Printf("%d -> ", temp.Value)
        temp = temp.Previous
    }
    fmt.Printf("%d -> ", temp.Value)
    fmt.Println()
}

reverse函数实现了反向遍历,先遍历到链表末尾,再通过Previous指针反向输出节点值。

双向链表的优势

双向链表相比单链表,优势在于可以进行双向遍历,删除和插入操作更加灵活。如果丢失了头结点的指针,仍可以通过尾节点找到链表的其他节点。但双向链表需要维护两个指针,增加了存储空间的消耗和代码的复杂性。

结语

链表和双向链表作为基础的数据结构,在处理动态数据和顺序查找中具有显著优势。通过对Go语言中链表的实现,读者可以更深入理解如何在实际开发中使用这些数据结构来提高程序的性能和可维护性。


http://www.kler.cn/news/303255.html

相关文章:

  • Linux 基本指令(一)
  • Linux内核学习之 -- 系统调用open()和write()的实现笔记
  • Spring Boot集成Akka Stream快速入门Demo
  • c++stack和list 介绍
  • 20. 如何在MyBatis中处理多表关联查询?常见的实现方式有哪些?
  • 数据分析-26-时间序列预测之基于ARIMA的时间序列数据分析
  • k8s命名详解
  • Redis地理数据类型GEO
  • 通信工程学习:什么是FDMA频分多址
  • Games101笔记-线性代数(一)
  • WORD批量转换器MultiDoc Converter
  • 第 11篇 Helm 部署 RabbitMQ
  • flink的大状态复用
  • C++——一道关于多态的经典面试题
  • 宠物空气净化器应该怎么选择才能选到除毛效果好的产品
  • mysql-搭建主从复制
  • pdf怎么压缩?分享5种压缩PDF文件的方法
  • 《CSS新世界》书评
  • 使用程序集解析的方式内嵌dll到exe中
  • #名词区别篇:npx pnpm npm yarn区别
  • gitlab无法push(pre-receive hook declined)
  • 如何使用 Choreographer 进行帧率优化
  • 旅游网站开发:SpringBoot框架实战
  • 观察者模式与hook机制的联系
  • Java面试篇基础部分-Java序列化
  • 高性能缓存利器:Caffeine 在 Spring Boot 中的应用
  • 快速完成论文初稿写作的ChatGPT提示词分享
  • 怎样将vue项目 部署在ngixn的子目录下
  • linux环境下手动安装mysql
  • holynix靶机详解