Swift数据结构-队列Queue

声明:算法和数据结构的文章均是作者从github上翻译过来,为方便大家阅读。如果英语阅读能力强的朋友,可以直接到swift算法俱乐部查看所有原文,以便快速学习。作者同时也在学习中,欢迎交流

排队序列就是一个你只能在队伍的最后面加入新的值,从最前面取出旧的值的一串序列。这样的机制保证了最早进入序列的值,同时也会是最早出来的,就像我们平时排队办理业务一样,先到先服务!

为什么我们需要这样的机制呢?实际上,很多算法都会出现类似情况,在某一时刻你只想添加某些元素到一个临时列表里,然后过一会儿又将它们移除出去。这时候,你添加或者移除元素的顺序会影响整个算法。

队列可以提供先进先出的顺序(FIFO)。它每一次移除的元素,都是你最先放进去的元素。这里有一个非常类似的数据结构,堆栈Stack,属于后进先出的顺序(LIFO)。

比如,我们将一个数字放进队列里。

queue.enqueue(10)

现在队列的内容为 [ 10 ]。 我们再放进下一个数字:

queue.enqueue(3)

现在队列的内容变为 [ 10, 3 ]。我们继续放入新的数字:

queue.enqueue(57)

现在队列的内容变为[10,3,57]。这回,我们将队列里最先端的数字移除:

queue.dequeue

这里我们得到的返回值为10,因为它是我们最后放进去的数字。现在队列的内容变为[3,57]。

queue.dequeue

这一次我们得到的返回值为3,我们可以继续移除队列的数据。如果队列变为空,移除方程返回结果为nil,在一些执行语句里面会返回错误信。

注意: 通常情况下队列并不是最好的选择。如果对于一个数组来说,元素的添加和移除过程不是很重要的话,这里我们建议使用堆栈Stack会是更好的选择,因为堆栈Stack更简单也更高效。

代码

在swift中,队列很容易创建。简单的说它就是对一个数组进行包装,让你能够对数组添加,移除,查看数据。

public struct Queue<T> { 
    fileprivate var array = [T]() 
    public var isEmpty: Bool { 
        return array.isEmpty 
    } 
    public var count: Int { 
        return array.count 
    }
    public mutating func enqueue(_ element: T) { 
        array.append(element) 
    } 
    public mutating func dequeue() -> T? { 
        if isEmpty {
           return nil 
        } else { 
           return array.removeFirst() 
        }
     } 
    public var front: T? { 
        return array.first 
    }
}

队列算法运算效率良好,但大多数情况下并不是最优选择。

插入队列过程是一个O(1)运算过程,因为每一次在队列的最后一位插入新的数值消耗的时间是固定的,与队列当前的大小无关,这是队列算法最棒的地方。

你可能会好奇,为什么添加数值到数组里面是一个O(1)运算过程?因为在swift里,在一个数组的最末尾,swift都预留了一些空位备用。比如我们执行以下代码:

var  queue = Queue<String>()
queue.enqueue("Ada")
queue.enqueue("Steve")
queue.enqueue("Tim")

我们得到的数组应该是这样的:

["Ada", "Steve", "Tim", xxx, xxx , xxx]

这里的xxx是尚未填入数值的预留内存。添加新的数值会重写最近的一个xxx。

["Ada", "Steve", "Tim", "Grace", xxx , xxx]

这就相当于将一部分内存从一个地方复制到另一个地方,这是一个恒定运算过程。

当然,数组中的xxx个数是有限的。当最后一个xxx被使用,你还想继续添加数值,这个数组就需要重新调整大小,获取更多的空间。

调整大小包括获取新的内存,同时将现有的数据复制到新的数组里面。这是一个O(n)过程,所以这个过程有点慢。但是,因为这种情况偶尔发生,所以总体上来说,队列在添加新的数值到数组里的过程,所消耗的平均时间仍然接近O(1).

至于队列取出过程,就不太一样了。在取出数值的过程中,我们移除的是队列最前端的数值,而不是最末端。这个过程基本上一直是O(n)运算因为它需要将剩余的数据在内存上进行保留和移动。

在我们的例子中,取出第一个元素"Ada"其实是将"Steve"复制到"Ada"的位置,同时"Tim"被复制到原来"Steve"的位置,"Grace"被复制到运来"Tim"的位置:

取出前      ["Ada", "Steve", "Tim", "Grace", xxx , xxx]
                                 /        /            /
                               /        /            / 
                             /        /            /
                           /        /            /
取出后      ["Steve", "Tim", "Grace", xxx , xxx,xxx]

将所有数据在内存上进行移动一直是O(n)运算。所以在执行队列算法时候,插入数值的过程是简单高效的,但是取出的过程却有待提高。

更有效的队列算法

为了让队列算法更加有效,我们可以在预留内存位置上做文章,但是这次我们选择在队列的最前头进行内存预留。这里我们需要自己写代码去实现,因为swift本身的逻辑并没有支持我们提出来的想法。

想法如下:当我们需要从队列中取出元素的时候,我们不做数组元素的移动(慢),而是记住这个元素的位置,并且把它重写为xxx(快)。在取出"Ada"之后,数组内容如下:

[xxx, "Steve", "Tim", "Grace", xxx , xxx]

我们继续取出"Steve",数组变成:

[xxx, xxx, "Tim", "Grace", xxx , xxx]

因为在队列算法中,最前端的预留内存永远不会被用到,为了防止内存浪费,我们可以偶尔对数组重新进行大小调整,即删除前端预留内存,将"Tim"等元素往前放:

["Tim", "Grace", xxx , xxx]

这个调整过程包含了内存移动,所以这里是一个O(n)运算。但是因为它只是偶尔发生,所以,总体来说,取出过程也变成了O(1)运算。

一下代码实现了上述所说的新队列算法:

public struct Queue<T>{
   fileprivate var array = [T?]()
   fileprivate var head = 0

   public var isEmpty : Bool {
        return count == 0
   }
   public var count: Int {
    return array.count - head
  }

  public mutating func enqueue(_ element: T) {
    array.append(element)
  }

  public mutating func dequeue() -> T? {
    guard head < array.count, let element = array[head] else { return nil }

    array[head] = nil
    head += 1

    let percentage = Double(head)/Double(array.count)
    if array.count > 50 && percentage > 0.25 {
      array.removeFirst(head)
      head = 0
    }

    return element
  }

  public var front: T? {
    if isEmpty {
      return nil
    } else {
      return array[head]
    }
  }
}

因为我们需要将数组的元素重写为空,所以现在这里的数组储存对象类型为T?,而不是T。变量head是数组里面最前面的对象的索引。

对比原来的代码,我们对dequeuq()进行了修改。当我们取出数值的时候,我们首先将array[head]改为nil,同时将head的数值增加,保证其表示下一个元素的索引。

取出前:

["Ada", "Steve", "Tim", "Grace", xxx , xxx]
  head

取出后:

[xxx, "Steve", "Tim", "Grace", xxx , xxx]
        head

当然,如果我们从来不将前头的这些空点移除掉,而是不停的添加新的数值,移除前端的数值,那数组的大小将会不断变大,消耗的内存跟着增加。所以我们必须周期性的对数组进行调整。代码为:

    let percentage = Double(head)/Double(array.count)
    if array.count > 50 && percentage > 0.25 {
      array.removeFirst(head)
      head = 0
    }

在数组大小超过50的前提下,我们计算了空点在数组中所占的比例,当占比超过25%,我们就对数组进行一次调整,避免内存浪费。这里的50可以自己调整。

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

推荐阅读更多精彩内容

  • 一、什么是队列 队列是一种表(list),但是你只能把最新的元素插到最后,或者移除最前面的元素。 我们把这种特征成...
    ShannonChenCHN阅读 2,852评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,501评论 25 707
  • java笔记第一天 == 和 equals ==比较的比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量...
    jmychou阅读 1,485评论 0 3
  • 热爱生命 汪国真 我不去想是否能够成功 既然选择了远方 便只顾风雨兼程 我不去想能否赢得爱情 既然钟情于玫瑰 就勇...
    啊树崽阅读 288评论 0 2
  • 具体思路 在UITabBarControllerview出现之后viewDidAppear,在tabbar添加自定...
    HWenj阅读 1,131评论 2 3