什么是链表

  链表是一种数据结构,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。

  链表通常由一连串节点组成,节点可以在运行时动态生成,每个节点包含任意的实例数据(data fields)和存储下一个或下一个结点地址的指针域

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

  使用链表结构可以避免在使用数组时需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大

  常用的链表方式有三种:
  1. 单链表
  2. 双向链表
  3. 循环链表

链表的基础概念

链表包含头节点(必须)、头指针、首元节点、其他节点

  • 首元结点:就是链表中存储第一个元素的结点,如下图中 a1 的位置。
  • 头结点:它是在首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以存储链表的长度或者其它的信息,也可以为空不存储任何信息。
  • 头指针:它是指向链表中第一个结点的指针。若链表中有头结点,则头指针指向头结点;若链表中没有头结点,则头指针指向首元结点。

头结点在链表中不是必须的,但增加头结点有以下几点好处:

  • 增加了头结点后,首元结点的地址保存在头结点的指针域中,对链表的第一个数据元素的操作与其他数据元素相同,无需进行特殊处理。
  • 增加头结点后,无论链表是否为空,头指针都是指向头结点的非空指针,若链表为空的话,那么头结点的指针域为空

image.png

单向链表

单链表的形式如下图所示
image.png

实现一个单链表

package main

import (
    "fmt"
)

//定义一个 HeroNode
type HeroNode struct {
    no int
    name  string
    nickname string
    next  *HeroNode //这个表示指向下一个结点
}

//给链表插入一个结点
//编写第一种插入方法,在单链表的最后加入
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    //1. 先找到该链表的最后这个结点
    //2. 创建一个辅助结点
    temp := head
    for {
        if temp.next == nil { //表示找到最后
            break
        }
        temp = temp.next //  让 temp 不断的指向下一个结点
    }

    //3. 将 newHeroNode 加入到链表的最后
    temp.next = newHeroNode
}

//给链表插入一个结点
//编写第 2 种插入方法,根据 no  的编号从小到大插入..【实用】
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    //1. 找到适当的结点
    //2. 创建一个辅助结点[跑龙套, 帮忙]
    temp := head
    flag := true
    //让插入的结点的 no,和 temp 的下一个结点的 no 比较
    for {

        if temp.next == nil {
            //说明到链表的最后
            break
        } else if temp.next.no >= newHeroNode.no {
            //说明 newHeroNode 就应该插入到 temp 后面
            break
        } else if temp.next.no == newHeroNode.no {
            //说明我们额链表中已经有这个 no,就不然插入. flag = false
            break
        }
        temp = temp.next
    }

    if !flag {
        fmt.Println("对不起,已经存在 no=", newHeroNode.no)
        return
    } else {
        newHeroNode.next = temp.next
        temp.next = newHeroNode
    }
}

//删除一个结点
func DelHerNode(head *HeroNode,id int){
    temp:=head
    flag:=false
    //找到要删除节点的no,和temp的下一个结点no比较
    for {
        if temp.next == nil {
            //说明到了链表的最后
            break
        }else if temp.next.no == id {
            //找到了要删除的节点
            flag = true
            break
        }
        temp = temp.next
    }
    if flag{
        temp.next = temp.next.next
    }else {
        fmt.Println("id 不存在")
    }
}

//显示链表的所有结点信息
func ListHeroNode(head *HeroNode) {
    //1. 创建一个辅助结点
    temp := head

    // 先判断该链表是不是一个空的链表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return
    }
    //2. 遍历这个链表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.next.no, temp.next.name, temp.next.nickname)
        //判断是否链表后
        temp = temp.next
        if temp.next == nil {
            break
        }
    }
}

func main() {
    //1. 先创建一个头结点
    head := &HeroNode{}
    hero1 := &HeroNode{
        no : 1,
        name : "张三",
        nickname : "法外狂徒",
    }
    hero2 := &HeroNode{
        no : 2,
        name : "王五",
        nickname : "隔壁老王",
    }
    hero3 := &HeroNode{
        no : 3,
        name : "李四",
        nickname : "好人老四",
    }
    //3. 加入后展示
    InsertHeroNode2(head, hero3)
    InsertHeroNode2(head, hero1)
    InsertHeroNode2(head, hero2)
    ListHeroNode(head)
    fmt.Println()

    // 4.删除一个节点
    DelHerNode(head,2)
    ListHeroNode(head)
}

执行输出结果

>go run main.go
[1 , 张三 , 法外狂徒]==>[2 , 王五 , 隔壁老王]==>[3 , 李四 , 好人老四]==>
[1 , 张三 , 法外狂徒]==>[3 , 李四 , 好人老四]==>

双向链表

双向链表的形式如下图所示
image.png

实现一个双向链表

package main

import (
    "fmt"
)

