数据结构-队列

队列特点

  • 先进先出
  • 从队尾入队列
  • 从队头出队列
  • 队列的相关操作集中在队头与队尾

队列的接口抽象

队列与栈一样有这以下几个基本的功能接口,如下:

protocol QueueProtocol: CustomStringConvertible {
    associatedtype E
    //入队
    mutating func enQueue(element: E)
    //出队
    mutating func deQueue() -> E?
    //是否是空队列
    mutating func isEmpty() -> Bool
    //队列的长度
    mutating func size() -> Int?
    //队列的头
    mutating func front() -> E?
    //清空队列
    mutating func clear()
}

队列的种类

队列可以通过线性表来实现,基于基本队列概念,可以将队列细分为以下几种:

  1. 单端队列
    单端队列从统一的一个方向进行队列的元素的基本操作,队列的元素添加是按照统一的方向进行的。可以通过双向链表进行实现,双向链表的头指针与尾指针刚刚好配合队列的出队与入队操作。所以通过双向链表来实现其操作再合适不过了。

  2. 双端队列
    基于单端队列的扩充,不仅仅能够从一端进行相关队列元素的基础操作,同样也能够从另一端进行相关的队列功能的操作,可以通过双向链表进行实现,道理同单端队列一样。
    双端队列是能够在头尾两端添加、删除的队列。

  3. 循环队列
    队列的元素操作方向是循环的,保证其数据连续的同时能够同时充分的利用存储空间,可基于数组来实现。涉及到动态扩容以及动态下标查找的算法方案。

队列的几种实现形式

  • 单端队列

实现的基本思路:

  1. 依据队列的基本抽象接口,将其设计为协议
  2. 双向链表作为成员变量进行队列的封装并继承队列的基本抽象接口
  3. 通过双向链表的头指针来进行出队操作
  4. 通过双向链表的尾指针用来进行入队操作
  5. 通过链表的长度来间接反应队列的长度
  6. 通过链表的头结点来找到队列的头
/*
 队列:
 1. FIFO 先进先出
 2. 从尾进入,队头出来
 3.
 */

protocol QueueProtocol: CustomStringConvertible {
    associatedtype E
    mutating func enQueue(element: E)
    mutating func deQueue() -> E?
    mutating func isEmpty() -> Bool
    mutating func size() -> Int?
    mutating func front() -> E?
    mutating func clear()
}

struct SignalQueue<T: Equatable>: QueueProtocol, CustomStringConvertible {
    
    typealias E = T
    var list = DoubleList<T>()
    
    var description: String {
        return self.list.description
    }
    
    mutating func enQueue(element: T) {
        self.list.addFromLast(element: element)
    }
    
    mutating func deQueue() -> T? {
        let outNode = self.list.nodeOfIndex(index: 0) as? T
        self.list.remove(index: 0)
        return outNode
    }
    
    mutating func isEmpty() -> Bool {
        self.list.isEmpty()
    }
    
    mutating func size() -> Int? {
        self.list.size
    }
    
    mutating func front() -> T? {
        self.list.nodeOfIndex(index: 0) as? T
    }
    
    mutating func clear() {
        self.list.clear()
    }
    
}

  • 双端队列

基于单端队列进行实现,在单端队列的基础上添加头尾相关的功能。

  1. 入队分为头尾两种
  2. 出队也分为头尾两种
  3. 队头与队尾元素的获取
/*
 双端队列
 这里不要使用数组实现,因为数组的删除操作会有移位行为,影响性能,最好的采用双向链表去操作
 */

struct Deque<E: Equatable>: CustomStringConvertible {
    
    var list: DoubleList = DoubleList<E>()
    var description: String {
        
        
        var des = "length:\(list.size),front:\(String(describing: list.first?.element)),last:\(String(describing: list.last?.element)), ["
        
        var firstNode = list.first
        
        while firstNode != nil {
            des.append("\(String(describing: firstNode?.element)), ")
            firstNode = firstNode?.next
        }
        
        des.removeLast(2)
        des.append("]")
        
        return des
    }

    //从队尾入队
    mutating func enQueueRear(element: E) {
        list.addFromLast(element: element)
    }
    
    //从队头出队
    mutating func deQueueFront() -> E? {
        let firstQueueNode = list.first
        list.remove(index: 0)
        return firstQueueNode?.element
    }
    
    //从队头入队
    mutating func enQueueFront(element: E) {
        list.add(element: element)
    }
    
    //从队尾出队
    mutating func deQueueRear() -> E? {
        let lastNode = list.last
        list.remove(index: list.size - 1)
        return lastNode?.element
    }

    
    //是否为空
    mutating func isEmpty() -> Bool {
        false
    }
    
    //队列长度
    mutating func size() -> Int? {
        list.size
    }
    
    //获取队头
    mutating func front() -> E? {
        list.first?.element
    }
    
    //获取队尾
    mutating func rear() -> E? {
        list.last?.element
    }
    
    
    
    //清空队列
    mutating func clear() {
        list.clear()
    }
    
}

  • 循环队列

可基于数组实现,对数组进行相关的添加删除操作,需要注意几点:

  1. 元素在数组中的 逻辑下标真实下标 转换的操作
  2. 数组的动态扩容操作
  3. 动态扩容期间,原始数组的元素复制移动操作
  4. 队列头的更新
let DEFAULTSIZE = 5
let QUEUENULL = 0

/*
 循环队列
 */
struct CycleQueue<E: Equatable> : CustomStringConvertible, QueueProtocol{

    var description: String {
        var desStr = "Queue length:\(length), front:\(list[frontIndex]), ["
        for idx in 0..<length {
            let realIdx = realIndexInArray(index: idx)
            desStr.append("\(list[realIdx]), ")
        }
        desStr.removeLast(2)
        desStr.append("], 原始存储介质list:\(list)")
        return desStr
    }
    var defaultTypeValue: E
    var list: Array<E>
    var length = 0
    var frontIndex: Int = QUEUENULL
    
