逛博客看到的一位博主總結的12種排序算法(連接https://blog.csdn.net/tangdong3415/article/details/54016592),寫的很是不錯,因而絕不客氣的轉過來了(QAQ)html
從這一部分開始直接切入咱們計算機互聯網筆試面試中的重頭戲算法了,初始的想法是找一條主線,好比數據結構或者解題思路方法,將博主見過作過整理過的算法題逐個分析一遍(博主當年本身學算法就是用這種比較笨的刷題學的,囧),不過又想了想,算法這東西,博主本身學的過程當中一直深感,基礎仍是很是重要的,不少難題是基礎類數據結構和題目的思想綜合發散而來。好比說做爲最基本的排序算法就種類不少,而事實上筆試面試過程當中發現掌握的程度很通常,有不少題目,包括不少算法難題,其母題或者基本思想就是基於這些經典算法的,好比說快排的partition算法,好比說歸併排序中的思想,好比說桶排序中桶的思想。
這裏對筆試面試最常涉及到的12種排序算法(包括插入排序、二分插入排序、希爾排序、選擇排序、冒泡排序、雞尾酒排序、快速排序、堆排序、歸併排序、桶排序、計數排序和基數排序)進行了詳解。每一種算法都有基本介紹、算法原理分析、圖解/flash演示/視頻演示、算法代碼、筆試面試重點分析、筆試面試題等板塊,但願能幫助你們真正理解這些排序算法,並能使用這些算法的思想解決一些題。很少說了,下面就進入正題了。node
插入排序(Insertion Sort)的算法描述是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,一般採用in-place排序(即只需用到O(1)的額外空間的排序),於是在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。ios
通常來講,插入排序都採用in-place在數組上實現。具體算法描述以下:web
一、從第一個元素開始,該元素能夠認爲已經被排序面試
二、取出下一個元素,在已經排序的元素序列中從後向前掃描算法
三、若是該元素(已排序)大於新元素,將該元素移到下一位置shell
四、重複步驟3,直到找到已排序的元素小於或者等於新元素的位置數組
五、將新元素插入到該位置後數據結構
六、重複步驟2~5架構
若是目標是把n個元素的序列升序排列,那麼採用插入排序存在最好狀況和最壞狀況。最好狀況就是,序列已是升序排列了,在這種狀況下,須要進行的比較操做需(n-1)次便可。最壞狀況就是,序列是降序排列,那麼此時須要進行的比較共有n(n-1)/2次。插入排序的賦值操做是比較操做的次數減去(n-1)次。平均來講插入排序算法複雜度爲O(n^2)。於是,插入排序不適合對於數據量比較大的排序應用。可是,若是須要排序的數據量很小,例如,量級小於千,那麼插入排序仍是一個不錯的選擇。 插入排序在工業級庫中也有着普遍的應用,在STL的sort算法和stdlib的qsort算法中,都將插入排序做爲快速排序的補充,用於少許元素的排序(一般爲8個或如下)。
http://v.youku.com/v_show/id_XMjU4NTY5MzEy.html
把插入排序放在第一個的緣由是由於其出現的頻度不高,尤爲是這裏提到的直接排序算法,基本在筆試的選擇填空問時間空間複雜度的時候纔可能出現。畢竟排序速度比較慢,所以算法大題中考察的次數比較比較少。
請寫出鏈表的插入排序程序
下列排序算法中最壞複雜度不是n(n-1)/2的是 D
A.快速排序 B.冒泡排序 C.直接插入排序 D.堆排序
二分(折半)插入(Binary insert sort)排序是一種在直接插入排序算法上進行小改動的排序算法。其與直接排序算法最大的區別在於查找插入位置時使用的是二分查找的方式,在速度上有必定提高。
通常來講,插入排序都採用in-place在數組上實現。具體算法描述以下:
一、從第一個元素開始,該元素能夠認爲已經被排序
二、取出下一個元素,在已經排序的元素序列中二分查找到第一個比它大的數的位置
三、將新元素插入到該位置後
四、重複上述兩步
1)穩定
2)空間代價:O(1)
3)時間代價:插入每一個記錄須要O(log i)比較,最多移動i+1次,最少2次。最佳狀況O(n log n),最差和平均狀況O(n^2)。
二分插入排序是一種穩定的排序。當n較大時,總排序碼比較次數比直接插入排序的最差狀況好得多,但比最好狀況要差,所元素初始序列已經按排序碼接近有序時,直接插入排序比二分插入排序比較次數少。二分插入排序元素移動次數與直接插入排序相同,依賴於元素初始序列。
http://v.youku.com/v_show/id_XMTA1MTkwMTEy.html
這個排序算法在筆試面試中出現的頻度也不高,但畢竟是直接排序算法的一個小改進算法,同時二分查找又是很好的思想,有可能會在面試的時候提到,算法不難,留心一下就會了。
下面的排序算法中,初始數據集的排列順序對算法的性能無影響的是(B)
A、二分插入排序 B、堆排序 C、冒泡排序 D、快速排序
寫出下列算法的時間複雜度。
(1)冒泡排序;(2)選擇排序;(3)插入排序;(4)二分插入排序;(5)快速排序;(6)堆排序;(7)歸併排序;
希爾排序,也稱遞減增量排序算法,因DL.Shell於1959年提出而得名,是插入排序的一種高速而穩定的改進版本。
一、先取一個小於n的整數d1做爲第一個增量,把文件的所有記錄分紅d1個組。
二、全部距離爲d1的倍數的記錄放在同一個組中,在各組內進行直接插入排序。
三、取第二個增量d2<d1重複上述的分組和排序,
四、直至所取的增量dt=1(dt<dt-l<…<d2<d1),即全部記錄放在同一組中進行直接插入排序爲止。
希爾排序的時間複雜度與增量序列的選取有關,例如希爾增量時間複雜度爲O(n^2),而Hibbard增量的希爾排序的時間複雜度爲O(N^(5/4)),可是現今仍然沒有人能找出希爾排序的精確下界。
http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=92
http://v.youku.com/v_show/id_XMjU4NTcwMDIw.html
事實上希爾排序算法在筆試面試中出現的頻度也不比直接插入排序高,但它的時間複雜度並非一個定值,因此偶爾會被面試官問到選擇的步長和時間複雜度的關係,要稍微有點了解吧。算法大題中使用該方法或者其思想的題也很少。
寫出希爾排序算法程序,並說明最壞的狀況下須要進行多少次的比較和交換。
程序略,須要O(n^2)次的比較
設要將序列(Q, H, C, Y, P, A, M, S, R, D, F, X)中的關鍵碼按字母序的升序從新排列,則:
冒泡排序一趟掃描的結果是 H, C, Q, P, A, M, S, R, D, F, X ,Y ;
初始步長爲4的希爾(shell)排序一趟的結果是 P, A, C, S, Q, D, F, X , R, H,M, Y ;
二路歸併排序一趟掃描的結果是 H, Q, C, Y,A, P, M, S, D, R, F, X ;
快速排序一趟掃描的結果是 F, H, C, D, P, A, M, Q, R, S, Y,X ;
堆排序初始建堆的結果是 A, D, C, R, F, Q, M, S, Y,P, H, X 。
選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工做原理以下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。
n個記錄的文件的直接選擇排序可通過n-1趟直接選擇排序獲得有序結果:
一、初始狀態:無序區爲R[1..n],有序區爲空。
二、第i趟排序(i=1,2,3...n-1)
第i趟排序開始時,當前有序區和無序區分別爲R[1..i-1]和R(i..n)。該趟排序從當前無序區中選出關鍵字最小的記錄 R[k],將它與無序區的第1個記錄R交換,使R[1..i]和R分別變爲記錄個數增長1個的新有序區和記錄個數減小1個的新無序區。
三、前n-1趟結束,數組有序化了
選擇排序的交換操做介於0和(n-1)次之間。選擇排序的比較操做爲n(n-1)/2次之間。選擇排序的賦值操做介於0和3(n-1)次之間。比較次數O(n^2),比較次數與關鍵字的初始狀態無關,總的比較次數N=(n-1)+(n-2)+...+1=n*(n-1)/2。 交換次數O(n),最好狀況是,已經有序,交換0次;最壞狀況是,逆序,交換n-1次。 交換次數比冒泡排序少多了,因爲交換所需CPU時間比比較所需的CPU時間多,n值較小時,選擇排序比冒泡排序快。
最差時間複雜度 |
О(n²) |
最優時間複雜度 |
О(n²) |
平均時間複雜度 |
О(n²) |
最差空間複雜度 |
О(n) total, O(1) |
http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=85
http://v.youku.com/v_show/id_XMjU4NTY5NTcy.html
就博主看過的筆試面試題而言,選擇算法也大多出如今選擇填空中,要熟悉其時間和空間複雜度,最好最壞的狀況分別是什麼,以及在那種狀況下,每一輪的比較次數等。
在插入和選擇排序中,若初始數據基本正序,則選用 插入排序(到尾部) ;若初始數據基本反序,則選用 選擇排序 。
下述幾種排序方法中,平均查找長度(ASL)最小的是
A. 插入排序 B.快速排序 C. 歸併排序 D. 選擇排序
冒泡排序是一種簡單的排序算法。它重複地走訪過要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。走訪數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。
一、比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。
二、對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
三、針對全部的元素重複以上的步驟,除了最後一個。
四、持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。
冒泡排序是與插入排序擁有相等的執行時間,可是兩種法在須要的交換次數卻很大地不一樣。在最壞的狀況,冒泡排序須要O(n^2)次交換,而插入排序只要最多O(n)交換。冒泡排序的實現(相似下面)一般會對已經排序好的數列拙劣地執行(O(n^2)),而插入排序在這個例子只須要O(n)個運算。所以不少現代的算法教科書避免使用冒泡排序,而用插入排序取代之。冒泡排序若是能在內部循環第一次執行時,使用一個旗標來表示有無須要交換的可能,也有可能把最好的複雜度下降到O(n)。在這個狀況,在已經排序好的數列就無交換的須要。若在每次走訪數列時,把走訪順序和比較大小反過來,也能夠稍微地改進效率。有時候稱爲往返排序,由於算法會從數列的一端到另外一端之間穿梭往返。
最差時間複雜度 |
O(n^2) |
最優時間複雜度 |
O(n) |
平均時間複雜度 |
O(n^2) |
最差空間複雜度 |
總共O(n),須要輔助空間O(1) |
http://student.zjzk.cn/course_ware/data_structure/web/flashhtml/maopaopaixu.htm
http://v.youku.com/v_show/id_XMzMyOTAyMzQ0.html
通常咱們學到的第一個排序算法就是冒泡排序,不得不說,這個還真是一個很常見的考點,平均時間空間複雜度,最好最壞狀況下的時間空間複雜度,在不一樣狀況下每一趟的比較次數,以及加標誌位減小比較次數等,都是須要注意的地方。
對於整數序列100,99,98,…3,2,1,若是將它徹底倒過來,分別用冒泡排序,它們的比較次數和交換次數各是多少?
答:冒泡排序的比較和交換次數將最大,都是1+2+…+n-1=n(n-1)/2=50×99=4545次。
把一個字符串的大寫字母放到字符串的後面,各個字符的相對位置不變,不能申請額外的空間。
事實上,這道題放到冒泡排序這裏不知道是否是特別合適,只是有一種解法是相似冒泡的思想,以下解法一
每次遇到大寫字母就日後冒,最後結果即爲所求
步驟以下
一、兩個指針p1和p2,從後往前掃描
二、p1遇到一個小寫字母時停下, p2遇到大寫字母時停下,二者所指向的char交換
三、p1, p2同時往前一格
代碼以下:
雞尾酒排序等因而冒泡排序的輕微變形。不一樣的地方在於從低到高而後從高到低,而冒泡排序則僅從低到高去比較序列裏的每一個元素。他能夠獲得比冒泡排序稍微好一點的效能,緣由是冒泡排序只從一個方向進行比對(由低到高),每次循環只移動一個項目。
一、依次比較相鄰的兩個數,將小數放在前面,大數放在後面;
二、第一趟可獲得:將最大數放到最後一位。
三、第二趟可獲得:將第二大的數放到倒數第二位。
四、如此下去,重複以上過程,直至最終完成排序。
雞尾酒排序最糟或是平均所花費的次數都是O(n^2),但若是序列在一開始已經大部分排序過的話,會接近O(n)。
最差時間複雜度 |
O(n^2) |
最優時間複雜度 |
O(n) |
平均時間複雜度 |
O(n^2) |
參見http://zh.wikipedia.org/zh-cn/%E9%B8%A1%E5%B0%BE%E9%85%92%E6%8E%92%E5%BA%8F右側flash動畫
雞尾酒排序在博主印象中出現的頻度也不高,用到它的算法題大題不多,選擇填空出現的話多以雙向冒泡排序的名稱出現,注意注意時間空間複雜度,理解理解算法應該問題就不大了。
考點基本相似冒泡排序,請參考上一節
恩,重頭戲開始了,快速排序是各類筆試面試最愛考的排序算法之一,且排序思想在不少算法題裏面被普遍使用。是須要重點掌握的排序算法。
快速排序是由東尼·霍爾所發展的一種排序算法。其基本思想是基本思想是,經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
快速排序使用分治法來把一個串(list)分爲兩個子串行(sub-lists)。
步驟爲:
一、從數列中挑出一個元素,稱爲 "基準"(pivot),
二、從新排序數列,全部元素比基準值小的擺放在基準前面,全部元素比基準值大的擺在基準的後面(相同的數能夠到任一邊)。在這個分區退出以後,該基準就處於數列的中間位置。這個稱爲分區(partition)操做。
三、遞歸地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
遞歸的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。雖然一直遞歸下去,可是這個算法總會退出,由於在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。
算法僞代碼描述:
function quicksort(q)
var list less, pivotList, greater
if length(q) ≤ 1 {
return q
} else {
select a pivot value pivot from q
for each x in q except the pivot element
if x < pivot then add x to less
if x ≥ pivot then add x to greater
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
在平均情況下,排序 n 個項目要Ο(n log n)次比較。在最壞情況下則須要Ο(n^2)次比較,但這種情況並不常見。事實上,快速排序一般明顯比其餘Ο(n log n) 算法更快,由於它的內部循環(inner loop)能夠在大部分的架構上頗有效率地被實現出來。
最差時間複雜度 |
O(n^2) |
最優時間複雜度 |
O(n log n) |
平均時間複雜度 |
O(n log n) |
最差空間複雜度 |
根據實現的方式不一樣而不一樣 |
快速排序會遞歸地進行不少輪,其中每一輪稱之爲快排的partition算法,即上述算法描述中的第2步,很是重要,且在各類筆試面試中用到該思想的算法題層出不窮,下圖爲第一輪的partition算法的一個示例。
可一步步參見http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=86中的快排過程
http://v.youku.com/v_show/id_XMzMyODk4NTQ4.html
事實上,這個地方須要提一下的是,快排有不少種版本。例如,咱們「基準數」的選擇方法不一樣就有不一樣的版本,但重要的是快排的思想,咱們熟練掌握一種版本,在最後的筆試面試中也夠用了,我這裏羅列幾種最有名的版本C代碼。
咱們選取數組的第一個元素做爲主元,每一輪都是和第一個元素比較大小,經過交換,分紅大於和小於它的先後兩部分,再遞歸處理。
代碼以下
隨機選基準數的快排
徹底考察快排算法自己的題目,多出如今選擇填空,基本是關於時間空間複雜度的討論,最好最壞的情形交換次數等等。卻是快排的partition算法須要特別注意!頻度極高地被使用在各類算法大題中!詳見下小節列舉的面試小題。
這裏要重點強調的是快排的partition算法,博主當年面試的時候就遇到過數道用該思路的算法題,舉幾道以下:
最小的k個數,輸入n個整數,找出其中最下的k個數,例如輸入四、五、一、六、二、七、三、八、一、2,輸出最下的4個數,則輸出一、一、二、2。
固然,博主也知道這題能夠建大小爲k的大頂堆,而後用堆的方法解決。
可是這個題目可也以仿照快速排序,運用partition函數進行求解,不過咱們完整的快速排序分割後要遞歸地對先後兩段繼續進行分割,而這裏咱們須要作的是斷定分割的位置,而後再肯定對前段仍是後段進行分割,因此只對單側分割便可。代碼以下:
判斷數組中出現超過一半的數字
固然,這道題不少人都見過,並且最通用的一種解法是數對對消的思路。這裏只是再給你們提供一種思路,快排partition的方法在不少地方都能使用,好比這題。咱們也能夠選擇合適的斷定條件進行遞歸。代碼以下:
有一個由大小寫組成的字符串,如今須要對他進行修改,將其中的全部小寫字母排在大寫字母的前面(不要求保持原順序)
這題可能你們都能想到的方法是:設置首尾兩個指針,首指針向後移動尋找大寫字母,尾指針向前移動需找小寫字母,找到後都停下,交換。以後繼續移動,直至相遇。這種方法在這裏我就不作討論寫代碼了。
可是這題也能夠採用相似快排的partition。這裏使用從左日後掃描的方式。字符串在調整的過程當中能夠分紅兩個部分:已排好的小寫字母部分、待調整的剩餘部分。用兩個指針i和j,其中i指向待調整的剩餘部分的第一個元素,用j指針遍歷待調整的部分。當j指向一個小寫字母時,交換i和j所指的元素。向前移動i、j,直到字符串末尾。代碼以下:
不得不說,堆排序太容易出現了,選擇填空問答算法大題都會出現。建堆的過程,堆調整的過程,這些過程的時間複雜度,空間複雜度,以及如何應用在海量數據Top K問題中等等,都是須要重點掌握的。
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。
咱們這裏介紹幾個問題,一步步推到堆排序的算法。
咱們這裏提到的堆通常都指的是二叉堆,它知足二個特性:
1---父結點的鍵值老是大於或等於(小於或等於)任何一個子節點的鍵值。
2---每一個結點的左子樹和右子樹都是一個二叉堆(都是最大堆或最小堆)。
以下爲一個最小堆(父結點的鍵值老是小於任何一個子節點的鍵值)
這是爲了保持堆的特性而作的一個操做。對某一個節點爲根的子樹作堆調整,其實就是將該根節點進行「下沉」操做(具體是經過和子節點交換完成的),一直下沉到合適的位置,使得剛纔的子樹知足堆的性質。
例如對最大堆的堆調整咱們會這麼作:
一、在對應的數組元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一個,將其下標存儲在largest中。
二、若是A[i]已經就是最大的元素,則程序直接結束。
三、不然,i的某個子結點爲最大的元素,將A[largest]與A[i]交換。
四、再從交換的子節點開始,重複1,2,3步,直至葉子節點,算完成一次堆調整。
這裏須要提一下的是,通常作一次堆調整的時間複雜度爲log(n)。
以下爲咱們對4爲根節點的子樹作一次堆調整的示意圖,可幫咱們理解。
建堆是一個經過不斷的堆調整,使得整個二叉樹中的數知足堆性質的操做。在數組中的話,咱們通常從下標爲n/2的數開始作堆調整,一直到下標爲0的數(由於下標大於n/2的數都是葉子節點,其子樹已經知足堆的性質了)。下圖爲其一個圖示:
很明顯,對葉子結點來講,能夠認爲它已是一個合法的堆了即20,60, 65, 4, 49都分別是一個合法的堆。只要從A[4]=50開始向下調整就能夠了。而後再取A[3]=30,A[2] = 17,A[1] = 12,A[0] = 9分別做一次向下調整操做就能夠了。
堆排序是在上述3中對數組建堆的操做以後完成的。
數組儲存成堆的形式以後,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]從新恢復堆。第二次將A[0]與A[n-2]交換,再對A[0…n-3]從新恢復堆,重複這樣的操做直到A[0]與A[1]交換。因爲每次都是將最小的數據併入到後面的有序區間,故操做完成後整個數組就有序了。
以下圖所示:
最差時間複雜度 |
O(n log n) |
最優時間複雜度 |
O(n log n) |
平均時間複雜度 |
O(n log n) |
最差空間複雜度 |
O(n) |
略,見上一節。
可參見http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=88中的flash動畫,幫助理解
http://v.youku.com/v_show/id_XMzQzNzAwODQ=.html
直接上代碼吧,重點注意HeapAdjust,BuildHeap和HeapSort的實現。
堆排序相關的考察太多了,選擇填空問答算法大題都會出現。建堆的過程,堆調整的過程,這些過程的時間複雜度,空間複雜度,須要比較交換多少次,以及如何應用在海量數據Top K問題中等等。堆又是一種很好作調整的結構,在算法題裏面使用頻度很高。
編寫算法,從10億個浮點數當中,選出其中最大的10000個。
典型的Top K問題,用堆是最典型的思路。建10000個數的小頂堆,而後將10億個數依次讀取,大於堆頂,則替換堆頂,作一次堆調整。結束以後,小頂堆中存放的數即爲所求。代碼以下(爲了方便,這裏直接使用了STL容器):
設計一個數據結構,其中包含兩個函數,1.插入一個數字,2.得到中數。並估計時間複雜度。
使用大頂堆和小頂堆存儲。
使用大頂堆存儲較小的一半數字,使用小頂堆存儲較大的一半數字。
插入數字時,在O(logn)時間內將該數字插入到對應的堆當中,並適當移動根節點以保持兩個堆數字相等(或相差1)。
獲取中數時,在O(1)時間內找到中數。
歸併排序是創建在歸併操做上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。歸併排序是一種穩定的排序方法。
將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
歸併排序具體算法描述以下(遞歸版本):
一、Divide: 把長度爲n的輸入序列分紅兩個長度爲n/2的子序列。
二、Conquer: 對這兩個子序列分別採用歸併排序。
三、Combine: 將兩個排序好的子序列合併成一個最終的排序序列。
歸併排序的效率是比較高的,設數列長爲N,將數列分開成小數列一共要logN步,每步都是一個合併有序數列的過程,時間複雜度能夠記爲O(N),故一共爲O(N*logN)。由於歸併排序每次都是在相鄰的數據中進行操做,因此歸併排序在O(N*logN)的幾種排序方法(快速排序,歸併排序,希爾排序,堆排序)也是效率比較高的。
能夠參考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=93中的過程
http://video.sina.com.cn/v/b/80012415-1642346981.html
歸併排序自己做爲一種高效的排序算法,也是常會被問到的。尤爲是歸併排序體現的遞歸思路很重要,在遞歸的過程當中能夠完成不少事情,不少算法題也是使用的這個思路,可見下面7)部分的筆試面試算法題。
題目輸入一個數組,數組元素的大小在0->999.999.999的範圍內,元素個數爲0-500000範圍。題目要求經過相鄰的元素的交換,使得輸入的數組變爲有序,要求輸出交換的次數
這題求解的其實就是一個逆序對。咱們回想一下歸併排序的過程:
歸併排序是用分治思想,分治模式在每一層遞歸上有三個步驟:
分解:將n個元素分紅個含n/2個元素的子序列。
解決:用合併排序法對兩個子序列遞歸的排序。
合併:合併兩個已排序的子序列已獲得排序結果。
在歸併排序算法中稍做修改,就能夠在n log n的時間內求逆序對。
將數組A[1...size],劃分爲A[1...mid] 和 A[mid+1...size].那麼逆序對數的個數爲 f(1, size) = f(1, mid) + f(mid+1, size) + s(1, mid, size),這裏s(1, mid, size)表明左值在[1---mid]中,右值在[mid+1, size]中的逆序對數。因爲兩個子序列自己都已經排序,因此查找起來很是方便。
代碼以下:
有10個文件,每一個文件1G,每一個文件的每一行存放的都是用戶的query,每一個文件的query均可能重複。要求你按照query的頻度排序。
一、hash映射:順序讀取10個文件,按照hash(query)%10的結果將query寫入到另外10個文件(記爲)中。這樣新生成的文件每一個的大小大約也1G(假設hash函數是隨機的)。
二、hash統計:找一臺內存在2G左右的機器,依次對用hash_map(query, query_count)來統計每一個query出現的次數。注:hash_map(query,query_count)是用來統計每一個query的出現次數,不是存儲他們的值,出現一次,則count+1。
三、堆/快速/歸併排序:利用快速/堆/歸併排序按照出現次數進行排序。將排序好的query和對應的query_cout輸出到文件中。這樣獲得了10個排好序的文件(記爲)。對這10個文件進行歸併排序(內排序與外排序相結合)。
歸併一個左右兩邊分別排好序的數組,空間複雜度要求O(1)。
使用原地歸併,可以讓歸併排序的空間複雜度降爲O(1),可是速度上會有必定程度的降低。代碼以下:
桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工做的原理是將數組分到有限數量的桶子裏。每一個桶子再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。
桶排序是穩定的,且在大多數狀況下常見排序裏最快的一種,比快排還要快,缺點是很是耗空間,基本上是最耗空間的一種排序算法,並且只能在某些情形下使用。
桶排序具體算法描述以下:
一、設置一個定量的數組看成空桶子。
二、尋訪串行,而且把項目一個一個放到對應的桶子去。
三、對每一個不是空的桶子進行排序。
四、從不是空的桶子裏把項目再放回原來的串行中。
桶排序最好狀況下使用線性時間O(n),很顯然桶排序的時間複雜度,取決與對各個桶之間數據進行排序的時間複雜度,由於 其它部分的時間複雜度都爲O(n);很顯然,桶劃分的越小,各個桶之間的數據越少,排 序所用的時間也會越少。但相應的空間消耗就會增大。
能夠證實,即便選用插入排序做爲桶內排序的方法,桶排序的平均時間複雜度爲線性。 具體證實,請參考算法導論。其空間複雜度也爲線性。
能夠參考http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=90中的過程
這裏就不給出桶排序的視頻了,見上flash吧
桶排序是一種很巧妙的排序方法,在處理密集型數排序的時候有比較好的效果(主要是這種狀況下空間複雜度不高),其思想也可用在不少算法題上,詳見後續筆試面試算法例題。
一年的全國高考考生人數爲500 萬,分數使用標準分,最低100 ,最高900 ,沒有小數,你把這500 萬元素的數組排個序。
對500W數據排序,若是基於比較的先進排序,平均比較次數爲O(5000000*log5000000)≈1.112億。可是咱們發現,這些數據都有特殊的條件: 100=<score<=900。那麼咱們就能夠考慮桶排序這樣一個「投機取巧」的辦法、讓其在毫秒級別就完成500萬排序。
建立801(900-100)個桶。將每一個考生的分數丟進f(score)=score-100的桶中。這個過程從頭至尾遍歷一遍數據只須要500W次。而後根據桶號大小依次將桶中數值輸出,便可以獲得一個有序的序列。並且能夠很容易的獲得100分有***人,501分有***人。
實際上,桶排序對數據的條件有特殊要求,若是上面的分數不是從100-900,而是從0-2億,那麼分配2億個桶顯然是不可能的。因此桶排序有其侷限性,適合元素值集合並不大的狀況。
在一個文件中有 10G 個整數,亂序排列,要求找出中位數。內存限制爲 2G。只寫出思路便可(內存限制爲 2G的意思就是,可使用2G的空間來運行程序,而不考慮這臺機器上的其餘軟件的佔用內存)。
分析: 既然要找中位數,很簡單就是排序的想法。那麼基於字節的桶排序是一個可行的方法。
思想:將整型的每1byte做爲一個關鍵字,也就是說一個整形能夠拆成4個keys,並且最高位的keys越大,整數越大。若是高位keys相同,則比較次高位的keys。整個比較過程相似於字符串的字典序。按如下步驟實施:
一、把10G整數每2G讀入一次內存,而後一次遍歷這536,870,912即(1024*1024*1024)*2 /4個數據。每一個數據用位運算">>"取出最高8位(31-24)。這8bits(0-255)最多表示255個桶,那麼能夠根據8bit的值來肯定丟入第幾個桶。最後把每一個桶寫入一個磁盤文件中,同時在內存中統計每一個桶內數據的數量,天然這個數量只須要255個整形空間便可。
二、繼續之內存中的整數的次高8bit進行桶排序(23-16)。過程和第一步相同,也是255個桶。
三、一直下去,直到最低字節(7-0bit)的桶排序結束。我相信這個時候徹底能夠在內存中使用一次快排就能夠了。
給定n個實數x1,x2,...,xn,求這n個實數在實軸上相鄰2個數之間的最大差值M,要求設計線性的時間算法
典型的最大間隙問題。
要求線性時間算法。須要使用桶排序。桶排序的平均時間復發度是O(N).若是桶排序的數據分佈不均勻,假設都分配到同一個桶中,最壞狀況下的時間複雜度將變爲O(N^2).
桶排序: 最關鍵的建桶,若是桶設計得很差的話桶排序是幾乎沒有做用的。一般狀況下,上下界有兩種取法,第一種是取一個10^n或者是2^n的數,方便實現。另外一種是取數列的最大值和最小值而後均分做桶。
對於這個題,最關鍵的一步是:由抽屜原理知:最大差值M>= (Max(V[n])-Min(V[n]))/(n-1)!因此,假如以(Max(V[n])-Min(V[n]))/(n-1)爲桶寬的話,答案必定不是屬於同一個桶的兩元素之差。所以,這樣建桶,每次只保留桶裏面的最大值和最小值便可。
代碼以下:
計數排序(Counting sort)是一種穩定的排序算法。計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等於i的元素的個數。而後根據數組C來將A中的元素排到正確的位置。它只能對整數進行排序。
算法的步驟以下:
一、找出待排序的數組中最大和最小的元素
二、統計數組中每一個值爲i的元素出現的次數,存入數組C的第i項
三、對全部的計數累加(從C中的第一個元素開始,每一項和前一項相加)
四、反向填充目標數組:將每一個元素i放在新數組的第C(i)項,每放一個元素就將C(i)減去1
當輸入的元素是n 個0到k之間的整數時,它的運行時間是 O(n + k)。計數排序不是比較排序,排序的速度快於任何比較排序算法。
因爲用來計數的數組C的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據範圍很大的數組,須要大量時間和內存。例如:計數排序是用來排序0到100之間的數字的最好的算法,可是它不適合按字母順序排序人名。可是,計數排序能夠用在基數排序中的算法來排序數據範圍很大的數組。
咱們使用計數排序對一個亂序的整數數組進行排序。
首先建立一個臨時數組(長度爲輸入數據的最大間隔),對於每個輸入數組的整數k,咱們在臨時數組的第k位置"1"。以下圖
上圖中,第一行表示輸入數據,第二行表示建立的臨時數據,臨時數組的下標表明輸入數據的某一個值,臨時數組的值表示輸入數據中某一個值的數量。
若是輸入數據中有重複的數值,那麼咱們增長臨時數組相應的值(好比上圖中5有3個,因此小標爲5的數組的值是3)。在「初始化」臨時數組之後,咱們就獲得了一個排序好的輸入數據。
咱們順序遍歷這個數組,將下標解釋成數據, 將該位置的值表示該數據的重複數量,記得獲得一個排序好的數組。
可參見http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=89中的flash過程
前面的flash已經可以清晰地表示出整個計數排序的過程了,這裏就不推薦視頻了
計數排序在處理密集整數排序的問題的時候很是有限,尤爲是有時候題目對空間並不作太大限制,那使用計數排序可以達到O(n)的時間複雜度,遠快於全部基於比較的其餘排序方法。
某地區年齡排序問題
夠典型的計數排序吧,年齡的區間也就那麼大,代碼就不上了,請參照上述參照計數排序算法。
基數排序是一種非比較型整數排序算法,其原理是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。因爲整數也能夠表達字符串(好比名字或日期)和特定格式的浮點數,因此基數排序也不是隻能使用於整數。基數排序的發明能夠追溯到1887年赫爾曼·何樂禮在打孔卡片製表機(Tabulation Machine)上的貢獻。
整個算法過程描述以下:
一、將全部待比較數值(正整數)統一爲一樣的數位長度,數位較短的數前面補零。
二、從最低位開始,依次進行一次排序。
三、這樣從最低位排序一直到最高位排序完成之後, 數列就變成一個有序序列。
基數排序的時間複雜度是 O(k•n),其中n是排序元素個數,k是數字位數。
注意這不是說這個時間複雜度必定優於O(n·log(n)),由於k的大小通常會受到n的影響。 以排序n個不一樣整數來舉例,假定這些整數以B爲底,這樣每位數都有B個不一樣的數字,k就必定不小於logB(n)。因爲有B個不一樣的數字,因此就須要B個不一樣的桶,在每一輪比較的時候都須要平均n·log2(B) 次比較來把整數放到合適的桶中去,因此就有:
k 大於或等於 logB(n)
每一輪(平均)須要 n·log2(B) 次比較
因此,基數排序的平均時間T就是:
T ≥ logB(n)·n·log2(B) = log2(n)·logB(2)·n·log2(B) = log2(n)·n·logB(2)·log2(B) = n·log2(n)
因此和比較排序類似,基數排序須要的比較次數:T ≥ n·log2(n)。 故其時間複雜度爲 Ω(n·log2(n)) = Ω(n·log n) 。
可參見http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=91中的flash過程
http://www.tudou.com/programs/view/vfoUHC-tgi0
計數排序在處理密集整數排序的問題的時候很是有限,尤爲是有時候題目對空間並不作太大限制,那使用計數排序可以達到O(n)的時間複雜度,遠快於全部基於比較的其餘排序方法。
總結一下各類排序算法以下:
名稱 |
時間複雜度 |
額外空間 |
穩定性 |
考點 |
插入排序 |
平均O(n^2) 最優O(n) 最差O(n^2) |
O(1) |
穩定 |
選擇填空 各類時間複雜度 移動元素個數 |
二分插入排序 |
平均 O(n^2) |
O(1) |
穩定 |
同上 |
希爾排序 |
最差O(n log n) 最優 O(n) |
O(n) |
不穩定 |
時間複雜度 比較次數 |
選擇排序 |
O(n^2) |
O(1) |
不穩定 |
同插入排序 |
冒泡排序 |
O(n^2) 最優O(n) 最差O(n^2) |
O(1) |
穩定 |
時間複雜度 比較次數 單輪冒泡 |
雞尾酒排序 |
O(n^2) |
O(1) |
穩定 |
同上 |
快速排序 |
O(n log n) |
O(1) |
不穩定 |
時間複雜度 快排partition算法 |
堆排序 |
O(n log n) |
O(n) |
不穩定 |
時間複雜度 堆調整,建堆,堆排序,Top K問題 |
歸併排序 |
平均O(nlogn) 最差O(nlogn) 最優O(n) |
O(n) |
穩定 |
時間複雜度 遞歸思想 |