常見的八種排序方法

八種排序的時間複雜度:

排序法 平均時間 最差情形 穩定度 額外空間 備註
冒泡 O(n2)     O(n2) 穩定 O(1) n小時較好
選擇 O(n2) O(n2) 不穩定 O(1) n小時較好
插入 O(n2) O(n2) 穩定 O(1) 大部分已排序時較好
基數 O(logRB) O(logRB) 穩定 O(n)

B是真數(0-9),

		<p>R是基數(個十百)</p>
		</td>
	</tr><tr><td>Shell</td>
		<td>O(nlogn)</td>
		<td>O(ns) 1&lt;s&lt;2</td>
		<td>不穩定</td>
		<td>O(1)</td>
		<td>s是所選分組</td>
	</tr><tr><td>快速</td>
		<td>O(nlogn)</td>
		<td>O(n2)</td>
		<td>不穩定</td>
		<td>O(nlogn)</td>
		<td>n大時較好</td>
	</tr><tr><td>歸併</td>
		<td>O(nlogn)</td>
		<td>O(nlogn)</td>
		<td>穩定</td>
		<td>O(1)</td>
		<td>n大時較好</td>
	</tr><tr><td>堆</td>
		<td>O(nlogn)</td>
		<td>O(nlogn)</td>
		<td>不穩定</td>
		<td>O(1)</td>
		<td>n大時較好</td>
	</tr></tbody></table></div><p>&nbsp;</p>

冒泡排序(Bubble Sort):

傳統的冒泡排序需要依次比較相鄰的兩個元素,按照升序或者降序的規則進行交換,如要讓 3 2 1三個數進行升序排序,首先從3開始跟2比較大於2進行交換,然後在與1進行比較,進行交換,第一趟排序結果就是2 1 3;然後 2與1比較大於1交換,2與3比較小於3不變。這就是冒泡排序的原理。然而,如果是讓 3 2 1進行降序排序呢,還要再從頭到尾比較一邊豈不是很浪費空間,此時我們就可以使用一個 flag標記來對冒泡排序進行排序。具體方法我們來看一下代碼。

演示:

代碼:

一般的冒泡排序:

  1. int arr[]={6,2,3,4,0};
  2. int change=0;
  3. for(int i=0;i<arr.length-1;i++){
  4. for(int j=0;j<arr.length-1-i;j++){
  5. if(arr[j]>arr[j+1]){
  6. change=arr[j];
  7. arr[j]=arr[j+1];
  8. arr[j+1]=change;
  9. }
  10. }
  11. System.out.println("第"+(i+1)+"趟"+Arrays.toString(arr));
  12. }

通過標記 flag來判斷 是否進行了交換,如果沒有則直接退出循環,從而達到了對排序的進一步優化。

優化後的冒泡排序:

  1. int arr[]={6,2,3,4,0};
  2. int change=0;
  3. Boolean flag=false;
  4. for(int i=0;i<arr.length-1;i++){
  5. for(int j=0;j<arr.length-1-i;j++){
  6. if(arr[j]>arr[j+1]){
  7. flag=true;
  8. change=arr[j];
  9. arr[j]=arr[j+1];
  10. arr[j+1]=change;
  11. }
  12. }
  13. if(!flag){
  14. break;
  15. }else {
  16. flag=false;
  17. }
  18. System.out.println("第"+(i+1)+"趟"+Arrays.toString(arr));
  19. }

選擇排序(Select Sort):

第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然後再從剩餘的未排序元素中尋找到最小(大)元素,然後放到已排序的序列的末尾。以此類推,直到全部待排序的數據元素的個數爲零。選擇排序是不穩定的排序方法。

假設序列第一個位置爲最小,然後依次和後面的元素進行比較,比第一個元素小的元素就設個標記再往後依次比較,直到找到最小值然後與第一個位置元素進行交換。

演示:

代碼:

  1. int[] arry= {7,4,8,3};
  2. for(int i = 0;i<arry.length-1;i++){
  3. int minIndex=i;
  4. int minArry=arry[i];
  5. for (int j = i+1;j<arry.length;j++){
  6. if(minArry>arry[j]){
  7. minIndex=j;
  8. minArry=arry[j];
  9. }
  10. }
  11. if(minIndex!=i){
  12. arry[minIndex]=arry[i];
  13. arry[i]=minArry;
  14. }
  15. System.out.println(Arrays.toString(arry));
  16. }

用過代碼我們不難發現冒泡排序和選擇排序的時間複雜度都是O(n²),接下來我們看一下他們的區別:

(1)冒泡排序是比較相鄰位置的兩個數,而選擇排序是按順序比較,找最大值或者最小值;

(2)冒泡排序每一輪比較後,位置不對都需要換位置,選擇排序每一輪比較都只需要換一次位置;

(3)冒泡排序是通過數去找位置,選擇排序是給定位置去找數;

 冒泡排序優缺點:優點:比較簡單,空間複雜度較低,是穩定的;
                              缺點:時間複雜度太高,效率慢;

選擇排序優缺點:優點:一輪比較只需要換一次位置;

                             缺點:效率慢,不穩定(舉個例子5,8,5,2,9   我們知道第一遍選擇第一個元素5會和2交換,那麼原序列中2個5的相對位置前後順序就破壞了)

 

插入排序(Inser Sort):

插入排序的基本操作就是將一個數據插入到已經排好序的有序數據中,從而得到一個新的、個數加一的有序數據,算法適用於少量數據的排序,時間複雜度爲O(n^2)。是穩定的排序方法。

 在一個數組中我們開始把第一個元素看爲是一個排好序的隊列,剩餘的看爲是亂序的序列,在剩餘的隊列中依次選取元素與前面隊列進行比較再爾進行排序,至此我們就保證前面的元素永遠都是有序的,從而達到插入排序的效果。

演示:

代碼:

  1. int arry[]={7,5,4,3,2};
  2. for(int i=1;i<arry.length;i++){
  3. int insertVal = arry[i];
  4. int insertIndex=i-1;
  5. while(insertIndex >= 0 && insertVal<arry[insertIndex]){
  6. arry[insertIndex+1]=arry[insertIndex];
  7. insertIndex--;
  8. }
  9. arry[insertIndex+1]=insertVal;
  10. System.out.println(Arrays.toString(arry));
  11. }

 

優化後的代碼:

優化後的代碼我們可以發現,通過insertIndex是否變化 來判斷while循環是否執行,從而避免不必要的賦值。

  1. int arry[]={7,5,4,3,2};
  2. for(int i=1;i<arry.length;i++){
  3. int insertVal = arry[i];
  4. int insertIndex=i-1;
  5. while(insertIndex >= 0 && insertVal<arry[insertIndex]){
  6. arry[insertIndex+1]=arry[insertIndex];
  7. insertIndex--;
  8. }
  9. if(insertIndex+1!=i){ //此時無需交換
  10. arry[insertIndex+1]=insertVal;
  11. }
  12. System.out.println(Arrays.toString(arry));
  13. }

希爾排序(Shell Sort):

對於簡單插入排序,存在一個效率的問題如2,3,4,5,1這個數組,使用插入排序他的順序是

{2 3 4 5 5}
{2 3 4 4 5}
{2 3 3 4 5}
{2 2 3 4 5}
{1 2 3 4 5}  不難看出 我們只需要換一個數然後過程執行了五次,對效率有很大影響,此時我們可以引入一種改進後的插入排序,那就是希爾排序,也叫縮小增量排序。

希爾排序就是按照下標一定增量進行分組,每組再按照直接插入算法排序,隨着組的減少,每組的元素也越來越少,當組數減少至爲1時,整個文件分成1組,算法便終止。

演示:

代碼:

(交換法)

  1. package sort;
  2. import java.util.Arrays;
  3. public class ShellSort {
  4. public static void main(String[] args) {
  5. int temp=0;
  6. int arry[]={3,2,1,6,5,7,8,4,9,0};
  7. // gap代表分了多少組
  8. for(int gap =arry.length/2;gap>0;gap/=2){
  9. //i代表第幾組開始是 第0 6個 1 7個 2 8個 3 9個 4 10個爲一組
  10. for (int i=gap;i<arry.length;i++){
  11. //j代表第幾組的第幾個元素
  12. for(int j=i-gap;j>=0;j-=gap){
  13. if(arry[j]>arry[j+gap]){
  14. temp=arry[j];
  15. arry[j]=arry[j+gap];
  16. arry[j+gap]=temp;
  17. }
  18. }
  19. }
  20. System.out.println(Arrays.toString(arry));
  21. }
  22. }
  23. }

(移步法)

  1. int arry[]={3,2,1,6,5,7,8,4,9,0};
  2. // gap代表分了多少組
  3. for(int gap =arry.length/2;gap>0;gap/=2){
  4. //i代表第幾組開始是 第0 6個 1 7個 2 8個 3 9個 4 10個爲一組 共五個組那麼0 和6肯定爲一組
  5. for (int i=gap;i<arry.length;i++){
  6. int j=i;
  7. int temp=arry[j];
  8. if(arry[j]<arry[j-gap]){
  9. while(j-gap>=0 && temp < arry[j - gap]){
  10. arry[j]=arry[j-gap];
  11. j-=gap;
  12. }
  13. arry[j]=temp;
  14. }
  15. }
  16. System.out.println(Arrays.toString(arry));
  17. }
  18. }

快速排序(Quick Sort):

通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然後再按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。大致意思就是在一個數組中取中間元素比它小的方左邊比它大的則放右邊 兩邊元素再按照快排要求,最終變成有序序列。

演示:

代碼:

  1. public static void quickSort(int[] arr, int left, int right) {
  2. int l = left;// 左下標
  3. int r = right;// 右下標
  4. int pivot = arr[(left + right) / 2];// 找到中間的值
  5. // 將比pivot小的值放在其左邊,比pivot大的值放在其右邊
  6. while (l < r) {
  7. // 在pivot左邊尋找,直至找到大於等於pivot的值才退出
  8. while (arr[l] < pivot) {
  9. l += 1;// 將l右移一位
  10. }
  11. // 在pivot右邊尋找,直至找到小於等於pivot的值才退出
  12. while (arr[r] > pivot) {
  13. r -= 1;// 將r左移一位
  14. }
  15. if (l >= r) {
  16. // 左右下標重合,尋找完畢,退出循環
  17. break;
  18. }
  19. // 交換元素
  20. int temp = arr[l];
  21. arr[l] = arr[r];
  22. arr[r] = temp;
  23. //倘若發現值相等的情況,則沒有比較的必要,直接移動下標即可
  24. // 如果交換完後,發現arr[l]==pivot,此時應將r左移一位
  25. if (arr[l] == pivot) {
  26. r -= 1;
  27. }
  28. // 如果交換完後,發現arr[r]==pivot,此時應將l右移一位
  29. if (arr[r] == pivot) {
  30. l += 1;
  31. }
  32. }
  33. // 如果l==r,要把這兩個下標錯開,否則會出現無限遞歸,導致棧溢出的情況
  34. if (l == r) {
  35. l += 1;
  36. r -= 1;
  37. }
  38. // 向左遞歸
  39. if (left < r) {
  40. quickSort(arr, left, r);
  41. }
  42. // 向右遞歸
  43. if (right > l) {
  44. quickSort(arr, l, right);
  45. }
  46. }

 

歸併排序(Merge Sort):

 

歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。
 

 

演示:

代碼:

  1. public static void mergeSort(int[] arr, int left, int right, int[] temp) {
  2. // 分解
  3. if (left < right) {
  4. int mid = (left + right) / 2;// 中間索引
  5. // 向左遞歸進行分解
  6. mergeSort(arr, left, mid, temp);
  7. // 向右遞歸進行分解
  8. mergeSort(arr, mid + 1, right, temp);// mid + 1,中間位置的後一個位置纔是右邊序列的開始位置
  9. // 每分解一輪便合併一輪
  10. merge(arr, left, right, mid, temp);
  11. }
  12. }
  13. /**
  14. * 合併的方法
  15. *
  16. * @param arr 待排序的數組
  17. * @param left 左邊有序序列的初始索引
  18. * @param right 中間索引
  19. * @param mid 右邊有序序列的初始索引
  20. * @param temp 做中轉的數組
  21. */
  22. public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
  23. int i = left; // 初始化i,左邊有序序列的初始索引
  24. int j = mid + 1;// 初始化j,右邊有序序列的初始索引(右邊有序序列的初始位置即爲中間位置的後一個位置)
  25. int t = 0;// 指向temp數組的當前索引,初始爲0
  26. // 先把左右兩邊的數據(已經有序)按規則填充到temp數組
  27. // 直到左右兩邊的有序序列,有一邊處理完成爲止
  28. while (i <= mid && j <= right) {
  29. // 如果左邊有序序列的當前元素小於或等於右邊有序序列的當前元素,就將左邊的元素填充到temp數組中
  30. if (arr[i] <= arr[j]) {
  31. temp[t] = arr[i];
  32. t++;// 索引後移
  33. i++;// i後移
  34. } else {
  35. // 反之,將右邊有序序列的當前元素填充到temp數組中
  36. temp[t] = arr[j];
  37. t++;// 索引後移
  38. j++;// j後移
  39. }
  40. }
  41. // 把有剩餘數據的一邊的元素填充到temp中
  42. while (i <= mid) {
  43. // 此時說明左邊序列還有剩餘元素
  44. // 全部填充到temp數組
  45. temp[t] = arr[i];
  46. t++;
  47. i++;
  48. }
  49. while (j <= right) {
  50. // 此時說明左邊序列還有剩餘元素
  51. // 全部填充到temp數組
  52. temp[t] = arr[j];
  53. t++;
  54. j++;
  55. }
  56. // 將temp數組的元素複製到原數組
  57. t = 0;
  58. int tempLeft = left;
  59. while (tempLeft <= right) {
  60. arr[tempLeft] = temp[t];
  61. t++;
  62. tempLeft++;
  63. }
  64. }

 

基數排序(radix sort):

基數排序會分配是個桶 標號分別是0-9,在第一次排序時會將每個元素的個位取出,放到相應編號的桶中,然後按照桶的順序依次放回原來的數組;進行第二次排序時,會將每個元素的十位取出放到相應編號的桶中,然後按照桶的順序依次放回原來的數組;以此類推直到最高位排完,排序也就完成。

演示:

數組{718,34,72,401,64}進行基數排序

 

第一趟排序結果{ 401 ,72 ,34 , 64,718}

第二趟排序結果{401,718,34,64,72}

第三趟排序結果{34,64,72,401,718}  排序完成 將元素返回原數組

 

代碼:

  1. public static void raixSort(int[] arr) {
  2. // 第一輪(針對每個元素的個位進行排序處理)
  3. // 定義一個二維數組,模擬桶,每個桶就是一個一維數組
  4. // 爲了防止放入數據的時候桶溢出,我們應該儘量將桶的容量設置得大一些
  5. int[][] bucket = new int[10][arr.length];
  6. // 記錄每個桶中實際存放的元素個數
  7. // 定義一個一維數組來記錄每個桶中每次放入的元素個數
  8. int[] bucketElementCounts = new int[10];
  9. for (int j = 0; j < arr.length; j++) {
  10. // 取出每個元素的個位
  11. int digitOfElement = arr[j] % 10;
  12. // 將元素放入對應的桶中
  13. // bucketElementCounts[digitOfElement]就是桶中的元素個數,初始爲0,放在第一位
  14. bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
  15. // 將桶中的元素個數++
  16. // 這樣接下來的元素就可以排在前面的元素後面
  17. bucketElementCounts[digitOfElement]++;
  18. }
  19. // 按照桶的順序取出數據並放回原數組
  20. int index = 0;
  21. for (int k = 0; k < bucket.length; k++) {
  22. // 如果桶中有數據,才取出放回原數組
  23. if (bucketElementCounts[k] != 0) {
  24. // 說明桶中有數據,對該桶進行遍歷
  25. for (int l = 0; l < bucketElementCounts[k]; l++) {
  26. // 取出元素放回原數組
  27. arr[index++] = bucket[k][l];
  28. }
  29. }
  30. // 第一輪處理後,需要將每個bucketElementCounts[k]置0
  31. bucketElementCounts[k] = 0;
  32. }
  33. System.out.println("第一輪:" + Arrays.toString(arr));
  34. // ----------------------------
  35. // 第二輪(針對每個元素的十位進行排序處理)
  36. for (int j = 0; j < arr.length; j++) {
  37. // 取出每個元素的十位
  38. int digitOfElement = arr[j] / 10 % 10;
  39. // 將元素放入對應的桶中
  40. // bucketElementCounts[digitOfElement]就是桶中的元素個數,初始爲0,放在第一位
  41. bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
  42. // 將桶中的元素個數++
  43. // 這樣接下來的元素就可以排在前面的元素後面
  44. bucketElementCounts[digitOfElement]++;
  45. }
  46. // 按照桶的順序取出數據並放回原數組
  47. index = 0;
  48. for (int k = 0; k < bucket.length; k++) {
  49. // 如果桶中有數據,才取出放回原數組
  50. if (bucketElementCounts[k] != 0) {
  51. // 說明桶中有數據,對該桶進行遍歷
  52. for (int l = 0; l < bucketElementCounts[k]; l++) {
  53. // 取出元素放回原數組
  54. arr[index++] = bucket[k][l];
  55. }
  56. }
  57. // 第二輪處理後,需要將每個bucketElementCounts[k]置0
  58. bucketElementCounts[k] = 0;
  59. }
  60. System.out.println("第二輪:" + Arrays.toString(arr));
  61. // ----------------------------
  62. // 第三輪(針對每個元素的百位進行排序處理)
  63. for (int j = 0; j < arr.length; j++) {
  64. // 取出每個元素的百位
  65. int digitOfElement = arr[j] / 100 % 10;
  66. // 將元素放入對應的桶中
  67. // bucketElementCounts[digitOfElement]就是桶中的元素個數,初始爲0,放在第一位
  68. bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
  69. // 將桶中的元素個數++
  70. // 這樣接下來的元素就可以排在前面的元素後面
  71. bucketElementCounts[digitOfElement]++;
  72. }
  73. // 按照桶的順序取出數據並放回原數組
  74. index = 0;
  75. for (int k = 0; k < bucket.length; k++) {
  76. // 如果桶中有數據,才取出放回原數組
  77. if (bucketElementCounts[k] != 0) {
  78. // 說明桶中有數據,對該桶進行遍歷
  79. for (int l = 0; l < bucketElementCounts[k]; l++) {
  80. // 取出元素放回原數組
  81. arr[index++] = bucket[k][l];
  82. }
  83. }
  84. // 第三輪處理後,需要將每個bucketElementCounts[k]置0
  85. bucketElementCounts[k] = 0;
  86. }
  87. System.out.println("第三輪:" + Arrays.toString(arr));
  88. }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

        <div class="person-messagebox">
            <div class="left-message"><a href="https://blog.csdn.net/qq_42183409">
                <img src="https://profile.csdnimg.cn/D/0/F/3_qq_42183409" class="avatar_pic" username="qq_42183409">
            </a></div>
            <div class="middle-message">
                                    <div class="title"><span class="tit "><a href="https://blog.csdn.net/qq_42183409" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;,&quot;ab&quot;:&quot;new&quot;}" target="_blank">Ysdo</a></span>
                    <!-- 等級,level -->
                                            <img class="identity-icon" src="https://csdnimg.cn/identity/blog5.png">                                            </div>
                <div class="text"><span>原創文章 181</span><span>獲贊 41</span><span>訪問量 9萬+</span></div>
            </div>
                            <div class="right-message">
                                        <a class="btn btn-sm attented bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;,&quot;ab&quot;:&quot;new&quot;}">已關注</a>
                                                            <a href="https://im.csdn.net/im/main.html?userName=qq_42183409" target="_blank" class="btn btn-sm bt-button personal-letter">私信
                    </a>
                                </div>
                        </div>
                    
    </div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章