Swift 算法實(shí)戰(zhàn)之路:排序


以前的文章中近她,我們主要是在講數(shù)據(jù)結(jié)構(gòu):比如數(shù)組叉瘩、鏈表、隊(duì)列粘捎、樹薇缅。這些數(shù)據(jù)結(jié)構(gòu)都是了解Swift和算法的基礎(chǔ)。從今以后的文章攒磨,我們將更多的關(guān)注于通用算法泳桦,這次我們就來聊聊排序。這次的主要內(nèi)容有:

  • 基本概念
  • 排序?qū)崙?zhàn)

基本概念

我們平常用的排序算法一般就以下幾種:

名稱 時(shí)間復(fù)雜度 空間復(fù)雜度 是否穩(wěn)定
冒泡排序 O(n^2) O(1)
插入排序 O(n^2) O(1)
選擇排序 O(n^2) O(1)
堆排序 O(nlogn) O(1)
歸并排序 O(nlogn) O(n)
快速排序 O(nlogn) O(1)
桶排序 O(n) O(k)

這些算法具體的定義本文不再贅述娩缰。一般情況下灸撰,好的排序算法性能是O(nlogn),壞的性能是O(n^2)拼坎。本文在此用swift示范實(shí)現(xiàn)歸并排序:

func mergeSort(array: [Int]) -> [Int] {
  var helper = Array(count: array.count, repeatedValue: 0)
  var array = array
  mergeSort(&array, &helper, 0, array.count - 1)
  return array
}

func mergeSort(inout array: [Int], inout _ helper: [Int], _ low: Int, _ high: Int) {
  guard low < high else {
    return
  }
  
  let middle = (high - low) / 2 + low
  mergeSort(&array, &helper, low, middle)
  mergeSort(&array, &helper, middle + 1, high)
  merge(&array, &helper, low, middle, high)
}

func merge(inout array: [Int], inout _ helper: [Int], _ low: Int, _ middle: Int, _ high: Int) {
  // copy both halves into a helper array
  for i in low ... high {
    helper[i] = array[i]
  }
  
  var helperLeft = low
  var helperRight = middle + 1
  var current = low
  
  // iterate through helper array and copy the right one to original array
  while helperLeft <= middle && helperRight <= high {
    if helper[helperLeft] <= helper[helperRight] {
      array[current] = helper[helperLeft]
      helperLeft += 1
    } else {
      array[current] = helper[helperRight]
      helperRight += 1
    }
    current += 1
  }
  
  // handle the rest
  guard middle - helperLeft >= 0 else {
    return
  }
  for i in 0 ... middle - helperLeft {
    array[current + i] = helper[helperLeft + i]
  }
}

表格中有一個(gè)特例是桶排序浮毯,它是將輸入的數(shù)組分配到一定數(shù)量的空桶中,每個(gè)空桶再單獨(dú)排序泰鸡。當(dāng)輸入的數(shù)組是均勻分配時(shí)债蓝,桶排序的時(shí)間復(fù)雜度為O(n)。舉個(gè)微軟的面試題來當(dāng)例子:

有三種顏色(紅盛龄,黃饰迹,藍(lán))的球若干,要求將所有紅色的球放在黃色球的前面余舶,最后放上所有的藍(lán)色球蹦锋。

這道題目最直接的解法就是桶排序。首先第一次遍歷欧芽,統(tǒng)計(jì)有多少個(gè)紅色球(假設(shè)x個(gè))莉掂,多少個(gè)黃色球(假設(shè)y個(gè)),和多少個(gè)藍(lán)色球(假設(shè)z個(gè))千扔。然后再一次遍歷憎妙,數(shù)組前部x個(gè)位置填充紅色球,中間y個(gè)位置放上對(duì)應(yīng)數(shù)量的黃色球曲楚,最后z個(gè)位置再放上藍(lán)色球厘唾。

另外解釋一下穩(wěn)定的意思:相等的鍵值,如果排過序后與原來未排序的次序相同龙誊,則稱此排序算法為穩(wěn)定抚垃。舉個(gè)例子:

// 原數(shù)組
[[2, 1], [1,3], [1,4]]

// 排序算法一
[[1,3], [1,4], [2, 1]]
// 排序算法二
[[1,4], [1,3], [2, 1]]

我們注意到排序算法一和二的區(qū)別就在于對(duì)[1, 3], [1, 4]這兩個(gè)元素的處理。排序算法一中,這兩個(gè)元素位置與原數(shù)組相同鹤树,故稱其為穩(wěn)定算法铣焊。而排序算法二則是不穩(wěn)定算法。

Swift中罕伯,排序的使用如下:

// 以升序排列為例曲伊,原數(shù)組可改變
array.sort

// 以降序排列為例,原數(shù)組不可改變
newArray = array.sorted(by: >)

// 字典鍵值排序示例
let keys = Array(map.keys)
let sortedKeys = keys.sorted() {
  return map[$0]! > map[$1]!
}

在其他語言比如Java中追他,其自帶的sort函數(shù)是用歸并排序?qū)崿F(xiàn)的坟募。而在Swift源代碼中,sort函數(shù)采用的是一種內(nèi)審算法(IntroSort)邑狸。它由堆排序懈糯、插入排序、快速排序三種算法構(gòu)成单雾,依據(jù)輸入的深度相應(yīng)選擇最佳的算法來完成昂利。本文關(guān)注的重點(diǎn)是實(shí)戰(zhàn),所以不做展開铁坎。對(duì)源代碼感興趣的朋友可以去Github讀蘋果的Swift的開源庫蜂奸。

排序?qū)崙?zhàn)

直接來看一道Facebook, Google, Linkedin都考過的面試題。