//定义一个 HeroNode
type HeroNode struct {
    no int
    name  string
    nickname string
    pre   *HeroNode //这个表示指向上一个结点
    next  *HeroNode //这个表示指向下一个结点
}

//给双向链表插入一个结点
//编写第一种插入方法,最后加入
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode) {
    //1. 先找到该链表的最后这个结点
    //2. 创建一个辅助结点
    temp := head
    for {
        if temp.next == nil { //表示找到最后
            break
        }
        temp = temp.next //  让 temp 不断的指向下一个结点
    }

    //3. 将 newHeroNode 加入到链表的最后
    temp.next = newHeroNode
    newHeroNode.pre = temp
}

//给双向链表插入一个结点
//编写第 2 种插入方法,根据 no  的编号从小到大插入..【实用】
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode) {
    //1. 找到适当的结点
    //2. 创建一个辅助结点[跑龙套, 帮忙]
    temp := head
    flag := true
    //让插入的结点的 no,和 temp 的下一个结点的 no 比较
    for {
        if temp.next == nil {
            //说明到链表的最后
            break
        } else if temp.next.no >= newHeroNode.no {
            //说明 newHeroNode 就应该插入到 temp 后面
            break
        } else if temp.next.no == newHeroNode.no {
            //说明我们额链表中已经有这个 no,就不然插入.
            flag = false
            break
        }
        temp = temp.next
    }

    if !flag {
        fmt.Println("对不起,已经存在 no=", newHeroNode.no)
        return
    } else {
        newHeroNode.next = temp.next
        newHeroNode.pre = temp
        if temp.next != nil {
            temp.next.pre = newHeroNode
        }
        temp.next = newHeroNode
    }
}

//双向链表种删除一个结点
func DelHerNode(head *HeroNode,id int){
    temp:=head
    flag:=false
    //找到要删除节点的no,和temp的下一个结点no比较
    for {
        if temp.next == nil {
            //说明到了链表的最后
            break
        }else if temp.next.no == id {
            //找到了要删除的节点
            flag = true
            break
        }
        temp = temp.next
    }
    if flag{
        temp.next = temp.next.next
        if temp.next != nil {
            temp.next.pre = temp
        }
    }else {
        fmt.Println("id 不存在")
    }
}

//显示链表的所有结点信息
func ListHeroNode(head *HeroNode) {
    //1. 创建一个辅助结点
    temp := head

    // 先判断该链表是不是一个空的链表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return
    }
    //2. 遍历这个链表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.next.no, temp.next.name, temp.next.nickname)
        //判断是否链表后
        temp = temp.next
        if temp.next == nil {
            break
        }
    }
}

func ListHeroNode2(head *HeroNode) {
    //1. 创建一个辅助结点
    temp := head

    // 先判断该链表是不是一个空的链表
    if temp.next == nil {
        fmt.Println("空空如也。。。。")
        return
    }
    //2. 让temp定位到双向链表的最后节点
    for  {
        if temp.next == nil {
            break
        }
        temp = temp.next
    }

    //3.遍历链表
    for {
        fmt.Printf("[%d , %s , %s]==>", temp.no, temp.name, temp.nickname)
        //判断是否链表头
        temp = temp.pre
        if temp.pre == nil {
            break
        }
    }
}

func main() {
    //1. 先创建一个头结点
    head := &HeroNode{}
    hero1 := &HeroNode{
        no : 1,
        name : "张三",
        nickname : "法外狂徒",
    }
    hero2 := &HeroNode{
        no : 2,
        name : "王五",
        nickname : "隔壁老王",
    }
    hero3 := &HeroNode{
        no : 3,
        name : "李四",
        nickname : "好人老四",
    }
    //3. 加入后展示
    InsertHeroNode2(head, hero3)
    InsertHeroNode2(head, hero1)
    InsertHeroNode2(head, hero2)
    fmt.Println("\n正向打印链表")
    ListHeroNode(head)

    fmt.Println("\n逆向打印链表")
    ListHeroNode2(head)

    // 4.删除一个节点
    DelHerNode(head,2)
    fmt.Println("\n删除id 2后正向打印链表")
    ListHeroNode(head)
    fmt.Println("\n删除id 2后逆向打印链表")
    ListHeroNode2(head)
}

执行的结果

>go run main.go

正向打印链表
[1 , 张三 , 法外狂徒]==>[2 , 王五 , 隔壁老王]==>[3 , 李四 , 好人老四]==>
逆向打印链表
[3 , 李四 , 好人老四]==>[2 , 王五 , 隔壁老王]==>[1 , 张三 , 法外狂徒]==>

删除id 2后正向打印链表
[1 , 张三 , 法外狂徒]==>[3 , 李四 , 好人老四]==>
删除id 2后逆向打印链表
[3 , 李四 , 好人老四]==>[1 , 张三 , 法外狂徒]==>

苑h余
1 声望0 粉丝

下一篇 »
go 稀疏数组