    init(defaultTypeValue: E) {
        self.defaultTypeValue = defaultTypeValue
        self.list = [E](repeating: defaultTypeValue, count: DEFAULTSIZE)
    }
    
    //找到逻辑下标在数组中的真是下标
    func realIndexInArray(index: Int) -> Int {
        var realIndex = 0
        if (frontIndex + index) >= list.count {
            realIndex = frontIndex + index - list.count
        }else {
            realIndex = frontIndex + index
        }
        return realIndex
    }
    
    //自动扩展空间内存
    mutating func autoExpandCapacity() {
        if (length + 1) > list.count {
            //按照旧的容量的两倍进行扩展
            var newList = [E](repeating: defaultTypeValue, count: list.count * 2)
            //将旧容量的数据放入到新的内存数组中
            for idx in 0..<length {
                newList[idx] = list[realIndexInArray(index: idx)]
            }
            print(newList)
            self.list = newList
            frontIndex = 0
            
            print("*重新扩容* capacity:\(self.list.count), frontIndex:\(frontIndex), list:\(self.list)")
        }
    }
    
    mutating func enQueue(element: E) {
        autoExpandCapacity()
        let rearIndex = realIndexInArray(index:length)
        list[rearIndex] = element
        length += 1
    }
    
    mutating func deQueue() -> E? {
        let elementOfIndex = list[frontIndex]
        list[frontIndex] = defaultTypeValue
        frontIndex = (frontIndex + 2) > list.count ? (frontIndex + 1 - list.count) : (frontIndex + 1)
        length -= 1
        print("移出\(elementOfIndex) at Index:\(frontIndex), queue:\(list)")
        return elementOfIndex
    }
    
    mutating func isEmpty() -> Bool {
        length == 0
    }
    
    mutating func size() -> Int? {
        length
    }
    
    mutating func front() -> E? {
        list[frontIndex]
    }
    
    mutating func clear() {
        list.removeAll()
        length = 0
    }

}

扩展

  • 如何通过栈来实现队列

栈有着先进后出的特点,队列恰恰相反。依赖于此特点,可以对栈进行两次出栈操作,就间接的完成队列的排列顺序了。实现关键点如下:

  1. 定义两个分别用于入队操作的栈 inStack 与出队操作 outStack 的栈
  2. outStack 栈中元素的来源是 inStack 的入栈
  3. outStack 栈不为空的时候,不进行两个栈的元素移动操作,主要是为了保持其队列的元素顺序不被打乱。
  4. outStack 栈为空的时候,对 inStack 中的所有元素进行出栈操作,将其出栈的元素放入到 outStack

以上的 3、4 步骤其主要目的就是为了避免来回的出队入队操作导致的元素顺序被打乱的可能。

/*
 使用栈来实现队列
 */
struct QueueUseStack<E: Equatable> : QueueProtocol {
    
    var inStack: Stack<E>?
    var outStack: Stack<E>?
    
    var description: String{
        //拷贝一份
        var copySelf = self
        var infoStr = "length:\(String(describing: copySelf.size())),front:\(String(describing: copySelf.outStack?.top())),["
        
        while !copySelf.outStack!.isEmpty() {
            infoStr.append("\(String(describing: copySelf.outStack?.pop())),")
        }
        
        while !copySelf.inStack!.isEmpty() {
            copySelf.outStack?.push(element: copySelf.inStack!.pop()!)
        }
        
        while !copySelf.outStack!.isEmpty() {
            infoStr.append("\(String(describing: copySelf.outStack?.pop())),")
        }
        infoStr.removeLast()
        infoStr.append("]")
        return infoStr
    }
    
    init(defaultTypeValue: E) {
        self.inStack = Stack<E>(arrayDefault: defaultTypeValue)
        self.outStack = Stack<E>(arrayDefault: defaultTypeValue)
    }
    
    
    mutating func enQueue(element: E) {
        self.inStack?.push(element: element)
    }
    
    mutating func deQueue() -> E? {
        if outStack!.isEmpty() {
            while !inStack!.isEmpty() {
                outStack!.push(element: inStack!.pop()!)
            }
        }
        
        return outStack?.pop()
    }
    
    mutating func isEmpty() -> Bool {
        (inStack!.size() + outStack!.size()) == 0
    }
    
    mutating func size() -> Int? {
        inStack!.size() + outStack!.size()
    }
    
    mutating func front() -> E? {
        outStack!.top()
    }
    
    mutating func clear() {
        inStack!.clear()
        outStack!.clear()
    }
    
}


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

推荐阅读更多精彩内容

  • 队列是一种特殊的线性表,只能在头尾两端进行操作 队尾(rear):只能从队尾添加元素,一般叫做 enQueue,入...
    鼬殿阅读 282评论 0 0
  • 一、什么是队列? 1.先进先出(FIFO)2.支持两个操作:入队enqueue(),放一个数据到队尾;出队dequ...
    橘子_好多灰阅读 346评论 0 4
  • 什么是队列结构: 队列是允许在一端进行插入操作,而在另一端进行删除操作的的线性表。队列是一种先进先出的(First...
    雨飞飞雨阅读 537评论 0 1
  • 一、概念 队列也是一种”操作受限“的线性表,体现在先进先出原则。 二、常见操作 入队:队列尾部放入数据出队:队列头...
    王小鹏的随笔阅读 246评论 0 0
  • 队列是线性表的一种,在操作数据元素时,和栈一样,有自己的规则:使用队列存取数据元素时,数据元素只能从表的一端进入队...
    飞扬code阅读 1,110评论 0 2