已知有很多會(huì)議硬萍,如果有這些會(huì)議時(shí)間有重疊扩所,則將它們合并。
比如有一個(gè)會(huì)議的時(shí)間為3點(diǎn)到5點(diǎn)朴乖,另一個(gè)會(huì)議時(shí)間為4點(diǎn)到6點(diǎn)祖屏,那么合并之后的會(huì)議時(shí)間為3點(diǎn)到6點(diǎn)

解決算法題目第一步永遠(yuǎn)是把具體問題抽象化。這里每一個(gè)會(huì)議我們已知開始時(shí)間和結(jié)束時(shí)間买羞,就可以寫一個(gè)類來定義它:

public class MeetingTime {
  public var start: Int
  public var end: Int
  public init(_ start: Int, _ end: Int) {
    self.start = start
    self.end = end
  }
}

然后題目說已知有很多會(huì)議袁勺,就是說我們已知有一個(gè)MeetingTime的數(shù)組、所以題目就轉(zhuǎn)化為寫一個(gè)函數(shù)畜普,輸入為一個(gè)MeetingTime的數(shù)組期丰,輸出為一個(gè)將原數(shù)組中所有重疊時(shí)間都處理過的新數(shù)組。

func merge(meetingTimes: [MeetingTime]) -> [MeetingTime] {}

下面來分析一下題目怎么解吃挑。最基本的思路是遍歷一次數(shù)組钝荡,然后歸并所有重疊時(shí)間。舉個(gè)例子:[[1, 3], [5, 6], [4, 7], [2, 3]]舶衬。這里我們可以發(fā)現(xiàn)[1, 3]和[2, 3]可以歸并為[1, 3]埠通,[5, 6]和[4, 7]可以歸并為[5, 7]。所以這里就提出一個(gè)要求:要將所有可能重疊的時(shí)間盡量放在一起逛犹,這樣遍歷的時(shí)候可以就可以從前往后一個(gè)接著一個(gè)的歸并端辱。于是很自然的想到 -- 按照會(huì)議開始的時(shí)間排序梁剔。

這里我們要對(duì)一個(gè)class進(jìn)行排序,而且要自定義排序方法舞蔽,在Swift中可以這樣寫:

meetingTimes.sortInPlace() {
  if $0.start != $1.start {
    return $0.start < $1.start
  } else {
    return $0.end < $1.end
  }
}

意思就是首先對(duì)開始時(shí)間進(jìn)行升序排列荣病,如果它們相同,就比較結(jié)束時(shí)間喷鸽。

有了排好順序的數(shù)組众雷,要得到新的歸并后的結(jié)果數(shù)組灸拍,我們只需要在遍歷的時(shí)候做祝,每次比較原數(shù)組(排序后)當(dāng)前會(huì)議時(shí)間與結(jié)果數(shù)組中當(dāng)前的會(huì)議時(shí)間,假如它們有重疊鸡岗,則歸并混槐;如果沒有,則直接添加進(jìn)結(jié)果數(shù)組之中轩性。所有代碼如下:

func merge(meetingTimes: [MeetingTime]) -> [MeetingTime] {
  // 處理特殊情況
  guard meetingTimes.count > 1 else {
    return meetingTimes
  }

  // 排序  
  var meetingTimes = meetingTimes.sort() {
    if $0.start != $1.start {
      return $0.start < $1.start
    } else {
      return $0.end < $1.end
    }
  }

  // 新建結(jié)果數(shù)組
  var res = [MeetingTime]()
  res.append(meetingTimes[0])

  // 遍歷排序后的原數(shù)組声登,并與結(jié)果數(shù)組歸并     
  for i in 1..<meetingTimes.count {
    let last = res[res.count - 1]
    let current = meetingTimes[i]
    if current.start > last.end {
      res.append(current)
    } else {
      last.end = max(last.end, current.end)
    }
  }
        
  return res
}

展望

排序在Swift中的應(yīng)用場景很多,比如tableView中對(duì)于dataSource的處理揣苏。當(dāng)然很多時(shí)候悯嗓,排序都是和搜索,尤其是二分搜索配合使用卸察。下期探討搜索的時(shí)候脯厨,會(huì)對(duì)排序進(jìn)行進(jìn)一步拓展。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坑质,一起剝皮案震驚了整個(gè)濱河市合武,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涡扼,老刑警劉巖稼跳,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吃沪,居然都是意外死亡汤善,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門票彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萎津,“玉大人,你說我怎么就攤上這事抹镊★鼻” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵垮耳,是天一觀的道長颈渊。 經(jīng)常有香客問我遂黍,道長,這世上最難降的妖魔是什么俊嗽? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任雾家,我火速辦了婚禮,結(jié)果婚禮上绍豁,老公的妹妹穿的比我還像新娘芯咧。我一直安慰自己,他們只是感情好竹揍,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布敬飒。 她就那樣靜靜地躺著,像睡著了一般芬位。 火紅的嫁衣襯著肌膚如雪无拗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天昧碉,我揣著相機(jī)與錄音英染,去河邊找鬼。 笑死被饿,一個(gè)胖子當(dāng)著我的面吹牛四康,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狭握,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼闪金,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了哥牍?” 一聲冷哼從身側(cè)響起毕泌,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嗅辣,沒想到半個(gè)月后撼泛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澡谭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年愿题,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛙奖。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潘酗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雁仲,到底是詐尸還是另有隱情仔夺,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布攒砖,位于F島的核電站缸兔,受9級(jí)特大地震影響日裙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惰蜜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一昂拂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抛猖,春花似錦格侯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瓢宦,卻和暖如春碎连,著一層夾襖步出監(jiān)牢的瞬間灰羽,已是汗流浹背驮履。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廉嚼,地道東北人玫镐。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像怠噪,于是被迫代替她去往敵國和親恐似。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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