Go实现双向链表的示例代码

本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。

目录

1、链表

  • 1.1 说明
  • 1.2 单向链表
  • 1.3 循环链表
  • 1.4 双向链表

2、redis队列

  • 2.1 说明
  • 2.2 应用场景
  • 2.3 演示

3、Go双向链表

  • 3.1 说明
  • 3.2 实现

4、总结

5、参考文献

  • 1、链表
  • 1.1 说明

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

链表有很多种不同的类型:单向链表,双向链表以及循环链表。

优势:

可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。链表允许插入和移除表上任意位置上的节点。

劣势:

由于链表增加了节点指针,空间开销比较大。链表一般查找数据的时候需要从第一个节点开始每次访问下一个节点,直到访问到需要的位置,查找数据比较慢。

用途:

常用于组织检索较少,而删除、添加、遍历较多的数据。

如:文件系统、LRU cache、Redis 列表、内存管理等。

1.2 单向链表

链表中最简单的一种是单向链表,

一个单向链表的节点被分成两个部分。它包含两个域,一个信息域和一个指针域。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址,而最后一个节点则指向一个空值。单向链表只可向一个方向遍历。

单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问哪个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。

1.3 循环链表

循环链表是与单向链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是像单链表那样置为NULL。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域的值等于表头指针时,说明已到表尾。而非象单链表那样判断链域的值是否为NULL。

1.4 双向链表

双向链表其实是单链表的改进,当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域(当此“连接”为最后一个“连接”时,指向空值或者空列表);一个存储直接前驱结点地址,一般称之为左链域(当此“连接”为第一个“连接”时,指向空值或者空列表)。

2、redis队列

2.1 说明

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

Redis 列表使用两种数据结构作为底层实现:双端列表(linkedlist)、压缩列表(ziplist)

通过配置文件中(list-max-ziplist-entries、list-max-ziplist-value)来选择是哪种实现方式

在数据量比较少的时候,使用双端链表和压缩列表性能差异不大,但是使用压缩列表更能节约内存空间

redis 链表的实现源码 redis src/adlist.h

2.2 应用场景

消息队列,秒杀项目

秒杀项目:

提前将需要的商品码信息存入 Redis 队列,在抢购的时候每个用户都从 Redis 队列中取商品码,由于 Redis 是单线程的,同时只能有一个商品码被取出,取到商品码的用户为购买成功,而且 Redis 性能比较高,能抗住较大的用户压力。

2.3 演示

如何通过 Redis 队列中防止并发情况下商品超卖的情况。

假设:

网站有三件商品需要卖,我们将数据存入 Redis 队列中

1、 将三个商品码(10001、10002、10003)存入 Redis 队列中

# 存入商品
RPUSH commodity:queue 10001 10002 10003

2、 存入以后,查询数据是否符合预期

# 查看全部元素
LRANGE commodity:queue 0 -1

# 查看队列的长度
LLEN commodity:queue

