1. 程式人生 > 實用技巧 >Java零基礎的開始:八大排序(面試答題!)

Java零基礎的開始:八大排序(面試答題!)

八大排序(重點!!)

​排序演算法大體可分為兩種:
1、比較排序,時間複雜度O(nlogn) ~ O(n^2),主要有:氣泡排序,選擇排序,插入排序,歸併排序,堆排序,快速排序等。
2、非比較排序,時間複雜度可以達到O(n),主要有:計數排序,基數排序,桶排序等。

一、氣泡排序

演算法思路

1、比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
2、對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最後一對,這樣在最後的元素應該會是最大的數;
3、針對所有的元素重複以上的步驟,除了最後一個;
4、重複步驟1~3,直到排序完成。 :

``java

/**
 * 氣泡排序的規則
 * 1、從第一個數開始,將這個數 與它相鄰的數比較,如果這個數大於相鄰的數
 * 則兩個數交換位置
 * 2、依次從第二個數開始比較,與相鄰的數比較
 * 3、以此類推  到倒數第二數截至
 * 4、重複以上步驟
 */
public static void main(String[] args) {
    int[] array = {5, 4, 3, 2, 1};
    //用於臨時交換的變數
    int temp = 0;
    for (int j = 0; j < array.length - 1; j++) {
        for (int i = 0; i < array.length - j - 1; i++) {
            //相鄰的數比較
            if (array[i] > array[i + 1]) {
                temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }
        System.out.println("比較一輪之後:" + Arrays.toString(array));
    }
}

二、選擇排序

演算法思路:

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。

 /**
     *  選擇排序: 從一堆數中選擇一個最小數 放在第一個位置,再從一堆數中選擇一個
     *          最小數放在第二個位置, 依次 將一堆數的最小數按順序排放。
     *    步驟: 1、假設第一個數是最小數,需要定義最小數的下標minIndex=0
     *             將這個數與後面的每一個數比較,找到最小數的下標即可
     *          2、將第一個數與最小數的下標交換 ,得出最小數在第一位。
     *          3、 依次類推, 將已比較的數 忽略,繼續從生下的元素中找足最小數,放入已比較的數的下一位
     *              直到整個數列比較結束
     * @param args
     */
    public static void main(String[] args) {
            int [] array = {3,2,1,5,7,4};
            for(int j=0;j<array.length-1;j++) {
                // 假設第一個數是最小數
                int minIndex = j;
                // 為什麼i =j+1  因為初始值要略過已比較的下標
                for (int i = 1+j; i < array.length; i++) {
                    if (array[minIndex] > array[i]) {
                        minIndex = i;
                    }
                }
                
                // 將這個最小數放在 第一位
                int temp = 0;
                temp = array[j];
                array[j] = array[minIndex];
                array[minIndex] = temp;

                System.out.println("----第一次完成後:" + Arrays.toString(array));
            }

        System.out.println("最後的排序:"+Arrays.toString(array));
    }
}

三、插入排序

演算法思路:

1、從第一個元素開始,該元素可以認為已經被排序;
2、取出下一個元素,在已經排序的元素序列中從後向前掃描;
3、如果該元素(已排序)大於新元素,將該元素移到下一位置;
4、重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
5、將新元素插入到該位置後;
6、重複步驟2~5。

/**
     * 插入排序
     *    1、從第一個元素開始,假設第一個元素是已排好序的
     *    2、從下一個元素開始,依次比較它前面的所有元素(從後向前掃描)
     *    3、 如果這個元素 小於它前面的元素 則兩兩交換 ,
     *         如果這個元素 大於它前面的元素,則不交換
     *    4、依次重複2,3步驟  ,直到將所有數 比較完成
     *   5,4,3,2,1
     *
     *   4 5 3  2  1   i從1開始
     *
     *   4 3  5 2  1    i從2開始
     *   3 4  5 2 1
     *
     *   3 4 2 5 1    i從3開始
     *   3 2 4 5 1
     *   2 3 4 5 1
     *
     *   2 3 4 1 5   i從4開始
     *   2 3 1 4 5
     *   2 1 3 4 5
     *   1 2 3 4 5
     * @param args
     */
    public static void main(String[] args) {
        int [] array = {5,4,3,2,1};
        // 外層迴圈迴圈  每一個數的比較次數
        for(int j=0;j<array.length-1;j++) {
            int temp = 0;
            for (int i = 1+j; i > 0; i--) {
                if (array[i] < array[i - 1]) {
                    temp = array[i];
                    array[i] = array[i - 1];
                    array[i - 1] = temp;
                }
            }
            System.out.println("每一次完成後的結果:"+ Arrays.toString(array));
        }
        System.out.println("最後一次完成後的結果:"+Arrays.toString(array));

    }

四、歸併排序

演算法思路:
該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。
1、把長度為n的輸入序列分成兩個長度為n/2的子序列;
2、對這兩個子序列分別採用歸併排序;
3、將兩個排序好的子序列合併成一個最終的排序序列。

/*
    k表示最終i和j比較之後最終需要放的位置
    i和j用來表示當前需要考慮的元素
    left表示最左邊的元素
    right表示最右邊的元素
    middle表示中間位置元素,放在第一個已經排好序的陣列的最後一個位置
*/
public class Merging {
    /*******************測試************************/
    public static void main(String[] args) {
          int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 , 9 , 19 ,12,16,14,12,22,33 };

          mergeSort(nums , 0 , nums.length - 1 );
          System.out.println(Arrays.toString(nums));
    }
    /********************演算法************************/
    /*
        arr:要處理的陣列
        l:開始位置
        r:結束位置
        遞迴對arr[ l ... r ]範圍的元素進行排序
     */
    private static void mergeSort(int[] arr,int left,int right){
        if( right - left <= 10 ){   //當資料很少的時候使用插入排序演算法
            ChaRuPaiXu.ChaRuPaiXuFa2( arr , left ,right);
            return;
        }
        int middle = ( left + right ) / 2;  //計算中點位置
        mergeSort( arr , left , middle );   //不斷地對陣列的左半邊進行對邊分
        mergeSort( arr , middle+1 , right );   //不斷地對陣列的右半邊進行對半分
        if( arr[middle] > arr[middle+1] )//當左邊最大的元素都比右邊最小的元素還小的時候就不用歸併了
            merge( arr , left , middle , right );  //最後將已經分好的陣列進行歸併
    }
    //將arr[ l... mid ]和arr[ mid ... r ]兩部分進行歸併
    /*
        |2, 7, 8, 3, 1  |  6, 9, 0, 5, 4|
     */
    private static void merge(int[] arr, int left, int mid, int right) {
        int arr1[] = new int[ right - left + 1 ];   //定義臨時陣列
        for( int i = left ; i <= right ; i++ )    //將陣列的元素全部複製到新建的臨時陣列中
            arr1[ i - left ] = arr[ i ];
        int i = left;
        int j = mid + 1;     //定義兩個索引
        for( int k = left;k <= right ; k++){
             if( i > mid )   //如果左邊都比較完了
             {
                 arr[ k ] = arr1[ j - left ];   //直接將右邊的元素都放進去
                 j++;
             }
             else if( j > right ){   //右邊都比較完了
                 arr[ k ] = arr1 [i - left ];   //直接將左邊的元素放進去
                 i++;
             }
             else if( arr1[ i-left ] < arr1[ j-left ] ){
                 arr[ k ] = arr1[ i - left];
                 i++;
             }
            else
             {
                 arr[ k ] = arr1[ j - left];
                 j++;
             }
        }
    }
}

五、快速排序

演算法思路:
通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
快速排序使用分治法來把一個串(list)分為兩個子串(sub-lists)。
1、從數列中挑出一個元素,稱為 “基準”(pivot);
2、重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽退出之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作;
3、遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。

public class Quick {
    public static void main(String[] args){
        int array[] = {1,2,4,3,9,7,8,6};

        quickSort(array,0,array.length-1);
        for( int i = 0 ; i < array.length ; i++ ){
            System.out.print(array[i]+" ");
        }
    }
    private static void quickSort(int[] arr,int l,int r){
        if( l >= r )  return;
        int p = partition(arr,l,r);    //找到中間位置
        quickSort(arr,l,p-1);
        quickSort(arr,p+1,r);
    }
    private static int partition(int[] arr,int l,int r){
        int v = arr[l];   //取出第一個元素
        int j = l;        //j表示小於第一個元素和大於第一個元素的分界點
        for( int i = l + 1;i <= r;i++ ){
            //將所有小於第一個元素的值的元素全部都放到它的左邊
            if( arr[i] < v ){     //如果當前元素小於v,則交換
                swap(arr,i,j+1);
                j++;
            }
        }
        swap(arr,l,j);  //將第一個元素和中間的元素進行交換
        return j;
    }
}
 public static void quickSort(int[] arr,int low,int high){
        int i,j,temp,t;
        if(low>high){
            return;
        }
        i=low;
        j=high;
        //temp就是基準位
        temp = arr[low];
 
        while (i<j) {
            //先看右邊,依次往左遞減
            while (temp<=arr[j]&&i<j) {
                j--;
            }
            //再看左邊,依次往右遞增
            while (temp>=arr[i]&&i<j) {
                i++;
            }
            //如果滿足條件則交換
            if (i<j) {
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }
 
        }
        //最後將基準為與i和j相等位置的數字交換
         arr[low] = arr[i];
         arr[i] = temp;
        //遞迴呼叫左半陣列
        quickSort(arr, low, j-1);
        //遞迴呼叫右半陣列
        quickSort(arr, j+1, high);
    }
 
 
    public static void main(String[] args){
        int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
        quickSort(arr, 0, arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

六、堆排序

演算法思路:
堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。

最大堆要求節點的元素都要不小於其孩子,最小堆要求節點元素都不大於其左右孩子
那麼處於最大堆的根節點的元素一定是這個堆中的最大值.

public class Heap {
    public static void main(String[] args) {
        int A[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
        HeapSort(A, A.length);
        System.out.println(Arrays.toString(A));
    }
public static void Swap(int A[], int i, int j)
{
    int temp = A[i];
    A[i] = A[j];
    A[j] = temp;
}
public static void Heapify(int A[], int i, int size)  // 從A[i]向下進行堆調整
{
    int left_child = 2 * i + 1;         // 左孩子索引
    int right_child = 2 * i + 2;        // 右孩子索引
    int max = i;                        // 選出當前結點與其左右孩子三者之中的最大值
    if (left_child < size && A[left_child] > A[max])
        max = left_child;
    if (right_child < size && A[right_child] > A[max])
        max = right_child;
    if (max != i)
    {
        Swap(A, i, max);                // 把當前結點和它的最大(直接)子節點進行交換
        Heapify(A, max, size);          // 遞迴呼叫,繼續從當前結點向下進行堆調整
    }
}
public static int BuildHeap(int A[], int n)           // 建堆,時間複雜度O(n)
{
    int heap_size = n;
    for (int i = heap_size / 2 - 1; i >= 0; i--) // 從每一個非葉結點開始向下進行堆調整
        Heapify(A, i, heap_size);
    return heap_size;
}
public static void HeapSort(int A[], int n)
{
    int heap_size = BuildHeap(A, n);    // 建立一個最大堆
    while (heap_size > 1)   // 堆(無序區)元素個數大於1,未完成排序
    {
        // 將堆頂元素與堆的最後一個元素互換,並從堆中去掉最後一個元素
        // 此處交換操作很有可能把後面元素的穩定性打亂,所以堆排序是不穩定的排序演算法
        Swap(A, 0, --heap_size);
        Heapify(A, 0, heap_size);     // 從新的堆頂元素開始向下進行堆調整,時間複雜度O(logn)
    }
}
}

七、希爾排序

演算法思路:
1、選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2、按增量序列個數k,對序列進行k 趟排序;
3、每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度

常用的h序列由Knuth提出,該序列從1開始,通過如下公式產生:
h = 3 * h +1
反過來程式需要反向計算h序列,應該使用
h = ( h - 1 ) / 3

public class Shell {
    public static void main(String[] args) {
        int array[] = {1,2,4,3,9,7,8,6};
        int h = 0;
        int length = array.length;
        while( h <= length ){    //計算首次步長
            h = 3 * h + 1;
        }
        while( h >= 1 ){
            for( int i = h;i < length;i++ ){
                int j = i - h;         //左邊的一個元素
                int get = array[i];    //當前元素
                while( j >= 0 && array[j] > get ){   //左邊的比當前大,則左邊的往右邊挪動
                    array[j+h] = array[j];
                    j = j - h;
                }
                array[j + h] = get;  //挪動完了之後把當前元素放進去
            }
            h = ( h - 1 ) / 3;  
        }
        for( int i = 0 ; i < array.length ; i++ ){
            System.out.print(array[i]+" ");
        }
    }
}

猜猜最後一個排序是什麼?到對有獎!!!