整理系統的時候發現了原來寫的各類算法的總結,看了一下,大吃一驚,那時候的我還如此用心,具體的算法,有的已經模糊甚至忘記了,看的時候就把內容整理出來,順便在熟悉一下,之後須要的時候就能夠直接過來摘抄了。下面是總結的幾個經常使用的排序算法:算法
可能你們對插入排序,快速排序,冒泡排序比較經常使用,在知足需求的時候也簡單一些,下面逐一說一下每一個算法的實現方式,不保證是寫的最有效率的,可是能保證的是,各類算法的中心思想是對的,你們領會精神便可:api
插入排序:數組
插入排序在生活中最真實的場景就是在打牌的時候了,尤爲在鬥地主的時候使用的最頻繁了,咱們天天在不知不覺中都在使用這種算法,因此請不要告訴你們你不會算法,來咱們你們一塊兒重溫那舒適的時刻,首先上家搬牌,做爲比較聰明伶俐的人,手急眼快的人,我喜歡不看牌,叩成一摞,最後看,這跟排序算法沒毛關係,略過,都抓完以後看牌。從左到右,看抓的什麼牌,一看,第一圈抓了個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,以此類推,不知不覺中就完成了插入排序的算法。ide
過程就是這個樣子,咱們看一下在代碼中如何實現: ui
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開始,因此看起來可能以爲怪怪的,這讓這段代碼看起來可能有點很差理解,this
第2-10行:的循環就是咱們看牌的過程,爲何從第張開始,是由於看第一張的時候沒有比較,姑且認爲它(點數爲5)是最小的,spa
第3行:的意思就是告訴我當前看的這張牌將要和那個位置的牌作比較(應該都是跟如今看的牌所在位置的前一個位置的牌作比較),我第二張抓到的是2,他就要跟第與第一張比較,3d
第4行:看這張牌是什麼點數。 code
第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行是逐個桶中去把數值拿出來,拿出來的數值就是排序完成的。
幾個常見的排序算法就說完了,不一樣的算法解決不一樣場景下的問題,歡迎你們和我進行交流。
下面是源代碼的下載地址: