今天花了點時間把七個常見的內部排序從新複習了一遍,總結一下,也算是驗證一下本身有沒有真正理解。算法
冒泡排序(Bubble Sort):shell
不少人聽到排序第一個想到的應該就是冒泡排序了。也確實,冒泡排序的想法很是的簡單:大的東西沉底,汽泡上升。基於這種思想,咱們能夠得到第一個版本的冒泡:數組
public static void sort1(int[] array) { for (int i = 0; i < array.length; i++) { // i爲肯定了幾個數 for (int j = 1; j < array.length - i; j++) { if (array[j - 1] > array[j]) { // 進行兩元素之間的位置交換 int temp = array[j - 1]; array[j - 1] = array[j]; array[j] = temp; } } } }
再想想,其實有這樣一種狀況:若是在某一個遍歷的過程當中,沒有發生數據交換,那其實說明了這個數組已是有序的了:因此咱們能夠做一點小小的優化(雖然不常常有用):性能
// 升級版1 // 基於一個事實,若是某一次遍歷沒有發生數據交換,那麼排序已經完成 public static void sort2(int[] array) { boolean complete = false; for (int i = 0; i < array.length && !complete; i++) { complete = true; // 假設已經完成了 for (int j = 1; j < array.length - i; j++) { if (array[j - 1] > array[j]) { complete = false; int temp = array[j - 1]; array[j - 1] = array[j]; array[j] = temp; } } } }
這樣在應對某些比較特殊的狀況下,會有必定的效果。測試
再來想一想這樣一個事實:最後產生交換的位置以後的元素是有序的。想像一下,若是一個數組只是前半部分的元素是無序的,那麼咱們實際上只須要遍歷到無序的位置便可,其實咱們上前面的算法中array.length – i這一步已是作了相似的工做,由於咱們知道後面已經有i個元素是有序的了。因此咱們能夠獲得第三個版本的冒泡:優化
// 升級版2 // 基於這樣一個事實,若是最後的數據交換位置以後的元素是有序的 // 因此,這個也是基於版本1的再一次增強 public static void sort3(int[] array) { int flag = array.length; // 用於標識元素的最後的位置 while (flag != 0) { // 爲0說明沒有發生數據的交換 int last = flag; flag = 0; for (int j = 1; j < last; j++) { if (array[j - 1] > array[j]) { flag = j; int temp = array[j - 1]; array[j - 1] = array[j]; array[j] = temp; } } } }
選擇排序(Selection Sort):spa
選擇排序也是比較好理解的,每次從左到右掃描一次,能夠獲得最大的(或最小的)元素的下標,而後咱們再把它與數組末尾(或者開頭)的元素進行交換,這樣每一次均可以找到一個最大的。實現起來也是很簡單的:指針
// 每次從中選出最小的元素,只進行一次交換 // 相比冒泡,大大減小了元素的交換次數 public static void sort(int[] array) { for (int i = 0; i < array.length; i++) { // 肯定了多少個元素 int min = i; // 每次都默認第一個是最小的 for (int j = i + 1; j < array.length; j++) { if (array[min] > array[j]) { min = j; } } int temp = array[min]; array[min] = array[i]; array[i] = temp; } }
直接插入排序(Insertion Sort):code
直接插入排序的思路有點相似於咱們平時打牌時整理時的方法,好比我整理牌的方式是,右邊選擇,而後插入到左邊已經整理好的牌中。blog
直接插入排序也是這樣:將要排序的元素分爲有序區和無序區。每次從無序區拿出一個元素,而後在有序區找到本身的位置,強勢插入。
public static void sort(int[] array) { for (int i = 1; i < array.length; i++) { // 默認第一個是有序的 int temp = array[i]; // 拿出要插入的數據 int j = i; // 尋找插入位置 while (j > 0 && temp < array[j - 1]) { array[j] = array[j - 1]; j--; } array[j] = temp; } }
對於直接插入排序來講,常常用到一個「優化」就是使用數組的第0個元素來放置要插入的數據,這樣作有一個好處就是不用每次都去檢查j指針是否小於0。理論上能夠節省點時間。
另外一種優化就是能夠在查找插入位置的時候能夠經過二分查找來實現,也有必定的做用。
接下來看一下這三個算法的PK狀況,爲了增強對比咱們找到了Java類庫中的Arrays.sort()這個方法來參與PK,測試數據是50000個0到500000的整數。使用的是System.currentTimeMillis()這個方法來計時:
某幾回結果以下:
性能差異如此之大!顯然,這三個排序算法都「弱暴了」。
接下來來看看今天的第一個高級一點的算法,也就是傳說中第一批被證實是突破了N的平方運行時間的排序算法。
希爾排序(Shell Sort):
先來看看具體的程序:
public static void sort(int[] array) { for (int step = array.length / 2; step > 0; step /= 2) { for (int i = step; i < array.length; i++) { int temp = array[i]; int j = i; while (j >= step && temp < array[j - step]) { array[j] = array[j - step]; j -= step; } array[j] = temp; } } }
這~~~看起來是如此簡單的代碼。很難想像它有什麼牛X之處。我還記得當時這個算法真是把我給糾結了好久,從代碼上看,它有兩個for循環嵌套,裏面還有一個while循環。看起來時間複雜度很像是N的三次方吧。
再有,當step爲1的時候,看看,是否是和直接插入排序的代碼是如出一轍的。那這個算法怎麼可能會快啊!
希爾排序有時被叫作縮減增量排序(diminishing increment sort),使用一個序列h1,h2,h3……這樣一個增量序列。只要h1=1時,任何增量序列都是能夠的。但有些可能更好。對於希爾排序爲何會比直接插入排序快的緣由,咱們能夠來看一個比較極端的例子:
假如對於一個數組{8,7,6,5,4,3,2,1}以從小到大的順序來排。直接插入排序顯然是很悲劇的了。
它的每次排序結果是這樣的:
7, 8, 6, 5, 4, 3, 2, 1
6, 7, 8, 5, 4, 3, 2, 1
5, 6, 7, 8, 4, 3, 2, 1
4, 5, 6, 7, 8, 3, 2, 1
3, 4, 5, 6, 7, 8, 2, 1
2, 3, 4, 5, 6, 7, 8, 1
1, 2, 3, 4, 5, 6, 7, 8
而後咱們來看看Shell排序會怎樣處理,一開始步長爲4
數組分爲8, 7, 6, 5和4, 3, 2, 1
首先是7和4進行比較,交換位置。
變成了4, 7, 6, 5和8, 3, 2, 1
同理7和3,6和2,5和1也是樣的,因此當步長爲4時的結果是:
4, 3, 2, 1, 8, 7, 6, 5
能夠看到,大的數都在後邊了。
接下來的步長爲2
這一步過程就多了不少:
一開始是4和2進行比較,交換,獲得:
2, 3, 4, 1, 8, 7, 6, 5
3和1比較,交換,獲得:
2, 1, 4, 3, 8, 7, 6, 5
接下來是4和8,3和7,這兩個比較沒有元素交換。接下來8和6,7和5就須要交換了。因此步長爲2時的結果就是:
2, 1, 4, 3, 6, 5, 8, 7
能夠明顯地感受到,數組變得「基本有序」了。
接下來的步長1,變成了直接插入排序。手動模擬一下就能夠發現,元素的交換次數只有四次!這是至關可觀的。也由此咱們能夠獲得一個基本的事實:對於基本有序的數組,使用直接插入排序的效率是很高的!
那回到咱們一開始的問題,希爾排序爲何會快?
首先說明一下,我上邊的例子是極端的,不能做爲正常狀況來看的。但咱們能夠看出一點端倪:
希爾排序對元素的移動效率比直接排序要高;好比咱們看第一個步長4時,直接就把4,3,2,1這四個元素的位置向前移動了4位,比起直接插入排序的一次進一步要明顯高效得多。
其次,希爾每次都將數據變得「更加有序」;這一個性質至關重要,由於它利用了上一次的排序結果,在此之上讓數據向「更加有序」更進一步。
最後,是一個觀察的事實,就是對於「基本有序」的數組而言,直接插入排序的效率是很高的,由於只須要交換少許的元素。
好的,咱們再來看看咱們寫的shell排序的效率怎樣:這一次是兩個重量級的選手,因此咱們把數據量提升到500000,看看shell排序和類庫中那個實現有多大的差距:
仍是有差距,但比起上次那秒殺級的差距這個結果絕對能夠接受了。要知道,類庫個的那個算法能夠用了「老長老長」的代碼~~~
還有三個比較麻煩的算法。一次是講不完的了。
先總結一下我的的一點體會:
對於排序而言,提升速度的方法明顯的有兩個,一個是減小數據的比較次數,一個是減小交換次數。
對於冒泡來講,它這兩個方法都是最差的。
而選擇排序明顯就減小了交換的次數。
而直接插入排序顯然在比較次數上要比選擇要少,由於咱們是從右至左找到合適的位置就中止。
而希爾排序相對於直接插入排序在數據交換次數上,要少得多。另外就是很好的利用了「基本有序」這個性質。在比較次數上也會少不少。
自己菜鳥一個,這些都是我的的總結,認識不足、甚至錯誤在所不免。但願各位指出。