整理系統的時候發現了原來寫的各種算法的總結,看了一下,大吃一驚,那時候的我還如此用心,具體的算法,有的已經模糊甚至忘記了,看的時候就把內容整理出來,順便在熟悉一下,以后需要的時候就可以直接過來摘抄了。下面是總結的幾個常用的排序算法:
- 插入排序
- 快速排序
- 冒泡排序
- 堆排序
- 計數排序
- 桶排序
可能大家對插入排序,快速排序,冒泡排序比較常用,在滿足需求的時候也簡單一些,下面逐一說一下每個算法的實現方式,不保證是寫的最有效率的,但是能保證的是,各種算法的中心思想是對的,大家領會精神即可:
插入排序:
插入排序在生活中最真實的場景就是在打牌的時候了,尤其在斗地主的時候使用的最頻繁了,我們每天在不知不覺中都在使用這種算法,所以請不要告訴大家你不會算法,來我們大家一起重溫那溫馨的時刻,首先上家搬牌,作為比較聰明伶俐的人,手急眼快的人,我喜歡不看牌,叩成一摞,最后看,這跟排序算法沒毛關系,略過,都抓完之后看牌。從左到右,看抓的什么牌,一看,第一圈抓了個5,第二圈來個2,假如說有強迫症,牌必須是按照順序排好的,由於按照順序排放,所以要滿足,如果你左邊的第一張牌的點數小於你抓牌的點數,那么左邊所有牌的點數都小於所抓牌的點數,這時候你就要比較,5>2(只論點數),然后你手里的牌就是2---5,第三圈來個4,牌都好小,這時候你就拿4和5比(或者拿2比都一樣),4<5,然后4應該在5的左邊,現在的順序暫時是2---4---5,因為還不確定4左邊的牌的點數是不是小於4,所有要找到所在位置左邊第一個小於4的點數,把4放在這個點數的右邊,好巧2<4,所以現在的順序確定為2---4---5,第四輪抓了一個3,都太小了,但是依然需要把3插到對應的位置,首先3<5,那么放到5的前面,順序暫時是2---4---3---5,3繼續向左邊尋找,知道找到比它小的那張牌,然后它和4比較結果暫時為2---3---4---5,然后在跟2做對比,得出結果,順序為2---3---4---5,以此類推,不知不覺中就完成了插入排序的算法。
過程就是這個樣子,我們看一下在代碼中如何實現:
1 public void sort(int[] data) { 2 for (int j = 1; j < data.length; j++) { 3 int i = j - 1; 4 int key = data[j]; 5 while (i >= 0 && data[i] > key) { 6 data[i + 1] = data[i]; 7 data[i] = key; 8 i -= 1; 9 } 10 } 11 }
由於我用數組來實現,下標從0開始,所以看起來可能覺得怪怪的,這讓這段代碼看起來可能有點不好理解,
第2-10行:的循環就是我們看牌的過程,為什么從第張開始,是因為看第一張的時候沒有比較,姑且認為它(點數為5)是最小的,
第3行:的意思就是告訴我當前看的這張牌將要和那個位置的牌做比較(應該都是跟現在看的牌所在位置的前一個位置的牌做比較),我第二張抓到的是2,他就要跟第與第一張比較,
第4行:看這張牌是什么點數。
第5行:如果還沒有比到頭,切前一個位置牌的點數大於這張牌的點數,那么將他們倆換位置。
第6-7行:互換位置。
第8行:由於這張牌的位置前移了一位,那么下次做比較牌的位置也要向前移動一位。
OK,插入排序很簡單就說道這里了。適合數據量小的情況下排序使用。
快速排序:
請寬恕我生活閱歷不高,快速排序我確實是找不到合適生活中的應用場景了,就畫個小圖來說明一下吧:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 1 | 6 | 8 | 4 | 1222 | 29 | 872 | 5 |
這些數據是用來排序的,快速排序是通過把將要排序的數組進行拆分,然后局部進行排序,在程序開始的首先要取數組中的左后一個位置保存的值作為所進行排序的數組“分水嶺”,就是把比這個值小的數都放在這個數的左邊,比這個數大的數都放到這個數的右邊。這是如何實現的,無非是兩方面,比較和位置調整。
需要將數組中的所有數據都進行比較,如果有需要,那么調整位置,調整位置分為主動調整和被動調整兩種情況。
主動調整:
需要滿足兩個條件:(1)小於最后一個位置保存的數據值。(2)所在位置前面的位置中保存的數值大於最后一個位置保存的數值。
主動調整的時候要將數據保存最后一個已經比較過得知保存的數值小於最后一個位置保存的數據值位置的后一位,是不是特別拗口,那上面的例子為例上面表格中上面的數字是對應數組的下標,在第一次比較的時候,比較1<5,前面沒有大於5的值,位置不用變,6>5,也不用變,8>5,也不用變,4<5,且在坐標為1的位置上保存的數值為6,所有4需要主動調整,此時最后一個已經比較過得知保存的數值小於最后一個位置保存的數據值的位置為0,要講4保存在1的位置,但是1的位置保存的值是6,所有6就要做被動調整。
被動調整:
由於本身的位置即將被別人占用,所做的調整。被動調整位置就是和發起主動調整的數據進行位置互換,最后將發起主動調整的數值保存到空出來的坐標上,發生后移的數值可以肯定的認為是大於最后一個下標所保存的數值的,不然早就被主動調整了。如果還是不好理解那么還是以本例子為例:在比較到4的時候,需要調整,需要被動調整的下標起始位置為1,發起主動調整的數值的下標為3,那么就和小標3保存的值8進行位置互換,后移之后的結果為:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 1 | 4 | 8 | 6 | 1222 | 29 | 872 | 5 |
第一輪比較之后,發現已經沒有需要調整位置的時候了,這時候該調整最后一個下標保存值的位置了,因為前面說在先要取數組中的左后一個位置保存的值作為所進行排序的數組“分水嶺”所以大家應該知道需要把下標為7保存的值5主動到下標下標為2的位置。然后進行位置調整。調整方法前面已經說過,不再重復。最終結果如下:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 1 | 4 | 5 | 6 | 1222 | 29 | 872 | 8 |
接下來將要分配的數組分為兩部分:
| 0 | 1 | 2 |
| 1 | 4 | 5 |
| 3 | 4 | 5 | 6 | 7 |
| 6 | 1222 | 29 | 872 | 8 |
對這兩部分重新進行操作,所以快速排序,需要用遞歸的方式計算。下面就需要保存的幾個變量做一下說明:
第一:是最后一個下標的值,因為在這個值在進行主動調整位置的時候,所在數組下標的位置會被覆蓋。如上例中的下標 7.
第二:正在與最后值進行比較的數據的下標k,保存k是為了通過比較之后萬一需要位置調整的時候,依次后移到的位置。
第三:已經與左后一個坐標保存的值比較過,明確不需要調整位置的最后一個數組下標i,保存這個變量是因為,如果后面的第k個下標元素需要調整位置,需要i+1下標上的數值和k下標的數值互換。
下面來看一下快速排序算法的代碼實現:
1 public void sort(int[] data) { 2 quickSort(data, 0, data.length - 1); 3 } 4 5 public void quickSort(int[] data, int p, int r) { 6 if (p < r) { 7 int q = partition(data, p, r); 8 quickSort(data, p, q - 1); 9 quickSort(data, q + 1, r); 10 } 11 } 12 13 public int partition(int[] data, int p, int r) { 14 int x = data[r]; 15 int i = p - 1; 16 for (int j = p; j <= r; j++) { 17 if (data[j] < x && i < j) { 18 i++; 19 int key = data[j]; 20 data[j] = data[i]; 21 data[i] = key; 22 // if (i != j) { 23 // data[i] += data[j]; 24 // data[j] = data[i] - data[j]; 25 // data[i] -= data[j]; 26 // } 27 } 28 } 29 int key = x; 30 data[r] = data[i + 1]; 31 data[i + 1] = key; 32 33 // if ((i + 1) != r) { 34 // data[i + 1] += data[r]; 35 // data[r] = data[i + 1] - data[r]; 36 // data[i + 1] -= data[r]; 37 // } 38 39 return 1 + i; 40 }
如果要是能夠將注釋部分互換位置的部分鬧明白,尤其是判斷條件,那么就真的理解快速排序法了。例子中quickSort方法實現了遞歸調用,重要的方法是partition,它完成了拆分排序數組,和位置調整的操作。
在partition中,我們用x來保存要比較的值。用i來保存已經確定的不需要位置調整的下標,從-1開始,用j來保存正在比較驗證的數值的數組下標,從0開始。
冒泡排序:
這個應該是最常見的,大概也就是在要排序的集合的最后面開始,用第一個元素依次和前面的比較,如果比前面的小,那么互換位置,然后在跟前一個比較,直到比較到一個位置(這個位置前面的數值一定小於正在比較的數值)。所以也需要用到兩個變量,一個用來標記正在比較的數值的下標,一個用來保存那個“位置”,猜測大家都知道,所以就對於冒泡排序就不多廢話了,上代碼:
1 public void sort(int[] data) { 2 for (int i = 0; i < data.length; i++) { 3 for (int j = data.length - 1; j > i + 1; j--) { 4 if (data[j - 1] > data[j]) { 5 data[j - 1] = data[j - 1] + data[j]; 6 data[j] = data[j - 1] - data[j]; 7 data[j - 1] -= data[j]; 8 } 9 } 10 } 11 }
很簡單。
堆排序:
不得不說,堆排序我已經不記得了,看了一會才看明白,看明白之后,我就對發明堆派尋的人很崇拜,多么牛逼的算法。
總的來說堆排序就是把數組a[0.....n]中總找出一個最大值,然后把最大值max放到數組的最后a[n]=max,然后再從a[0.....n-1]中找出一個最大值,然后把最大值保存在a[0.....n-1]中,依次類推。堆排序的關鍵就是如何最快的找到一定范圍內的最大值。
堆排序是通過最大堆的方式找到最大值,先了解一些概念:
二叉堆:就理解成二叉樹就行了。
最大堆:滿足所有的滿足父>子。
最小堆:滿足所有子>父。
首要要做的就是把要排序的數組構造成一個二叉堆,臆想的,比如排序的數組為:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 1 | 6 | 8 | 4 | 1222 | 29 | 872 | 5 |
然后我們通過想象構造出了如下一個二叉堆,構造二叉堆的方法很簡單,從頭到位按照二叉樹的方式構造,構造結果如下:

通過構造的二叉堆我們需要得出對我們有用的信息,有用的信息就是指每個元素在二叉堆中的孩子節點所在數組中的坐標,知道二叉樹的人不用看二叉堆都知道節點i的子節點的下標應該是2*i+1,已經2(i+1),因為我們是從下標0開始計算。數字就是通過下標來形象的描述二叉堆。
二叉堆構造完成以后,那么我們來說一下是如何將二叉堆構造成最大堆,構造方法是,用a[i]與他的兩個孩子a[2*i+1],a[2(i+1)](如果存在的話)比較,將a[i],a[2*i+1],a[2(i+1)]中最大的值與a[i]位置互換,那么i從哪開始算呢?很簡單就是找到最后一個有孩子的節點,因為我們是從0開始計算,那么這個下標應該是從a.lenth-1/2到0,這樣一輪下來a[0]就是最大值,代碼實現:
1 package sort; 2 3 /** 4 * O(nlgn) 把數據假象構造成一個二叉堆,每個節點的左右坐標跟別為2i,2i+1. 構造Max樹: 5 * 6 * @author Think 7 * 8 */ 9 public class HeapSort implements ISort { 10 int dataLength = 0; 11 12 /*** 13 * 將最大的值拿出來,放到最后,然后通過1--n-1個數據,找新的最大數 14 */ 15 @Override 16 public void sort(int[] data) { 17 dataLength = data.length; 18 buildMaxHeap(data); 19 for (int i = dataLength - 1; i >= 1; i--) { 20 data[i] += data[0]; 21 data[0] = data[i] - data[0]; 22 data[i] -= data[0]; 23 dataLength--; 24 max_heapify(data, 0); 25 } 26 } 27 28 /** 29 * 保證二叉堆的性質 A[i] >= A[left[i]] A[i] >= A[right[i]] 30 * 在構造二叉堆和每次從最后移除一個元素以后都要重新組織二叉堆的結構 31 * 32 * @param data 33 * @param i 34 */ 35 public void max_heapify(int[] data, int i) { 36 int largest = 0; 37 int l = 2 * i + 1; 38 int r = 2 * (i + 1); 39 if (l < dataLength && data[l] > data[i]) { 40 largest = l; 41 } else { 42 largest = i; 43 } 44 if (r < dataLength && data[r] > data[largest]) { 45 largest = r; 46 } 47 if (largest != i) { 48 data[i] += data[largest]; 49 data[largest] = data[i] - data[largest]; 50 data[i] -= data[largest]; 51 max_heapify(data, largest); 52 } 53 } 54 55 /** 56 * 構建最大堆 57 * 58 * @param data 59 */ 60 public void buildMaxHeap(int[] data) { 61 for (int j = ((dataLength - 1) / 2); j >= 0; j--) { 62 max_heapify(data, j); 63 } 64 } 65 }
在sort方法中,做的內容是構造最大堆,把通過最大堆獲取的最大值a[0]與a數組的最后一個下標保存的數值進行位置交換,然后在將最大值從構造最大堆的數據中排除。排除的方法是通過設置最大堆時候數組的界限,在例子中dataLength變量就是這個作用。
buildMaxHeap方法是第一次構造最大堆,大家可能有疑問在buildMaxHeap中調用max_heapify方法和在sort方法中調用max_heapify方法的第二個參數為何不一樣,為什么有的從dataLength - 1開始遞減,有的一直傳0,有沒有想過,其實不難回答,因為在排序開始的時候buildMaxHeap方法必須先構造出一個最大堆,此時對數據是沒有任何假設的,數完全是隨機的,我們不能保證a[0]保存的數值不是最大的,如果a[0]保存的數值是最大的,那么在和代碼39-46行中largest變量永遠的值都為0,沒有結果,所以要從最下面開始逐一比較。但是在sort方法中執行max_heapify的時候對數據的排列就有了最大堆性質的保證。所以就可以從0開始,但是如果還是要從最后往前比較那也絕對是沒有問題的。
由於交換完位置以后,可能導致被交換下去的較小的值,有小於它下面子節點值的可能,例如在本例子中的最后交換到1時候,剛交換完的時候應該是這樣的:

所有為了確保最大堆的性質所以要遞歸排序,這段代碼是通過47-51行來完成的。
計數排序:
我個人喜歡計數排序----簡單,但是對於要排序的數據是有一些要求,比如要明確知道要排序的一組數的范圍,輸入的數據的值要在(0,k)的開區間內,以及數據最好要密集,如果這組數據的波動性較大不適合用技術排序的方式進行排序操作。計數排序的中心思想就是對於給定的數值key確定有多少數小於key,有多少值,那么這個值就是key值應該在排序后的數組中的下標。
方向找到以后就要找方法實現,計數排序的關鍵在於如何計算並且保存小於x值的數值的個數。技術排序是通過一個臨時數組來保存,這個數組聲明的長度就是我們前面所說的明確最大值的長度+1。具體如何保存的請看具體實現:
1 package sort; 2 3 /** 4 * 計數排序(前提條件,能夠預先知道所需排序的上限,需要多余的一點空間) 適合數據密集。有明確范圍的情況 5 * 6 * @author Think 7 * 8 */ 9 public class CountSort { 10 11 public int[] sort(int[] data, int max_limit) { 12 int[] tmp = new int[max_limit]; 13 int[] des = new int[data.length]; 14 for (int i = 0; i < data.length; i++) { 15 tmp[data[i]] += 1; 16 } 17 for (int j = 1; j < max_limit; j++) { 18 tmp[j] += tmp[j - 1]; 19 } 20 for (int k = data.length - 1; k >= 0; k--) { 21 des[tmp[data[k]] - 1] = data[k]; 22 tmp[data[k]] -= 1; 23 } 24 25 return des; 26 } 27 28 }
12-13行,聲明臨時數組和最終需要生成的數組。14-16行是計數排序比較巧妙的地方,在tmp中第data[i]個坐標上設置累加1,在tmp數組中下標k的值,是在data數組中存在k值的個數。比如說在data數組中有3個5的數值,那么在tmp[5]中保存的值是3。比如需要排序的數組為:
| 5 | 4 | 6 | 3 | 2 | 1 |
那么tmp數組經過14-16行的循環操作以后的內容為:
| 0 | 1 | 1 | 1 | 1 | 1 | 1 |
我們根據tmp數組中的數據,以及tmp數組的下標就能通過17-19行的內容,算出要排序的數組每個數值在排序中應該排在什么位置,經過17-19行的處理,tmp數組中的內容為:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
結合tmp的下標和數值可以知道,tmp數組的第(data[i])個下標保存的值減1,(tmp[data[i]]-1)就是data[i]數據經過排序后所在數組中的位置。
所以在20-23行進行賦值,排序完成。 其中22行中tmp[data[k]] -= 1這句話是用來處理在派尋的數組中有重復數值的情況,對重復值進行排序。
桶排序:
如果需要排序的數組中的數值分布均勻,而且在區間[0,1)內,那么桶排序是個不錯的選擇,桶排序就是把[0,1)區間的分割成10個大小相同的自區間,然后將數組中的數值恰當的“漏”到對應的區間中,比如0.12要漏到[0.1,0.2)的區間中,就是值k要滿足區間的左邊值<=k<區間的右邊值。然后在通過插入排序或者快速排序等方式對每個區間的數值進行派尋,下面是我實現的桶派尋的代碼,和書上說的有點不一樣:
1 package sort; 2 3 public class BucketSort { 4 5 public double[] sort(double[] data) { 6 return bucket_sort(data); 7 } 8 9 public double[] bucket_sort(double[] data) { 10 double[] des = new double[data.length]; 11 Bucket[] tmp = new Bucket[10]; 12 for (int i = 0; i < tmp.length; i++) { 13 tmp[i] = new Bucket(0, null); 14 } 15 for (int i = 0; i < data.length; i++) { 16 Bucket bucket = new Bucket(data[i], null); 17 int bucket_list_index = (int) (data[i] * 10); 18 bucket_in_sort(tmp[bucket_list_index], bucket); 19 } 20 int j = 0; 21 for (int i = 0; i < tmp.length; i++) { 22 Bucket tmp_bucket = tmp[i].next; 23 while (tmp_bucket != null) { 24 des[j] = tmp_bucket.value; 25 tmp_bucket = tmp_bucket.next; 26 j++; 27 } 28 } 29 return des; 30 } 31 32 public void bucket_in_sort(Bucket sourct_bucket, Bucket bucket) { 33 Bucket tmp = sourct_bucket.next; 34 if (tmp == null) { 35 sourct_bucket.next = bucket; 36 return; 37 } 38 while (tmp.next != null) { 39 if (tmp.value > bucket.value) { 40 bucket.next = sourct_bucket.next; 41 sourct_bucket.next = bucket; 42 break; 43 } 44 tmp = tmp.next; 45 } 46 tmp.next = bucket; 47 } 48 49 public class Bucket { 50 double value; 51 public Bucket next; 52 53 public Bucket(double value, Bucket bucket) { 54 this.value = value; 55 this.next = bucket; 56 } 57 58 public double getValue() { 59 return value; 60 } 61 62 public void setValue(double value) { 63 this.value = value; 64 } 65 66 public Bucket getBucketList() { 67 return next; 68 } 69 70 public void setBucketList(Bucket next) { 71 this.next = next; 72 } 73 74 } 75 76 }
Bucket是我定義的一個對象,來幫助我實現桶排序,有value和next兩個屬性,value用來保存排序的數值,next表示桶中的下一個對象,如果不存在那么為空。第12-14行在每個桶中初始化一個鏈表對象Bucket,將來順着這個鏈表連接“漏”在桶中的數值,第15-18行是給每個數值確定“漏”到那個桶中,然后在調用bucket_in_sort方法,在“漏”如桶中的過程中直接排序,將value值大的Bucket對象放到后面。第21-28行是逐個桶中去把數值拿出來,拿出來的數值就是排序完成的。
幾個常見的排序算法就說完了,不同的算法解決不同場景下的問題,歡迎大家和我進行交流。
下面是源代碼的下載地址:
http://files.cnblogs.com/fantiantian/Algorithm.rar