3、 抢购开始,获取商品码,抢到商品码的用户则可以购买(由于 Redis 是单线程的,同一个商品码只能被取一次

# 出队
LPOP commodity:queue

这里了解到 Redis 列表是怎么使用的,下面就用 Go 语言实现一个双向链表来实现这些功能。

3、Go双向链表

3.1 说明

这里只是用 Go 语言实现一个双向链表,实现:查询链表的长度、链表右端插入数据、左端取数据、取指定区间的节点等功能( 类似于 Redis 列表的中的 RPUSH、LRANGE、LPOP、LLEN功能 )。

3.2 实现

节点定义

双向链表有两个指针,分别指向前一个节点和后一个节点

链表表头 prev 的指针为空,链表表尾 next 的指针为空

// 链表的一个节点
type ListNode struct {
  prev *ListNode // 前一个节点
  next *ListNode // 后一个节点
  value string  // 数据
}

// 创建一个节点
func NewListNode(value string) (listNode *ListNode) {
  listNode = &ListNode{
    value: value,
  }

  return
}

// 当前节点的前一个节点
func (n *ListNode) Prev() (prev *ListNode) {
  prev = n.prev

  return
}

// 当前节点的前一个节点
func (n *ListNode) Next() (next *ListNode) {
  next = n.next

  return
}

// 获取节点的值
func (n *ListNode) GetValue() (value string) {
  if n == nil {

    return
  }
  value = n.value

  return
}

定义一个链表

链表为了方便操作,定义一个结构体,可以直接从表头、表尾进行访问,定义了一个属性 len ,直接可以返回链表的长度,直接查询链表的长度就不用遍历时间复杂度从 O(n) 到 O(1)。

// 链表
type List struct {
  head *ListNode // 表头节点
  tail *ListNode // 表尾节点
  len int    // 链表的长度
}

// 创建一个空链表
func NewList() (list *List) {
  list = &List{
  }
  return
}

// 返回链表头节点
func (l *List) Head() (head *ListNode) {
  head = l.head

  return
}

// 返回链表尾节点
func (l *List) Tail() (tail *ListNode) {
  tail = l.tail

  return
}

// 返回链表长度
func (l *List) Len() (len int) {
  len = l.len

  return
}

在链表的右边插入一个元素

// 在链表的右边插入一个元素
func (l *List) RPush(value string) {

  node := NewListNode(value)

  // 链表未空的时候
  if l.Len() == 0 {
    l.head = node
    l.tail = node
  } else {
    tail := l.tail
    tail.next = node
    node.prev = tail

    l.tail = node
  }

  l.len = l.len + 1

  return
}

从链表左边取出一个节点

// 从链表左边取出一个节点
func (l *List) LPop() (node *ListNode) {

  // 数据为空
  if l.len == 0 {

    return
  }

  node = l.head

  if node.next == nil {
    // 链表未空
    l.head = nil
    l.tail = nil
  } else {
    l.head = node.next
  }
  l.len = l.len - 1

  return
}

通过索引查找节点

通过索引查找节点,如果索引是负数则从表尾开始查找。

自然数和负数索引分别通过两种方式查找节点,找到指定索引或者是链表全部查找完则查找完成。

// 通过索引查找节点
// 查不到节点则返回空
func (l *List) Index(index int) (node *ListNode) {

  // 索引为负数则表尾开始查找
  if index < 0 {
    index = (-index) - 1
    node = l.tail
    for true {
      // 未找到
      if node == nil {

        return
      }

      // 查到数据
      if index == 0 {

        return
      }

      node = node.prev
      index--
    }
  } else {
    node = l.head
    for ; index > 0 && node != nil; index-- {
      node = node.next
    }
  }

  return
}

返回指定区间的元素

// 返回指定区间的元素
func (l *List) Range(start, stop int) (nodes []*ListNode) {
  nodes = make([]*ListNode, 0)

  // 转为自然数
  if start < 0 {
    start = l.len + start
    if start < 0 {
      start = 0
    }
  }

  if stop < 0 {
    stop = l.len + stop
    if stop < 0 {
      stop = 0
    }
  }

  // 区间个数
  rangeLen := stop - start + 1
  if rangeLen < 0 {

    return
  }

  startNode := l.Index(start)
  for i := 0; i < rangeLen; i++ {
    if startNode == nil {
      break
    }

    nodes = append(nodes, startNode)
    startNode = startNode.next
  }

  return
}

4、总结

到这里关于链表的使用已经结束,介绍链表是有哪些(单向链表,双向链表以及循环链表),也介绍了链表的应用场景(Redis 列表使用的是链表作为底层实现),最后用 Go 实现了双向链表,演示了链表在 Go 语言中是怎么使用的,大家可以在项目中更具实际的情况去使用。

5、参考文献

维基百科 链表

github redis

项目地址:go 实现队列

https://github.com/link1st/link1st/tree/master/linked

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Go语言单链表实现方法

    本文实例讲述了Go语言单链表实现方法.分享给大家供大家参考.具体如下: 1. singlechain.go代码如下: 复制代码 代码如下: ////////// //单链表 -- 线性表 package singlechain //定义节点 type Node struct {     Data int     Next *Node } /* * 返回第一个节点 * h 头结点  */ func GetFirst(h *Node) *Node {     if h.Next == nil {  

  • golang双链表的实现代码示例

    双链表的实现 基本概念 每一个节点都存储上一个和下一个节点的指针 实现思路 创建一个节点结构体 每个节点都有上节点指针与下节点指针 每个节点都有一个key => value 创建一个链表结构体 链表容量大小属性 链表大小属性 链表锁, 实现并发安全 链表头节点 链表尾节点 实现链表操作方法 添加头部节点操作AppendHead 添加尾部节点操作AppendTail 追加尾部节点操作Append 插入任意节点操作Insert 删除任意节点操作Remove 删除头部节点操作RemoveHead 删除

  • Go实现双向链表的示例代码

    本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表. 目录 1.链表 1.1 说明 1.2 单向链表 1.3 循环链表 1.4 双向链表 2.redis队列 2.1 说明 2.2 应用场景 2.3 演示 3.Go双向链表 3.1 说明 3.2 实现 4.总结 5.参考文献 1.链表 1.1 说明 链表(Linked list)是一种常见的基础数据

  • JavaScript双向链表实现LRU缓存算法的示例代码

    目录 目标 什么是LRU 简介 硬件支持 寄存器 栈 代码实现 思路 链表节点数据结构 链表数据结构 LRUCache数据结构 完整代码 测试 目标 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构. 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 . void put(int key

  • 使用go实现一个超级mini的消息队列的示例代码

    目录 前言 目的 设计 协议 队列 broker 删除消息 生产者 消费者 启动 总结 前言 趁着有空余时间,就想着撸一个mini的生产-消费消息队列,说干就干了.自己是个javer,这次实现,特意换用了go.没错,是零基础上手go,顺便可以学学go. 前置知识: go基本语法 消息队列概念,也就三个:生产者.消费者.队列 目的 没想着实现多复杂,因为时间有限,就mini就好,mini到什么程度呢 使用双向链表数据结构作为队列 有多个topic可供生产者生成消息和消费者消费消息 支持生产者并发写

  • C语言实现线性动态(单向)链表的示例代码

    目录 什么是链表 为什么不用结构体数组 链表的操作 创建表 删除元素 插入元素 代码及运行结果 什么是链表 链表是数据结构里面的一种,线性链表是链表的一种,线性链表的延伸有双向链表和环形链表.在编程语言中优化数据结构可以在处理大数据时大大降低程序的空间复杂性和时间复杂性.这里我只用一个简单的例子——线性单向链表为例,说明C语言是如何实现该结构的. 链表的元素是由结构体来实现struct table *p.结构体中有一个成员是结构体指针struct table *next,而这个结构体指针的类型和

  • C语言带头双向循环链表的示例代码

    目录 前言 结构分析 链表的基本操作实现 创建节点 初始化链表 链表销毁 打印链表 链表尾插 链表尾删 链表头插 链表头删 链表查找 链表pos位置前面去插入 删除pos位置 链表判空 代码复用 总代码及头文件 前言 对于链表来说,不只有单链表这一个品种: 链表有很多种形态 按方向分:单向.双向 按带不带头:带头.不带头 按循环:循环.不循环 1.单向或则双向: 2.带头或者不带头: 3.循环或者不循环: 组合排列一下的话,链表一共有8种形态!!! 今天我们就来学习一下结构最复杂的带头双向循环链

  • Java实现双链表的示例代码

    目录 一.双向链表是什么 二.具体方法实现 定义结点 下标访问异常 获取链表长度 打印链表 清空链表 头插法 尾插法 指定位置插入 查找元素 删除第一次出现的关键字 删除所有值为key的节点 三.完整代码 一.双向链表是什么 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点.一般我们都构造双向循环链表. LinkedList底层就是一个双向链表,我们来实现一个双向链表. 这

  • 利用正则表达式将字符串分组示例代码

    前言 最近工作中遇到一个问题,需求是碰到'122333<<<<'这种字符串,要将其连贯的部分取出,得出['1', '22', '333', '<<<<']这样的列表,能想到的常规办法,遍历字符串,后一个与前一个逐个比较,这样真的很麻烦!又想到了另外两种方法,话不多说了,来一起看看详细的示例代码: 一.实际上可以借助itertools模块的groupby()方法来处理: import itertools Str = '122333<<<<

  • vue语法之拼接字符串的示例代码

    本文介绍了vue语法之拼接字符串的示例代码,分享给大家,具体如下. 先来一行代码: <div class="swiper-slide" v-for="item in message"> <img v-bind:src="['xxx(需要拼接的字符串)'+item.picurl]" alt="" width="100%" height="245" /> </d

  • node文字生成图片的示例代码

    今天老板提了需求,要在服务端生成邀请卡,嗯-,简单的说就是把要这张: 变成差多这样的: 后端搞ruby的哥们搞了个html转图片,说转得太慢了,我就把这坑接下来了 所以睡前就倒腾了下,搞了个简单的实现 解决思路 文字转svg -> svg转png -> 合并图片 相关轮子 images Node.js 轻量级跨平台图像编解码库,不需要额外安装依赖 text-to-svg 文字转svg svg2png svg转png图片 示例代码 'use strict'; const fs = require

  • highcharts 在angular中的使用示例代码

    本文介绍了highcharts 在angular中的使用示例代码,分享给大家.具体如下: 网址 https://www.hcharts.cn/demo/highcharts https://github.com/pablojim/highcharts-ng 安装依赖 npm install highcharts-ng --save 引入依赖 'highcharts/highcharts.src.js', 'highcharts-ng/dist/highcharts-ng.min.js' 注入依赖

随机推荐