九大排序算法Java實現

之前學習數據結構與算法時花了三天時間整理九大排序算法,並採用Java語言來實現,今天第一次寫博客,剛好可以把這些東西從總結的文檔中拿出來與大家分享一下,同時作爲自己以後的備忘錄。   

1.排序算法時間複雜度、穩定性分類:

2.排序算法問題描述與實現

2.1冒泡排序(交換排序-穩定)

【問題描述】對於一個int數組,請編寫一個冒泡排序算法,對數組元素排序。 
問題分析:冒泡排序,顧名思義,從前往後遍歷,每次遍歷在末尾固定一個最大值。

易錯點:每次內層循環結束都會在末尾確定一個元素的位置,因此內層循環的判斷條件要減去外層的循環次數i

public int[] BobbleSort(int[] array){ 

         for(int i=0;i<array.length-1;i++){  

            for(int j=0;j<array.length-1-i;j++){  //內層循環,注意array.length-1-i,一定要-i

                swap(array,j,j+1);//交換數據元素的方法

            }

         }

         return array;

      }

      public void swap(int[] array,int i,int j){//交換數據元素的方法

         if(array[i]>array[j]){

            int temp=array[i];

             array[i]=array[j];

             array[j]=temp;

         }

      }

冒泡排序的改進:加一個布爾型的flag,當某一趟冒泡排序沒有元素交換時,則冒泡結束,元素已經有序,可以有效的減少冒泡次數。

 

2.2 選擇排序(不穩定)

【問題描述】對於一個int數組,請編寫一個選擇排序算法,對數組元素排序。  
問題分析:選擇排序,從前往後遍歷,每次遍歷從頭開始依次選擇最小的來排序。

注意點:

【初始升序】:交換0次,時間複雜度爲o(n); 【初始降序】:交換n-1次,時間複雜度爲o(n^2)。

【特點】:交換移動數據次數少,比較次數多。

   public int[] choose(int[] array){

      for(int i=0;i<array.length-1;i++){

         int min=i;

         for (int j = i+1; j < array.length; j++) {

            if(array[min]>array[j])

                min=j;

         }

         if(i!=min){

            swap(array,i,min);

         }

      }

      return array;

   }

  public void swap(int[] array,int i,int j){

      if(array[i]>array[j]){

         int temp=array[i];

          array[i]=array[j];

          array[j]=temp;

      }

   }

 

2.3 直接插入排序(穩定的 )

【問題描述】對於一個int數組,請編寫一個插入排序算法,對數組元素排序。  
問題分析:插入排序,從前往後遍歷,每次遍歷確定一個元素的位置,下一個元素從後往前依次與排序好的元素比較,類似於摸撲克牌。

注意點:假定n是數組的長度,首先假設第一個元素被放置在正確的位置上,這樣僅需從1-n-1範圍內對剩餘元素進行排序。對於每次遍歷,從0-i-1範圍內21.的元素已經被排好序,每次遍歷的任務是:通過掃描前面已排序的子列表,將位置i處的元素定位到從0到i的子列表之內的正確的位置上。將arr[i]複製爲一個名爲target的臨時元素。向下掃描列表,比較這個目標值target與arr[i-1]、arr[i-2]的大小,依次類推。這個比較過程在小於或等於目標值的第一個元素(arr[j])處停止,或者在列表開始處停止(j=0)。在arr[i]小於前面任何已排序元素時,後一個條件(j=0)爲真,因此,這個元素會佔用新排序子列表的第一個位置。在掃描期間,大於目標值target的每個元素都會向右滑動一個位置(arr[j]=arr[j-1])。一旦確定了正確位置j,目標值target(即原始的arr[i])就會被複制到這個位置。與選擇排序不同的是,插入排序將數據向右滑動,並且不會執行交換。

   public int[] insert1(int[] array){

      for(int i=1;i<array.length;i++){

         int j=i;

         int temp=array[i];

         while(j>0&&array[j-1]>temp){

            array[j]=array[j-1];//元素右移

            j--;

         }

         array[j]=temp;

      }

      return array;

   }

 

2.4 希爾排序(2for循環+最後一個while,不穩定

問題描述:對於一個int數組,請編寫一個希爾排序算法,對數組元素排序。  
問題分析:希爾排序是改進的插入排序,算法先將要排序的一組數按某個增量d(n/2,n爲要排序數的個數)分成若干組,每組中記錄的下標相差d.對每組中全部元素進行直接插入排序,然後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。當增量減到1時,進行直接插入排序後,排序完成。希爾排序法(縮小增量法) 屬於插入類排序,是將整個無序列分割成若干小的子序列分別進行插入排序的方法。

  • public int[] hillInsert(int[] array){
  •     for (int d = array.length>>1; d>=1; d=d>>1) {
  •        for(int i=d;i<array.length;i++){
  •           int j=i;
  •           int temp=array[i];
  •           while(j>=d&&array[j-d]>temp){
  •              array[j]=array[j-d];
  •              j-=d;
  •           }
  •           array[j]=temp;
  •        }
  •     }
  •     return array;
  • }

2.5.堆排序(不穩定)

 問題描述:對於一個int數組,請編寫一個堆排序算法,對數組元素排序。  
問題分析:
堆數據結構是一種數組對象,它可以被視爲一科完全二叉樹結構(完全二叉樹是由滿二叉樹而引出來的。對於深度爲K的,有n個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹)。它的特點是父節點的值大於(小於)兩個子節點的值(分別稱爲大頂堆和小頂堆)。它常用於管理算法執行過程中的信息,應用場景包括堆排序優先隊列等。

完全二叉樹性質:如果i>1,則雙親是結點[i/2]。也就是說下標i與2i和2i+1是雙親子女關係。 當排序對象爲數組時,下標從0開始,所以下標 i 與下標 2i+1和2i+2是雙親子女關係。

算法思路:

  • 堆排序:(大根堆)
  • ①將存放在array[0,...,n-1]中的n個元素建成初始堆;
  • ②將堆頂元素與堆底元素進行交換,則序列的最大值即已放到正確的位置;
  • ③但此時堆被破壞,將堆頂元素向下調整使其繼續保持大根堆的性質,再重複第②③步,直到堆中僅剩下一個元素爲止。
  • 堆排序算法的性能分析:
  • 空間複雜度:o(1);
  • 時間複雜度:建堆:o(n),每次調整o(log n),故最好、最壞、平均情況下:o(n*logn);
  • 穩定性:不穩定
  • public static int[] heapMax(int[] array){  //建立大根堆
  •     for(int i=(array.length-2)/2;i>=0;i--){
  •        buildHeap(array,i,array.length);
  •     }
  •     return array;
  • }
  • private static void buildHeap(int[] array, int k, int length) {  //建堆方法
  •     // TODO Auto-generated method stub
  •     int temp=array[k];
  •     for(int i=2*k+1;i<length-1;i=2*i+1){
  •        if(array[i]<array[i+1]){
  •           i++;
  •        }
  •        if(temp>=array[i])
  •           break;
  •        else{
  •           array[k]=array[i];
  •           k=i;
  •        }
  •     } 
  •        array[k]=temp;
  • }
  • public static int[] heapSortArray(int[] array){ //進行堆排序
  •      array=heapMax(array);  
  •     for (int i =array.length-1; i >0; i--) {
  •        int temp=array[0];   
  •        array[0]=array[i];
  •        array[i]=temp;
  •        buildHeap(array,0,i);
  •     }
  •     return array;
  • }

 

2.6 歸併排序(穩定)

問題描述:對於一個int數組,請編寫一個歸併排序算法,對數組元素排序。 
問題分析:歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。首先考慮下如何將將二個有序數列合併。這個非常簡單,只要從比較二個數列的第一個數,誰小就先取誰,取了後就在對應數列中刪除這個數。然後再進行比較,如果有數列爲空,那直接將另一個數列的數據依次取出即可。可以看出合併有序數列的效率是比較高的,可以達到O(n)。

解決了上面的合併有序數列問題,再來看歸併排序,其的基本思路就是將數組分成二組A,B,如果這二組組內的數據都是有序的,那麼就可以很方便的將這二組數據進行排序。如何讓這二組組內數據有序了?

可以將A,B組各自再分成二組。依次類推,當分出來的小組只有一個數據時,可以認爲這個小組組內已經達到了有序,然後再合併相鄰的二個小組就可以了。這樣通過先遞歸的分解數列,再合併數列就完成了歸併排序。

   public int[] mergeSort(int[] A, int n) {

          //歸併排序,遞歸做法,分而治之

           mSort(A,0,n-1);

           return A;

       }

 

       private void mSort(int[] A,int left,int right){

    //分而治之,遞歸常用的思想,跳出遞歸的條件

    if(left>=right){

        return;

    }

    //中點

    int mid = (left+right)/2;

    //有點類似後序遍歷!

    mSort(A,left,mid);

    mSort(A,mid+1,right);

    merge(A,left,mid,right);

}

//將左右倆組的按序子序列排列成按序序列

private void merge(int[] A,int left,int mid,int rightEnd){

    //充當tem數組的下標

    int record = left;

    //最後複製數組時使用

    int record2 = left;

    //右子序列的開始下標

    int m =mid+1;

    int[] tem = new int[A.length];

    //只要left>mid或是m>rightEnd,就跳出循環

    while(left<=mid&&m<=rightEnd){

        if(A[left]<=A[m]){

            tem[record++]=A[left++];

        }else{

            tem[record++]=A[m++];

        }

    }

    while(left<=mid){

        tem[record++]=A[left++];

    }

    while(m<=rightEnd){

        tem[record++]=A[m++];

    }

    //複製數組

    for( ;record2<=rightEnd;record2++){

        A[record2] = tem[record2];

    }

}

 

2.7 快速排序算法(不穩定)

問題描述:對於一個int數組,請編寫一個快速排序算法,對數組元素排序。 
問題分析:快速排序(Quicksort)是對冒泡排序的一種改進,使用分治法(Divide and conquer)策略來把一個序列(list)分爲兩個子序列(sub-lists)。

從數列中挑出一個元素,稱爲」樞軸」(pivot)。重新排序數列,所有元素比樞軸值小的擺放在基準前面,所有元素比樞軸值大的擺在樞軸的後面(相同的數可以到任一邊)。

在這個分區結束之後,該樞軸就處於數列的中間位置。這個稱爲分區(partition)操作。遞歸地(recursive)把小於樞軸值元素的子數列和大於樞軸值元素的子數列排序。

  • public static final void quickSort(int[] array, intstart, intend) {
  •        // i相當於助手1的位置,j相當於助手2的位置
  •        int i = start, j = end;
  •        int pivot = array[i]; // 取第1個元素爲基準元素
  •        int emptyIndex = i; // 表示空位的位置索引,默認爲被取出的基準元素的位置
  •        // 如果需要排序的元素個數不止1個,就進入快速排序(只要i和j不同,就表明至少有2個數組元素需要排序)
  •        while (i < j) {
  •          // 助手2開始從右向左一個個地查找小於基準元素的元素
  •          while (i < j && pivot <= array[j])
  •            j--;
  •          if (i < j) {
  •            // 如果助手2在遇到助手1之前就找到了對應的元素,就將該元素給助手1的"空位",j成了空位
  •            array[emptyIndex] = array[emptyIndex = j];
  •          }
  •           
  •          // 助手1開始從左向右一個個地查找大於基準元素的元素
  •          while (i < j && array[i] <= pivot)
  •            i++;
  •          if (i < j) {
  •            // 如果助手1在遇到助手2之前就找到了對應的元素,就將該元素給助手2的"空位",i成了空位
  •            array[emptyIndex] = array[emptyIndex = i];
  •          }
  •        }   
  •        // 助手1和助手2相遇後會停止循環,將最初取出的基準值給最後的空位
  •        array[emptyIndex] = pivot;
  •         
  •        // =====本輪快速排序完成=====
  •        // 如果分割點i左側有2個以上的元素,遞歸調用繼續對其進行快速排序
  •        if (i - start > 1) {
  •          quickSort(array, 0, i - 1);
  •        }
  •        // 如果分割點j右側有2個以上的元素,遞歸調用繼續對其進行快速排序
  •        if (end - j > 1) {
  •          quickSort(array, j + 1, end);
  •        }
  •      }

 

算法優化:選取基準軸點時採用三數取中法:

public class Quick {

   public void sort(int[] array){

      int start=0;

      int end=array.length-1;

      quickSort(array,start,end);

   }

   public void quickSort(int[] array, int start, int end) {

      // TODO Auto-generated method stub

      int i=start;

      int j=end;

      int emptyIndex=start;

      int pivot=middle3(array,start,end);

      while(i<j){

         while(i<j&&array[j]>=pivot){

            j--;

         }

         if(i<j)

            array[emptyIndex]=array[emptyIndex=j];

         while(i<j&&array[i]<=pivot){

            i++;

         }

         if(i<j)

            array[emptyIndex]=array[emptyIndex=i];

      }

      array[emptyIndex]=pivot;

     

      if(i-start>1)

         quickSort(array,start,i-1);

      if(end-j>1)

         quickSort(array,j+1,end);

   }

 

2.8 計數排序算法(穩定,也是桶排序)

問題描述:對於一個int數組,請編寫一個計數排序算法,對數組元素排序。 
問題分析:

1. 提前必須是已知待排序的關鍵字爲整型且範圍已知。 
2. 時間複雜度爲O(n+k),n指的是桶的個數,k指的是待排序數組的長度,不是基於比較的排序算法,因此效率非常之高。 
3. 穩定性好,這個是計數排序非常重要的特性,可以用在後面介紹的基數排序中。 
4. 但需要一些輔助數組,如C[0..k],因此待排序的關鍵字範圍0~k不宜過大。

    public int[] countingSort(int[] A, int n) {

        if(A==null ||n<2){

            return A;

        }

        //找出桶的範圍,即通過要排序的數組的最大最小值來確定桶範圍

        int min=A[0];

        int max=A[0];

        for(int i=0;i<n;i++){

            min=Math.min(A[i],min);

            max=Math.max(A[i],max);

        }

        //確定桶數組,桶的下標即爲需排序數組的值,桶的值爲序排序數同一組值出現的次數

        int[] arr = new int[max-min+1];

        //往桶裏分配元素

        for(int i=0;i<n;i++){

            arr[A[i]-min]++;

        }

        //從桶中取出元素

        int index=0;

        for(int i=0;i<arr.length;i++){

            while(arr[i]-->0){

                A[index++]=i+min;

            }

        }

        return A;

    }

}

 

2.9 基數排序算法(穩定)

問題描述:對於一個int數組,請編寫一個基數排序算法,對數組元素排序。 
問題分析:

基數排序(Radix sort)是一種非比較型整數排序算法,其原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字符串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。

鍵字範圍0~k不宜過大。

public void sort(int[] array){

      int max=0;

      int d=0;// 位數

      for (int i = 0; i < array.length; i++) {

         max=Math.max(max, array[i]);

      }

      while(max>0){

         max/=10;

         d++;

      }

      int t=0;

      int m=1;

      int n=1;

      int[][] temp=new int[10][array.length];

      int[] order=new int[10];

      while(m<=d){

         for (int i = 0; i < array.length; i++) {

                int k=((array[i]/n)%10);

                temp[k][order[k]++]=array[i];

            }

        

         for (int i = 0; i < 10; i++) {

            if(order[i]!=0){

                for (int j = 0; j <order[i]; j++) {

                   array[t++]=temp[i][j];

                }

                order[i]=0;

            }

         }

         t=0;

         n=n*10;

         m++;

      }

   }

採用鏈表的方式來實現基數排序: 

//基數排序開始
public static void radixSort(int[] array){
int max=array[0];
for (int i = 0; i < array.length; i++) {
max=Math.max(max, array[i]);
}
int time=0;
while(max!=0){
time++;
max=max/10;
}
List<List<Integer>> list=new ArrayList<List<Integer>>();
for(int i=0;i<10;i++){
List<Integer> item=new ArrayList<Integer>();
list.add(item);
}

for (int i = 0; i < time; i++) {
for (int j = 0; j < array.length; j++) {
int index=array[j]%(int)Math.pow(10, i+1);
index/=(int)Math.pow(10, i);
list.get(index).add(array[j]);
}
int count=0;
for (List<Integer> a : list) {
for (int m : a) {
if(m!=0){
array[count++]=m;
}
}
a.clear();
}
}
}

//基數排序結束