Java中的插入排序,希爾排序,選擇排序,堆排序,冒泡排序,快速排序,歸併排序

這裏的排序都以升序爲例

1.插入排序

public static void insertSort(int[] array) {
        for (int bound = 1;bound<array.length;bound++) {
            int tmp = array[bound];
            int cur = bound-1;
            for (;cur>=0;cur--) {
                if (array[cur]>tmp) {
                    array[cur+1] = array[cur];

                }else {
                    break;
                }
            }
            array[cur+1] = tmp;
        }
    }

從bound號下標元素開始,用一個值tmp記住該元素值,因爲自己不需要和自己比較,就從下標爲1開始,用bound元素和前面元素進行比較,發現比bound元素大的,就把該元素向後移一位,直到找到比bound元素小的,就把tmp插入到該元素的後面。這要前面就是排好序的區間,後面就是待排區間。

時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定

2.希爾排序

public static void shellSort(int[] array) {
        int gap = (array.length)/2;
        while (gap>1) {
            insertSortGap(array,gap);
            gap = gap/2;
        }
        insertSortGap(array,1);
}

public static void insertSortGap(int[] array,int gap) {
        for (int bound = gap;bound<array.length;bound++) {
            int tmp = array[bound];
            int cur = bound-gap;
            for (;cur>=0;cur-=gap) {
                if (array[cur]>tmp) {
                    array[cur+gap] = array[cur];
                }else {
                    break;
                }

            }
            array[cur+gap] = tmp;

        }
}

希爾排序是插入排序的優化,就是分組後然後分別對每個組進行插入排序。分組呢就是指定一個數,每隔這個數取一個元素,作爲一組,一般的,這個gap取length/2,length/4,length/8.。。。直到gap =1在處理一波。根據插入排序,每次都要從下標爲1的元素開始,所以每組的下標爲1的元素是原數組下標爲gap的元素,然後原本對於插入排序要後移一位的,這裏都要移gap位,其他基本都一樣。

時間複雜度:O(n^1.3)
空間複雜度:O(1)
穩定性:不穩定

3.選擇排序

public static void selectSort(int[] array) {
        for (int bound = 0; bound<array.length;bound++) {
            for (int cur = bound;cur<array.length;cur++) {
                if (array[cur]<array[bound]) {
                    swap(array,cur,bound);
                }
            }
        }
    }
 private static void swap(int[] array, int cur, int bound) {
        int tmp = array[cur];
        array[cur] = array[bound];
        array[bound] = tmp;
    }

選擇排序是從0號下標開始,每次都從後面的元素中選出一個比當前元素小的進行交換,這樣最小的就排再了最前面。前面是已排序區間,後面是待排序區間。

時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:不穩定

4.堆排序

   public static void heapSort(int[] array) {
        createHeap(array);
        int heapSize = array.length;
        for (int i = 0;i<array.length-1;i++) {
            swap(array,0,heapSize-1);
            heapSize--;
            shiftDown(array,heapSize,0);
        }
    }
    private static void createHeap(int[] array) {
        for (int i = (array.length-1-1)/2;i>=0;i--) {
            shiftDown(array,array.length,i);
        }
    }
private static void swap(int[] array, int cur, int bound) {
        int tmp = array[cur];
        array[cur] = array[bound];
        array[bound] = tmp;
    }
    private static void shiftDown(int[] array, int heapSize, int i) {
        int parent = i;
        int child = 2*parent+1;
        while (child<heapSize) {
            if (child+1<heapSize && array[child+1]>array[child]) {
                child = child+1;
            }
            if (array[child]>array[parent]) {
                swap(array,child,parent);
            }else {
                break;
            }
            parent = child;
            child = 2*parent+1;
        }

    }

先用向下調整構造大堆,用一個數表示當前堆得大小,然後進去循環,每次把堆頂元素和最後一個元素進行交換,然後把除最後一個元素外(就是對堆的大小進行減減操作)的堆進行向下調整使其重新變爲大堆,這裏只要循環length-1就行了,因爲當堆剩下最後一個元素時就不需要進行任何操作了。
關於建堆操作,就從最後一個非葉子節點開始向下調整就行了。向下調整,從當前指定下標元素開始,爲父節點,左孩子下標爲乘以2加一,循環條件爲左孩子小標小於當前堆的大小,如果右孩子也小於堆的大小並且比左孩子大,就把右孩子下標給左孩子,保證左孩子一定是最大的,然後和父節點比大小進行適當交換,如果符合大堆要求,直接break就行了,然後把孩子節點下標賦給父節點,新的孩子節點再再新的父節點基礎上乘以2加一,進行下次循環。

時間複雜度:O(nlogn)
空間複雜度:O(1)
穩定性:不穩定

5.冒泡排序

public static void bubbleSort(int[] array) {
        for (int bound = 0;bound<array.length;bound++) {
            for (int cur = array.length-1;cur>bound;cur--) {
                if (array[cur-1]>array[cur]) {
                    swap(array,cur-1,cur);
                }
            }
        }
    }

冒泡排序,從最後兩個元素開始,每次不符合升序,就交換兩個值,然後都往前移一位,再比較兩個值,這樣最小的就到最前面了。,注意cur的循環終止條件,是>,沒有等於號。這樣前面就是已排序區間,後面是待排序區間。

時間複雜度:O(n^2)
空間複雜度:O(1)
穩定性:穩定

6.快速排序

 public static void quickSort(int[] array) {
        quickSortHelper(array,0,array.length-1);
    }

    private static void quickSortHelper(int[] array, int left, int right) {
        if (left>=right) {
            return;
        }
        int index = partition(array,left,right);
        quickSortHelper(array,left,index-1);
        quickSortHelper(array,index+1,right);


    }

    private static int partition(int[] array, int left, int right) {
        int baseValue = array[right];
        int i = left;
        int j = right;
        while (i<j) {
            while (i<j && array[i]<=baseValue) {
                i++;
            }
            while (i<j&&array[j]>=baseValue) {
                j--;
            }
            if (i<j) {
                swap(array,i,j);
            }
        }
        swap(array,i,right);
        return i;
    }

快速排序的核心操作是:partition
先找一個基準值,一般是最後一個或者第一個,如果找中間的,需要交換到最前面或者最後面。
找最後一個爲基準值的話,先從第一個開始從左往右找比基準值大的元素,再從最後一個開始從右往左找比基準值小的元素,然後交換兩個值,重複上述操作,知道前面的指針和後面的指針重合,這個數一定是大於基準值的,和基準值進行交換,然後再對重合位置的左右兩邊遞歸進行上述操作,完成整個排序。需要注意的是,當你基準值找的是第一個時,就需要先從右往左找,再從左往右找。
我們可以用一個輔助方法,去完成快速排序,傳入他要排序的區間,如果區間只有一個或者沒有元素,就不用進行處理直接返回,然後進行partition 操作完成第一次排序,並返回新的基準值的位置,接下來對左右區間進行遞歸處理。
對於partition 方法,我們找最後一個爲基準值,然後用i,j分別指向第一個和最後一個元素,判斷i,j是否重合,不重合就進入循環,從前找比基準值大的,從後找比基準值小的,進行交換,出循環後,此時,i,j重合,直接和基準值進行交換,最後返回此時基準值的位置。

時間複雜度:最壞-》O(n^2)
平均-》O(nlogn)
空間複雜度:最壞-》O(n)
平均-》O(logn)
穩定性:不穩定

快速排序非遞歸:

public static void quickSortByLoop(int[] array) {
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        stack.push(array.length-1);
        while (!stack.empty()) {
            int right = stack.pop();
            int left = stack.pop();
            if (left >= right) {
                continue;
            }
            int index = partition(array,left,right);
            stack.push(index+1);
            stack.push(right);
            stack.push(left);

            stack.push(index-1);
        }
    }

    private static int partition(int[] array, int left, int right) {
        int baseValue = array[right];
        int i = left;
        int j = right;
        while (i < j) {
            while (i<j && array[i] <= baseValue) {
                i++;
            }
            while (i<j && array[j] >= baseValue) {
                j--;
            }
            if (i < j) {
                swap(array,i,j);
            }

        }
        swap(array,i,right);
        return i;
    }

    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

非遞歸也是參考遞歸代碼進行編寫的,依舊需要用到棧,先把需要進行排序的區間進行入棧,如果棧非空進入循環,先後進行兩次出棧,如果區間裏只有一個元素或者沒有,進去下次循環,否則就進行partition操作,再把兩邊的區間分別入棧。

7.歸併排序

 public static void mergeSort(int[] array) {
        mergeSortHelper(array,0,array.length);
    }

    private static void mergeSortHelper(int[] array, int left, int right) {
        if (right-left <= 1) { //前閉後開區間
            return;
        }
        int mid = (left+right)/2;
        mergeSortHelper(array,left,mid);
        mergeSortHelper(array,mid,right);
        merge(array,left,mid,right);
    }

    private static void merge(int[] array, int left, int mid, int right) {
        int cur1 = left;
        int cur2 = mid;
        int[] output = new int[right-left];
        int outputIndex = 0;
        while (cur1 < mid && cur2 < right) {
            if (array[cur1] <= array[cur2]) { //  <=保證穩定性
                output[outputIndex] = array[cur1];
                cur1++;
                outputIndex++;
            }else {
                output[outputIndex] = array[cur2];
                cur2++;
                outputIndex++;
            }
        }
        while (cur1<mid) {
            output[outputIndex] = array[cur1];
            cur1++;
            outputIndex++;

        }
        while (cur2<right) {
            output[outputIndex] = array[cur2];
            cur2++;
            outputIndex++;
        }
        for (int i = 0;i<right-left;i++) {
            array[left+i] = output[i];
        }
    }

歸併排序,任然需要一個輔助方法去完成排序,傳入要排序的區間的參數,此處的區間是前閉後開的,所以判斷是否只有一個元素或者沒有元素的時候需要right-left<=1去判斷,然後依靠中間下標分爲兩組,在進行後序遍歷遞歸處理,最後的“訪問節點操作”就是排序操作再用一個方法去進行實現。
需要傳入前中後三個指針參數,left和mid就是分成的兩組的首元素,歸併需要額外的空間去進行存儲,所以需要一個可以足夠存儲傳進來數組的空間,創建一個right-left大小的數組,用一個變量表示當前數組裏有幾個元素,當兩個數組都不爲空時,判斷left和mid指向的當前元素的大小,把小的傳進新建的數組,然後指針向後移動一位,進行比較,最後出循環判斷兩個數組的其中一個是否還有元素剩餘,把剩餘的全部加入新建數組裏,最後還需要注意把新建數組裏的元素全部按順序再傳回到原數組裏。這樣整個代碼就完成了。

時間複雜度:O(nlogn)
空間複雜度:O(n)
穩定性:穩定

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