常見的五種排序演算法

  • 2019 年 12 月 26 日
  • 筆記

冒泡排序

冒泡排序簡介

冒泡排序只會操作相鄰的兩個數據。每次冒泡操作都會對相鄰的兩個元素進行比較,看是否滿足大小關係要求。

如果不滿足就讓它倆互換。一次冒泡會讓至少一個元素移動到它應該在的位置,重複 n 次,就完成了 n 個數據的排序工作。大的數往下沉,小的數往下冒。 穩定、原地排序,最好時間複雜度為O(n),最壞與平均時間複雜度為O(n2)。

原地排序簡介

原地排序指的是空間複雜度為O(1)的排序演算法。

穩定排序簡介

穩定性指的是:如果待排序列中存在值相等的元素,經過排序之後,相等元素的先後順序沒有改變 比如我們有一組數據 2,9,3,4,8,3,按照大小排序之後就是 2,3,3,4,8,9。 這組數據里有兩個 3。經過某種排序演算法排序之後,如果兩個 3 的前後順序沒有改變,那我們就把這種排序演算法叫作穩定的排序演算法;如果前後順序發生變化,那對應的排序演算法就叫作不穩定的排序演算法。

冒泡程式碼程式碼實現

public class BubbleSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};          bubbleSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }         public static void bubbleSort(int[] data){          if(data.length<=1) return;          for(int i=0;i<data.length;i++){              //提前退出冒泡循環的標誌位              boolean flag = false;              for(int j=0;j<data.length-1-i;++j){                  if(data[j]>data[j+1]){                      int temp = data[j];                      data[j] = data[j+1];                      data[j+1] = temp;                      flag = true;//有數據交換                  }              }              if(!flag) break;//沒有數據交換,退出          }      }  }

插入排序

插入排序簡介

我們將數組中的數據分為兩個區間,已排序區間和未排序區間。初始已排序區間只有一個元素,就是數組的第一個元素。插入演算法的核心思想是取未排序區間中的元素,在已排序區間中找到合適的插入位置將其插入,並保證已排序區間數據一直有序。重複這個過程,直到未排序區間中元素為空,演算法結束。 穩定、原地排序,最好時間複雜度為O(n),最壞與平均時間複雜度為O(n2)。

程式碼實現

public class InsertSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{4,3,2,1,5};    insertSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }         public static void insertSort(int[] data){          if(data.length<=1) return;          for(int i=1;i<data.length;++i){              int value = data[i];              int j=i-1;              for(;j>=0;--j){                  if(data[j]>value){                      data[j+1] = data[j];                  }else {                      break;                  }              }              data[j+1]=value;          }      }  }

選擇排序

選擇排序簡介

選擇排序演算法的實現思路有點類似插入排序,也分已排序區間和未排序區間。但是選擇排序每次會從未排序區間中找到最小的元素,將其放到已排序區間的末尾。 不穩定、原地排序,最好時間複雜度為O(n2),最壞與平均時間複雜度為O(n2)。 選擇排序是一種不穩定的排序演算法。選擇排序每次都要找剩餘未排序元素中的最小值,並和前面的元素交換位置,這樣破壞了穩定性。

程式碼實現

public class SelectSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] data=new int[]{2,3,5,4,7,6,9,10,12,11,23,22,18,8};          selectSort(data);          for(int i=0;i<data.length;i++){              System.out.print(data[i]+" ");          }      }      public static void selectSort(int[] data){          if(data.length<=1) return;          for(int i=0;i<data.length-1;++i){              int minIndex=i;              for(int j=i+1;j<data.length;++j){                  if(data[j]<data[minIndex]){                      minIndex = j;                  }              }              int temp = data[i];              data[i] = data[minIndex];              data[minIndex] = temp;          }      }  }

歸併排序

歸併排序簡介

如果要排序一個數組,我們先把數組從中間分成前後兩部分,然後對前後兩部分分別排序,再將排好序的兩部分合併在一起,這樣整個數組就都有序了。 歸併排序用的是一種分治思想,它不是原地排序,即空間複雜度不是O(1),空間複雜度為O(n),不管最好還是最壞或平均,它的時間複雜度都為O(nlogn),是一個穩定的演算法。

程式碼實現

