Swift 算法實(shí)戰(zhàn)之路:棧和隊(duì)列

這期的內(nèi)容有點(diǎn)劍走偏鋒毅该,我們來討論一下棧和隊(duì)列。Swift語言中沒有內(nèi)設(shè)的棧和隊(duì)列潦牛,很多擴(kuò)展庫中使用Generic Type來實(shí)現(xiàn)椏粽疲或是隊(duì)列。正規(guī)的做法使用鏈表來實(shí)現(xiàn)罢绽,這樣可以保證加入和刪除的時(shí)間復(fù)雜度是O(1)畏线。然而筆者覺得最實(shí)用的實(shí)現(xiàn)方法是使用數(shù)組,因?yàn)?Swift 沒有現(xiàn)成的鏈表良价,而數(shù)組又有很多的 API 可以直接使用寝殴,非常方便。本期主要內(nèi)容有:

  • 棧和隊(duì)列的基本Swift實(shí)現(xiàn)明垢,以及在iOS開發(fā)中應(yīng)用的實(shí)例
  • Facebook棧相關(guān)面試題一道
  • 棧和隊(duì)列的互相實(shí)現(xiàn)及其思想

實(shí)現(xiàn)

對(duì)于棧來說蚣常,我們需要了解以下幾點(diǎn):

  • 棧是后進(jìn)先出的結(jié)構(gòu)。你可以理解成有好幾個(gè)盤子要壘成一疊痊银,哪個(gè)盤子最后疊上去抵蚊,下次使用的時(shí)候它就最先被抽出去。
  • 在iOS開發(fā)中溯革,如果你要在你的App中添加撤銷操作(比如刪除圖片贞绳,恢復(fù)刪除圖片),那么棧是首選數(shù)據(jù)結(jié)構(gòu)
  • 無論在面試還是寫App中致稀,只關(guān)注棧的這幾個(gè)基本操作:push, pop, isEmpty, peek, size冈闭。
protocol Stack {
  /// 持有的元素類型
  associatedtype Element
  
  /// 是否為空
  var isEmpty: Bool { get }
  /// 棧的大小
  var size: Int { get }
  /// 棧頂元素
  var peek: Element? { get }
  
  /// 進(jìn)棧
  mutating func push(_ newElement: Element)
  /// 出棧
  mutating func pop() -> Element?
}

struct IntegerStack: Stack {
  typealias Element = Int
  
  var isEmpty: Bool { return stack.isEmpty }
  var size: Int { return stack.count }
  var peek: Element? { return stack.last }
  
  private var stack = [Element]()
  
  func push(_ newElement: Element) {
    stack.append(newElement)
  }
  
  func pop() -> Element? {
    return stack.popLast()
  }
}

對(duì)于隊(duì)列來說,我們需要了解以下幾點(diǎn):

  • 隊(duì)列是先進(jìn)先出的結(jié)構(gòu)抖单。這個(gè)正好就像現(xiàn)實(shí)生活中排隊(duì)買票萎攒,誰先來排隊(duì)遇八,誰先買到票。
  • iOS開發(fā)中多線程的GCD和NSOperationQueue就是基于隊(duì)列實(shí)現(xiàn)的耍休。
  • 關(guān)于隊(duì)列我們只關(guān)注這幾個(gè)操作:enqueue, dequeue, isEmpty, peek, size刃永。
protocol Queue {
  /// 持有的元素類型
  associatedtype Element
  
  /// 是否為空
  var isEmpty: Bool { get }
  /// 棧的大小
  var size: Int { get }
  /// 棧頂元素
  var peek: Element? { get }
  
  /// 入隊(duì)
  mutating func enqueue(_ newElement: Element)
  /// 出隊(duì)
  mutating func dequeue() -> Element?
}

struct IntegerQueue: Queue {
  typealias Element = Int
  
  var isEmpty: Bool { return left.isEmpty && right.isEmpty }
  var size: Int { return left.count + right.count }
  var peek: Element? { return left.isEmpty ? right.first : left.last }
  
  private var left = [Element]()
  private var right = [Element]()
  
  mutating func enqueue(_ newElement: Element) {
    right.append(newElement)
  }
  
  mutating func dequeue() -> Element? {
    if left.isEmpty {
      left = right.reversed()
      right.removeAll()
    }
    return left.popLast()
  }
}

實(shí)戰(zhàn)

下面是Facebook一道真實(shí)的面試題。

Given an absolute path for a file (Unix-style), simplify it.
For example,
path = "/home/", => "/home"
path = "/a/./b/../../c/", => "/c"

這道題目一看羊精,這不就是我們平常在terminal里面敲的cd啊pwd之類的嗎斯够,好熟悉啊。

根據(jù)常識(shí)园匹,我們知道以下規(guī)則:

  • . 代表當(dāng)前路徑雳刺。比如 /a/. 實(shí)際上就是 /a,無論輸入多少個(gè) . 都返回當(dāng)前目錄
  • ..代表上一級(jí)目錄裸违。比如 /a/b/.. 實(shí)際上就是 /a,也就是說先進(jìn)入a目錄本昏,再進(jìn)入其下的b目錄供汛,再返回b目錄的上一層,也就是a目錄涌穆。

然后針對(duì)以上信息怔昨,我們可以得出以下思路:

  1. 首先輸入是個(gè) String,代表路徑宿稀。輸出要求也是 String, 同樣代表路徑趁舀。
  2. 我們可以把 input 根據(jù) “/” 符號(hào)去拆分,比如 "/a/b/./../d/" 就拆成了一個(gè)String數(shù)組["a", "b", ".", "..", "d"]
  3. 創(chuàng)立一個(gè)棧然后遍歷拆分后的 String 數(shù)組祝沸,對(duì)于一般 String 矮烹,直接加入到棧中,對(duì)于 ".." 那我們就對(duì)棧做pop操作罩锐,其他情況不錯(cuò)處理

思路有了奉狈,代碼也就有了

func simplifyPath(path: String) -> String {
  // 用數(shù)組來實(shí)現(xiàn)棧的功能
  var pathStack = [String]()
  // 拆分原路徑
  let paths = path.components(separatedBy: "/")
        
  for path in paths {
    // 對(duì)于 "." 我們直接跳過        
    guard path != "." else {
      continue
    }
    // 對(duì)于 ".." 我們使用pop操作        
    if path == ".."  {
      if (pathStack.count > 0) {
        pathStack.removeLast()
      }
    // 對(duì)于太注意空數(shù)組的特殊情況
    } else if path != "" {
      pathStack.append(path)
    }
  }
  // 將棧中的內(nèi)容轉(zhuǎn)化為優(yōu)化后的新路徑      
  let res = stack.reduce("") { total, dir in "\(total)/\(dir)" }
  
  // 注意空路徑的結(jié)果是 "/"      
  return res.isEmpty ? "/" : res
}

上面代碼除了完成了基本思路,還考慮了大量的特殊情況涩惑、異常情況仁期。這也是硅谷面試考察的一個(gè)方面:面試者思路的嚴(yán)謹(jǐn)和代碼的風(fēng)格規(guī)范。
隊(duì)列會(huì)在以后講樹遍歷和圖的廣度優(yōu)先遍歷時(shí)大放異彩竭恬,所以本期隊(duì)列先按下不表跛蛋。

轉(zhuǎn)化

處理?xiàng):完?duì)列問題,最經(jīng)典的一個(gè)思路就是使用兩個(gè)棧/隊(duì)列來解決問題痊硕。也就是說在原棧/隊(duì)列的基礎(chǔ)上赊级,我們用一個(gè)協(xié)助棧/隊(duì)列來幫助我們簡(jiǎn)化算法,這是一種空間換時(shí)間的思路寿桨。比如

用棧來實(shí)現(xiàn)隊(duì)列

struct MyQueue {
  var stackA: Stack
  var stackB: Stack

  var isEmpty: Bool {
    return stackA.isEmpty && stackB.isEmpty;
  }

  var peek: Any? {
    get {
      shift();
      return stackB.peek;
    }
  }

  var size: Int {
    get {
      return stackA.size + stackB.size
    }
  }
  
  init() {
    stackA = Stack()
    stackB = Stack()
  }
  
  func enqueue(object: Any) {
    stackA.push(object);
  }
  
  func dequeue() -> Any? {
    shift()
    return stackB.pop();
  }
  
  fileprivate func shift() {
    if stackB.isEmpty {
      while !stackA.isEmpty {
        stackB.push(stackA.pop()!);
      }
    }
  }
}

用隊(duì)列實(shí)現(xiàn)棧

struct MyStack {
  var queueA: Queue
  var queueB: Queue
  
  init() {
    queueA = Queue()
    queueB = Queue()
  }

  var isEmpty: Bool {
    return queueA.isEmpty && queueB.isEmpty
  }
  
  var peek: Any? {
    get {
      shift()
      let peekObj = queueA.peek
      queueB.enqueue(queueA.dequeue()!)
      swap()
      return peekObj
    }
  }

  var size: Int {
    return queueA.size
  }
  
  func push(object: Any) {
    queueA.enqueue(object)
  }
  
  func pop() -> Any? {
    shift()
    let popObject = queueA.dequeue()
    swap()
    return popObject
  }

  private func shift() {
    while queueA.size != 1 {
      queueB.enqueue(queueA.dequeue()!)
    }
  }
  
  private func swap() {
    (queueA, queueB) = (queueB, queueA)
  }
}

上面兩種實(shí)現(xiàn)方法都是使用兩個(gè)相同的數(shù)據(jù)結(jié)構(gòu)此衅,然后將元素由其中一個(gè)轉(zhuǎn)向另一個(gè)强戴,從而形成一種完全不同的數(shù)據(jù)。

總結(jié)

Swift中棧和隊(duì)列是比較特殊的數(shù)據(jù)結(jié)構(gòu)挡鞍,個(gè)人認(rèn)為最實(shí)用的實(shí)現(xiàn)方法是利用數(shù)組骑歹。雖然它們本身比較抽象,卻是很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)和iOS開發(fā)中的功能模塊的基礎(chǔ)墨微。這也是一個(gè)工程師進(jìn)階之路理應(yīng)熟練掌握的兩種數(shù)據(jù)結(jié)構(gòu)道媚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翘县,隨后出現(xiàn)的幾起案子最域,更是在濱河造成了極大的恐慌,老刑警劉巖锈麸,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镀脂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡忘伞,警方通過查閱死者的電腦和手機(jī)薄翅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氓奈,“玉大人翘魄,你說我怎么就攤上這事∫蹋” “怎么了暑竟?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)育勺。 經(jīng)常有香客問我但荤,道長(zhǎng),這世上最難降的妖魔是什么怀大? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任纱兑,我火速辦了婚禮,結(jié)果婚禮上化借,老公的妹妹穿的比我還像新娘潜慎。我一直安慰自己,他們只是感情好蓖康,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布铐炫。 她就那樣靜靜地躺著,像睡著了一般蒜焊。 火紅的嫁衣襯著肌膚如雪倒信。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天泳梆,我揣著相機(jī)與錄音鳖悠,去河邊找鬼榜掌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乘综,可吹牛的內(nèi)容都是我干的憎账。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼卡辰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼胞皱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起九妈,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤反砌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后萌朱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宴树,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年晶疼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了森渐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冒晰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竟块,到底是詐尸還是另有隱情壶运,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布浪秘,位于F島的核電站蒋情,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏耸携。R本人自食惡果不足惜棵癣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夺衍。 院中可真熱鬧狈谊,春花似錦、人聲如沸沟沙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矛紫。三九已至赎瞎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間颊咬,已是汗流浹背务甥。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工牡辽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敞临。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓态辛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親哟绊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子因妙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件票髓、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評(píng)論 4 62
  • 點(diǎn)擊查看
    5504bbe197d8閱讀 150評(píng)論 0 0
  • 群雞高聲叫攀涵, 主報(bào)春來到。 發(fā)青河邊柳洽沟, 紅日高高照以故。 包蘊(yùn)有生機(jī), 迎來好運(yùn)妙裆操。 佳時(shí)喜氣多怒详, 節(jié)日鑼鼓鬧。
    白露丹楓閱讀 187評(píng)論 0 0
  • 栗豐佳佳想媽媽閱讀 133評(píng)論 0 2
  • 隨著年會(huì)越來越緊張静尼,每天感覺都很忙,今天早會(huì)袁導(dǎo)把我們部門都點(diǎn)了個(gè)遍传泊,其實(shí)當(dāng)時(shí)內(nèi)心一方面很難受鼠渺,另一方面又覺得袁導(dǎo)...
    夢(mèng)瑤閱讀 174評(píng)論 0 3