队列、堆栈和优先队列介绍及Redis实现

前言

队列、堆栈和优先队列是编程中常见的数据结构。本文首先简单介绍一下这几种数据结构,然后介绍如何用Redis实现这些数据结构。

数据结构简介

队列

普通队列有以下几个特性

  • 先进先出(FIFO)
  • 支持PUSH/POP,PUSH从尾端增加元素,POP从前端弹出元素
  • 容量不受限制

从普通队列可以衍生出定长队列,它比普通队列多出以下特性

  • 有固定的容量(最大长度)
  • 向满载的队列PUSH会失败

从定长队列可以衍生出可溢出的定长队列,它比定长队列多出以下特性

  • 向满载的队列PUSH会成功,但前端的元素会被挤出

以上三种队列都是单向队列,只能从尾端PUSH,从前端POP。它们又分别可以衍生出各自的双向版本。

双向 队列/定长队列/可溢出的定长队列

比单向版本多处以下特性

  • PUSH/POP均可以从前端/后端进行
  • 对于可溢出的双向队列,满载时从一端PUSH,会从另一端将元素挤出

堆栈

普通堆栈有以下特性

  • 后进先出 (LIFO)
  • 支持PUSH/POP,从尾端追加/弹出元素
  • 容量不受限制

衍生出的定长堆栈多处以下特性

  • 容量固定
  • 向满载的堆栈PUSH会失败

定长堆栈没有可溢出版本,因为堆栈只能从尾端PUSH/POP。

优先队列

和普通队列相比,优先队列中的元素都有一个优先级,队列中的元素按优先级排序,优先级高的元素先被弹出。类似队列,优先队列有三种版本,他们的特性也类似队列。

  • 普通优先队列
  • 定长优先队列
  • 可溢出的定长优先队列

其中可溢出的优先队列,满载时的PUSH操作会将优先级低的元素挤出。

Redis实现

思路

队列和堆栈都可以用Redis的list数据类型实现,因为list支持以下操作

  • lpush/rpush,从左侧/右侧追加元素
  • lpop/rpop,从左侧/右侧弹出元素
  • llen,获取队列的长度
  • lindex,获取某个位置的元素

定长和可溢出版本,Redis原生的list类型无法实现,需要写一点代码实现它们。Redis支持Lua脚本编程,我们就用它来实现这些版本。

优先队列可以用Redis的ZSET(SORTED SET)数据类型实现,ZSET为有序集合,集合中的元素都有一个分值,分值越低优先级越高。我们同样用Lua脚本实现定长和可溢出版本。ZSET支持以下操作

  • zadd,向集合增加元素
  • zrange,按优先级范围获取元素
  • zrem,从集合中删除元素

实现细节

队列

rpush + lpop即可实现一个普通队列。

对于定长队列,push前需检查队列长度,如果满载则返回错误码,否则追加元素。

对于可溢出的定长队列,push后检查队列长度,如果长度超出容量,则从前端将元素弹出。

堆栈双向队列的实现细节与之类似。

优先队列

  • PUSH操作用zadd实现
  • POP操作用zrange + zrem实现

增强特性

为了充分利用Redis的强大之处,我们还可以增加一些有趣的特性

  • 队列批量PUSH:list的push命令原生支持批量追加元素,而且时间复杂度为O(1)
  • 队列批量POP:list的pop命令不支持批量弹出,需要我们用循环实现批量POP
  • 优先队列批量PUSH:zset的zadd命令支持批量追加元素
  • 优先队列批量POP:zrange批量获取 + zrem循环删除
  • 检查元素是否在队列中:lindex获取某个位置的元素,循环检查即可得到结果
  • 检查元素是否在优先队列中:zrank获取某个元素的优先级顺序,即可得到结果

代码实例

我们看一下如何用Lua脚本实现可溢出的定长队列

PUSH

local cap=tonumber(ARGV[1])
for i,k in ipairs(ARGV) do
  if i > 1 then
    redis.call('rpush',KEYS[1],k)
  end
end
local len=redis.call('llen',KEYS[1])
local o={}
while len > cap do
  o[#o + 1]=redis.call('lpop',KEYS[1])
  len=len - 1
end
return { len,o }

POP

local o={}
for i=1,tonumber(ARGV[1]) do
  o[#o + 1]=redis.call('lpop',KEYS[1])
end
return o

代码已开源至github

Python版本

PHP版本

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容