/**   * 歸併排序   * 將需要排序的數組從中間分成兩部分,將這兩部分分別排序,再將排序好的兩部分合併在一起   * 時間複雜度O(nlogn)   * 空間複雜度(n)   *   */  public class MergeSort {         public static void main(String[] args) {          int[] a= new int[]{1,2,4,3,45,34,23,6,7};          mergeSort(a, 9);          for(int i=0;i<a.length;i++){              System.out.print(a[i]+" ");          }      }      private static void mergeSort(int[] a,int n){          mergeIntaily(a, 0, n-1);      }         private static void mergeIntaily(int[] a,int p,int r){          if(p>=r) return;          int q = p+(r-p)/2;          mergeIntaily(a, p, q);          mergeIntaily(a, q+1, r);          merge(a, p, q, r);      }         private static void merge(int[] a,int p,int q,int r){          int i=p;          int j = q+1;          int k=0;          //申請一個數組,大小與a[p..r]相同          int[] tmp = new int[r-p+1];          //用兩個游標,一個i指向a[p..q]中的第一個元素,一個j指向a[q+1..r]的第一個元素,          //如果a[i]<=a[j],就將a[i]的值放進tmp數組中,i後移,否則將a[j]元素放進tmp數組中,j後移          while(i<=q&&j<=r){              if(a[i]<=a[j]){                  tmp[k++] = a[i++];              }else {                  tmp[k++] = a[j++];              }          }             //判斷那部分數組剩餘          int start = i;          int end = q;          if(j<=r){              start = j;              end = r;          }             //將剩餘數組放進tmp中          while(start<=end){              tmp[k++] = a[start++];          }             //在將tmp數組中的元素放回原數組中          for(i=0;i<=r-p;i++){              a[p+i] = tmp[i];          }      }  }

merge(A[p…r], A[p…q], A[q+1…r]) 這個函數的作用就是,將已經有序的 A[p…q] 和 A[q+1…r] 合併成一個有序的數組,並且放入 A[p…r]。 申請一個臨時數組 tmp,大小與 A[p…r] 相同。我們用兩個游標 i 和 j,分別指向 A[p…q] 和 A[q+1…r] 的第一個元素。比較這兩個元素 A[i] 和 A[j],如果 A[i]<=A[j],我們就把 A[i] 放入到臨時數組 tmp,並且 i 後移一位,否則將 A[j] 放入到數組 tmp,j 後移一位。 繼續上述比較過程,直到其中一個子數組中的所有數據都放入臨時數組中,再把另一個數組中的數據依次加入到臨時數組的末尾,這個時候,臨時數組中存儲的就是兩個子數組合併之後的結果了。最後再把臨時數組 tmp 中的數據拷貝到原數組 A[p…r] 中。

快速排序

快速排序簡介

要排序的數組下標從p到r的一組數據,選取從p到r之間的任意一個數據作為pivot(分區點),也是分治思想。 快速排序是原地排序,不穩定,最壞時間複雜度為O(n2),最好和平均時間複雜度都為O(nlogn),空間複雜度為O(1)。

程式碼實現

public class QuickSort {      public static void main(String[] args) {          // TODO Auto-generated method stub          int[] a= new int[]{1,2,4,3,45,34,23,6,7};          quickSort(a, 9);          for(int i=0;i&lt;a.length;i++){              System.out.print(a[i]+&quot; &quot;);          }      }         private static void quickSort(int[] a,int n){          quickSortIntaily(a, 0, n-1);      }         private static void quickSortIntaily(int[] a,int p,int r){          if(p&gt;=r) return; //遞歸終止條件             int q= partition(a, p, r);             quickSortIntaily(a, p, q-1);          quickSortIntaily(a, q+1, r);      }         //分區函數,隨機選擇一個元素作為pivot,一般選取從p到r的最後一個元素,然後對a[p..r]分區,返回pivot下標      private static int partition(int[] a,int p,int r){          int pivot = a[r];          int i=p;          for(int j=p;j&lt;r;++j){              if(a[j]&lt;pivot){                  int tmp = a[i];                  a[i] = a[j];                  a[j] = tmp;                  ++i;              }          }          int tmp = a[i];          a[i] = a[r];          a[r] = tmp;          return i;      }  }

遍歷 p 到 r 之間的數據,將小於 pivot 的放到左邊,將大於 pivot 的放到右邊,將 pivot 放到中間。經過這一步驟之後,數組 p 到 r 之間的數據就被分成了三個部分,前面 p 到 q-1 之間都是小於 pivot 的,中間是 pivot,後面的 q+1 到 r 之間是大於 pivot 的。

歸併與快排區別

  • 歸併排序的處理過程是由下到上的,先處理子問題,然後再合併。而快排處理過程是由上到下的,先分區,然後再處理子問題。歸併排序雖然是穩定的、時間複雜度為 O(nlogn) 的排序演算法,但是它是非原地排序演算法;
  • 歸併排序和快速排序是兩種稍微複雜的排序演算法,都是使用分治的思想,程式碼都通過遞歸來實現,過程相似;

Tags: