Java常見排序算法及對應的時間複雜度和空間複雜度

排序算法經過了很長時間的演變,產生了很多種不同的方法。對於初學者來說,對它們進行整理便於理解記憶顯得很重要。每種算法都有它特定的使用場合,很難通用。因此,我們很有必要對所有常見的排序算法進行歸納。

排序大的分類可以分爲兩種:內排序和外排序。在排序過程中,全部記錄存放在內存,則稱爲內排序,如果排序過程中需要使用外存,則稱爲外排序。下面講的排序都是屬於內排序。

內排序有可以分爲以下幾類:

  (1)、插入排序:直接插入排序、二分法插入排序、希爾排序。

  (2)、選擇排序:直接選擇排序、堆排序。

  (3)、交換排序:冒泡排序、快速排序。

  (4)、歸併排序

  (5)、基數排序

1.插入排序

1.1直接插入排序

直接插入排序(Straight Insertion Sort)是一種最簡單的排序方法,其基本操作是將一條記錄插入到已排好的有序表中,從而得到一個新的、記錄數量增1的有序表。

算法中引進的附加記錄arr[i]稱監視哨或哨兵(Sentinel)。

哨兵有兩個作用:

① 進入查找(插入位置)循環之前,它保存了arr[i]的副本,使不致於因記錄後移而丟失arr[i]的內容;

② 它的主要作用是:在查找循環中"監視"下標變量l是否越界。一旦越界(即l=0),因爲arr[0]可以和自己比較,循環判定條件不成立使得查找循環結束,從而避免了在該循環內的每一次均要檢測l是否越界(即省略了循環判定條件"l>=1")。

例:

原有序表:(9 15 23 28 37) 20

找插入位置 : (9 15 ^ 23 28 37) 20

新有序表: (9 15 20 23 28 37)

public void insertSort(int[] arr){
        //從第一個元素開始做爲插入元素,和前面排好序的元素比較找到插入位置
        for(int i=1; i<arr.length; i++){
            int temp = arr[i];
            int l = i-1;
            //在前面排序好的數組找到插入位置
            for(; l>=0; l--){
                if(arr[l]>temp){
                    arr[l+1] = arr[l]; //元素向後移動
                }else{
                    break;  //找到插入元素位置
                }
            }
            arr[l+1] = temp; //插入元素
        }

        System.out.println(arr);
    }

1.2二分法插入排序

二分法插入排序,簡稱二分排序,是在插入第i個元素時,對前面的0~i-1元素進行折半,先跟他們中間的那個元素比,如果小,則對前半再進行折半,否則對後半進行折半,直到left<right,然後再把第i個元素前1位與目標位置之間的所有元素後移,再把第i個元素放在目標位置上。

public void advanceInsertSortWithBinarySearch(int[] arr){
        for(int i=1; i<arr.length; i++){
            int temp = arr[i]; //要插入元素
            int left = 0;
            int right = i-1;
            int mid = -1;
            while (left<=right){
                mid = left + (right - left)/2; //中間位置
                //找到新的左側和右側座標
                if(arr[mid]>temp){
                    right = mid - 1;
                }else {
                    left = mid + 1;
                }
            }
            for(int l=i-1; l>=left; l--){    //大於temp元素向右移動一位
                arr[l+1] = arr[l];
            }
            arr[left] = temp;   //插入元素
        }
        System.out.println(arr);
    }

1.3希爾排序
希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L.Shell於1959年提出而得名。
希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至1時,整個文件恰被分成一組,算法便終止

給定實例的shell排序的排序過程
假設待排序文件有10個記錄,其關鍵字分別是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次爲:
5,2,1

public void xiersort(int[] arr){
        int gap = arr.length;
        while (true){
            gap = gap/2; //分組,間隔gap的元素
            for(int i=0; i<gap; i++){ //比較gap組數據
                for(int l=i+gap; l<arr.length; l=+gap){ //執行插入排序,間隔gap的元素
                    int temp = arr[l];
                    int k = l-gap;
                    while (k>=0 && temp>arr[k]){ //將元素向前移動
                        arr[k+gap] = arr[k];
                        k = k-gap;
                    }
                    arr[k+gap] = temp; //插入元素
                }
            }

            if(gap == 1) //當分組等於1,停止排序
                break;
        }

        System.out.println(arr);
    }

2.選擇排序

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

2.1直接選擇排序
選擇排序法的第一層循環從起始元素開始選到倒數第二個元素,主要是在每次進入的第二層循環之前,將外層循環的下標賦值給臨時變量,接下來的第二層循環中,如果發現有比這個最小位置處的元素更小的元素,則將那個更小的元素的下標賦給臨時變量,最後,在二層循環退出後,如果臨時變量改變,則說明,有比當前外層循環位置更小的元素,需要將這兩個元素交換.

 public void selectSort(int[] arr){
        for(int i=0; i<arr.length; i++){
            int min = i; //最小元素位置臨時變量(0-arr.length-1)
            for(int l=i+1; l<arr.length; l++){ //在未排序剩餘元素找到最小元素位置
                if(arr[min]>arr[l]){
                    min = l;    //找到最小元素位置
                }
            }
            if(min != i){   //與最小位置元素替換,繼續尋找第二,第三個位置最小元素;
                int value = arr[min];
                arr[min] = arr[i];
                arr[i] = value;
            }
        }

        System.out.println(arr);
    }

2.2堆排序

a.基本思想:

  堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

  堆的定義下:具有n個元素的序列 (h1,h2,…,hn),當且僅當滿足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)時稱之爲堆。在這裏只討論滿足前者條件的堆。由堆的定義可以看出,堆頂元素(即第一個元素)必爲最大項(大頂堆)。完全二叉樹可以很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。

  思想:初始時把要排序的數的序列看作是一棵順序存儲的二叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。然後將根節點與堆的最後一個節點交換。然後對前面(n-1)個數重新調整使之成爲堆。依此類推,直到只有兩個節點的堆,並對它們作交換,最後得到有n個節點的有序序列。從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。

b.實例

初始序列:46,79,56,38,40,84

建堆:

交換,從堆中踢出最大數

依次類推:最後堆中剩餘的最後兩個結點交換,踢出一個,排序完成。

   public void buildHeap(int[] arrays){
        int arrayLength = arrays.length;
        //循環建堆
        for(int i=0; i<arrayLength-1; i++){
            //建堆
            buildMaxHeap(arrays, arrayLength-i-1);
            //交換堆頂和最後一個元素
            swap(arrays, 0, arrayLength-i-1);
            System.out.println(Arrays.toString(arrays));
        }
    }

    //對data數組從0到lastIndex建立大頂堆
    public void buildMaxHeap(int[] data, int lastIndex){
        //從lastIndex處節點(最後一個節點)的父節點開始
        for(int i=(lastIndex-1)/2; i>=0; i--){
            //k保存正在判斷的節點
            int k=i;
            //如果當前k節點的子節點存在
            while (k*2+1<=lastIndex){
                //k節點的左子節點的索引
                int biggerIndex = 2*k + 1;
                //如果biggerIndex小於lastIndex,即biggerIndex+1代表右節點存在
                if(biggerIndex<lastIndex){
                    //若右節點的值較大
                    if(data[biggerIndex] < data[biggerIndex+1]){
                        //biggerIndex總是記錄較大子節點的索引
                        biggerIndex++;
                    }
                }
                //如果k節點值小於其較大的子節點值
                if(data[k] < data[biggerIndex]){
                    //交換他們
                    swap(data, k, biggerIndex);
                    // 將biggerIndex賦予k,開始while循環的下一次循環,重新保證k節點的值大於其左右子節點的值
                    k=biggerIndex;
                }else {
                    break;
                }
            }
        }

    }

    //交換兩個元素
    public void swap(int[] data, int i, int j){
        int temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

3.交換排序

3.1冒泡排序

基本思想:在要排序的一組數中,對當前還未排好序的範圍內的全部數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換;

   public void bubbleSort(int[] arrays){
        for(int i=0; i<arrays.length; i++){
            //執行冒泡排序,大的元素下沉,小的元素上浮
            for(int l=0; l<arrays.length - 1 - i; l++){
                //-i表示最大的元素已經下沉到底部
                if(arrays[l]>arrays[l+1]){
                    int temp = arrays[l+1];
                    arrays[l+1] = arrays[l];
                    arrays[l] = temp;
                }
            }
        }

        System.out.println(arrays);

    }

3.2快速排序

基本思想:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟掃描,將待排序列分成兩部分,一部分比基準元素小,一部分大於等於基準元素,此時基準元素在其排好序後的正確位置,然後再用同樣的方法遞歸地排序劃分的兩部分;

    public void quick(int[] arrays){
        if(arrays.length>0){
            quickSort(arrays, 0, arrays.length-1);
        }
        System.out.println(arrays);
    }

    public void quickSort(int[] arrays, int start, int end) {
        if(start<end){
            int mid = getMiddle(arrays, start, end);    //計算中間位置
            quickSort(arrays, start, mid-1);       //左側元素遞歸
            quickSort(arrays, mid+1, end);        //右側元素遞歸
        }
    }

    public int getMiddle(int[] arrays, int start, int end){
        //中間位置元素
        int temp = arrays[start];
        while (start<end){
            //右側開始判斷,中間位置元素小於等於右側,end--
            while (start<end && temp<=arrays[end]){
                end--;
            }
            arrays[start] = arrays[end];
            //左側開始判斷,中間位置元素大於等於左側元素,start++
            while (start<end  && temp>=arrays[start]){
                start++;
            }
            arrays[end] = arrays[start];
        }
        arrays[start] = temp;
        //返回中間元素位置
        return start;
    }

4.歸併排序

基本思想:歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。

public int[] sort(int[] nums, int low, int high){
        int mid = (low+high)/2;
        if(low < high){
            //左邊
            sort(nums, low, mid);
            //右邊
            sort(nums, mid+1, high);
            //左右歸併
            merge(nums, low, mid, high);
        }

        return nums;
    }

    public void merge(int[] nums, int low, int mid, int high){
        int[] temp = new int[high - low + 1];
        int i = low; //左指針
        int j = mid+1; //右指針
        int k = 0;

        //把較小的數先移到新數組
        while (i<=mid && j<=high){
            if(nums[i]<nums[j]){
                temp[k++] = nums[i++];
            }else{
                temp[k++] = nums[j++];
            }
        }
        //把左邊剩餘的數據移入數組
        while (i<=mid){
            temp[k++] = nums[i++];
        }

        //把右邊剩餘的數據移入數組
        while (j<=high){
            temp[k++] = nums[j++];
        }

        //把新數組中的數據覆蓋nums數組
        for(int k2=0; k2<temp.length; k2++){
            nums[k2+low] = temp[k2];
        }

    }

5.基數排序

基本思想:將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序列。

 public void sort(int[] array){
        //找到最大數,確定要排序幾趟
        int max = 0;
        for(int i=0; i<array.length; i++){
            if(max<array[i]){
                max = array[i];
            }
        }

        //判斷位數
        int times = 0;
        while (max>0){
            max = max/10;
            times++;
        }

        //建立十個隊列
        List<ArrayList> queue = new ArrayList<>();
        for(int i=0; i < 10; i++){
            ArrayList  queueone = new ArrayList();
            queue.add(queueone);
        }
        //進行times次分配和收集
        for(int i=0; i<times; i++){
            //分配
            for(int j=0; j<array.length; j++){
                int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
                ArrayList queuetwo = queue.get(x);
                queuetwo.add(array[j]);
                queue.set(x, queuetwo);
            }

            //收集
            int count = 0;
            for(int j=0; j<10; j++){
                while (queue.get(j).size()>0){
                    ArrayList<Integer> quequethree = queue.get(j);
                    array[count] = quequethree.get(0);
                    quequethree.remove(0);
                    count++;
                }
            }
        }
    }

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章