經常使用排序算法簡要分析

選擇排序

給定一個規模爲n的數組,在n個數字中尋找最小的一個,而後與第0位置的數交換,而後在後n-1個數中找最小的,與第1個位置的數字交換。當交換了i次之後,數組左邊的i個數字是有序的,右邊n-i個數字無序。而後把在n-i個數字中尋找最小的,與i位置的數字交換位置。直到交換n次之後,整個數組有序。算法

時間複雜度:O(n²)數組

實現:markdown

public class Selection{
    public static void sort(int[] a) {
        int t;
        for(int i=0;i<a.length;i++){
            int index=i;//用來保存剩餘亂序數組中的最小值的位置
            for(int j=i+1;j<a.length;j++){
                if(a[index]>a[j]){//尋找最小值並保存位置
                    index=j;
                }
            }
            //找到最小值的位置後交換
            t=a[i];
            a[i]=a[index];
            a[index]=t;
        }
    }

}

插入排序

給定一個規模爲n的數組,把第i號(iui

public class Insertion{
    public static void sort(int[] a) {
        int t=0;
        for(int i=1;i<a.length;i++){
            int v=a[i],k;
            for(k=i-1;k>=0&&v<a[k];k--){//查找左邊比a[i]的元素大的元素,右移。
                a[k+1]=a[k];
            }
            a[k+1]=v;//把a[i]放入比a[i]小的元素的後面
        }
    }
}

希爾排序

根據插入排序,若是n個元素中最小的數在最右端,那麼該數字須要比較n-1次才能夠到達目標位置,由於是逐個比較元素的大小的。若是把逐個比較改成相隔1個比較並移動、相隔1個比較並移動、相隔h個比較並移動,那麼最右端的元素到目標位置的比較移動次數將會大幅度減少。因此,在每隔h個元素都比較移動完之後,n個數中將會出現n/h個有序對,他們彼此相隔h,而後把h–,而後再比較移動,直到h==1且比較移動完後,排序結束。插入排序就是h=1的希爾排序。
時間複雜度:未知
適用範圍:數據規模較大,數據較離散。
實現:spa

public class Shell {
    public static void sort(int[] a){
        int h=1,t=0;
        h=a.length/2;
        while(h>=1){//直到相隔距離爲1時,最後一次比較移動
            for(int i=h-1,j;i<a.length;i++){//從i位置開始,保證以前每隔h位置的元素都是有序的,一開始從h-1開始,左邊只有h個元素,因此每一組都是有序的
                int base=a[i];//要插入的元素
                for(j=i-h;j>=0&&a[j]>base;j-=h){//把要插入的元素與該組的其餘元素比較大小,若是被比較的元素較大,則右移h個位置
                        a[j+h]=a[j];
                }
                a[j+h]=base;//把要插入的元素插入到找到的位置
            }
            h/=2;//相隔距離每次減半
        }
// display(a);
    }
}

歸併排序

經過遞歸的方式,把n個元素等分紅兩部分,而後再將這兩部分等分紅四部分,以此類推,知道將n個元素分紅n個單獨的組,這n個組由於只有一個元素,因此是有序的。
而後把其中相鄰的兩個組合並,要求合併後的組也是有序的,因而造成了n/2個組。而後再相鄰的組合並,造成n/4個組。依此類推,知道合併成只有一個組,排序完畢。
合併兩個組時,能夠將兩個指針指向兩個數組的第一個位置,而後比較大小,把小的放入臨時數組,而後該指針後移。若是其中一個數組的指針到了最右,則把另外一個數組剩下的元素複製到臨時數組。
過程以下圖:
這裏寫圖片描述
時間複雜度:O(nlgn)
缺點:須要開闢一個與原數組相同大小的輔助數組
實現:指針

public class Merge {
    static int[] temp=null;
    public static void sort(int[] a){
        int start,end;
        temp=new int[a.length];
        start=0;
        end=a.length-1;
        merge(a,start,end);
// display(a);
    }
    public static void merge(int[] a,int start,int end){
        if(start==end)
            return;
        int mid=(start+end)/2;

        merge(a,start,mid);//把數組分紅兩半,分別排序
        merge(a,mid+1,end);
        if(a[mid]<a[mid+1])//前一個數組中最大的數比後一個數組中最小的數小,說明兩個數組合並後已經有序,全部不須要再排序
            return;
        mergeSort(a,start,end);
    }
    public static void mergeSort(int[] a,int start,int end){
        int mid=(start+end)/2;
        for(int i=start,j=mid+1,k=start;k<=end;k++){//i,j分別表示兩個數組的第一個元素(物理上只有一個數組,但邏輯上分爲兩個數組)
            if(i>mid)   temp[k]=a[j++];//若是第一個數組比較結束,則以此把第二個數組全部元素複製到臨時數組
            else if(j>end)  temp[k]=a[i++];//若是第二個數組比較結束,則以此把第一個數組全部元素複製到臨時數組
            else if(a[i]<a[j])  temp[k]=a[i++];//若是第一個數組i位置元素比較小,則把a[i]複製到臨時數組,而且i右移
            else    temp[k]=a[j++];//若是第二個數組j位置元素比較小,則把a[j]複製到臨時數組,而且j右移
        }
        System.arraycopy(temp, start, a, start, end-start+1);//把臨時數組中的元素複製到原數組
    }
    public static void display(int[] a){
        for(int i=0;i<a.length;i++)
            System.out.print(a[i]+" ");
        System.out.println();
    }
}

快速排序

一、 給定n個數,從start到end進行排序
二、 從左邊往右邊找到i知足a[i]>a[start]
三、從右邊往左邊找到j知足a[j]>a[start]
四、 交換a[i]和a[j]
五、 重複三、4,直到i>=j
六、 交換a[start]和a[j],此時知足a[0~j-1]都小於a[j], a[j+1,end]都大於於a[j]
七、 從1從新執行,此時start=start,end=j-1 和start=j+1,end=end,直到start>=end
過程圖:
這裏寫圖片描述
時間複雜度(平均):O(nlgn)
適用範圍:重複數字少,有序數字少
實現:code

public class Quick {
    public static void sort(int[] a) {
        sort(a, 0, a.length - 1);
    }

    private static void sort(int[] a, int start, int end) {
        if (start >= end)
            return;
        int i = partition(a, start, end);// 返回切割位置,此時i位置的元素大於[start,i-1],下雨[i+1,end]
        sort(a, start, i - 1);
        sort(a, i + 1, end);
    }

    private static int partition(int[] a, int start, int end) {// 要求返回位置的元素大於等於左邊元素,返回位置的元素都小於等於右邊的元素
        int i = start + 1, j = end;
        i = start;
        j = end + 1;
        int value=a[start];
        while (true) {
            while (a[++i] < value)
                // 向右尋找比a[start]大的數
                if (i == end)
                    break;
            while (a[--j] > value);// 向左尋找比a[start]小的數,但由於最左邊是a[start],因此j>start恆成立

// if (j == start)
// break;
            if (i >= j)// 若是i,j相遇或i>j,則結束尋找
                break;
            swap(a, i, j);
        }
        // j就是知足a[j]大於等於左邊,小於等於右邊的位置
        // 由於a[j]必定小於等於a[start]或者j等於start
        // a[i]必定大於等於a[start]或者i等於end
        // 根據前一種狀況,i跟j不在同一位置的話,a[j]就是小於a[start]最右邊的數,再往右的話就是a[i],而a[i]必定大於a[start]
        // 若是a[j]>a[start],就意味着j到了最左邊(由於j是從右往左尋找比a[start]小的數的,若是沒找到且停下來了,就意味着到頭了)
        // 可是所謂的最左邊就是a[start]所在位置,因此a[j]必定小於或等於a[start]
        swap(a, j, start);
        return j;

    }

    private static void swap(int[] a, int p, int q) {
        int t = a[p];
        a[p] = a[q];
        a[q] = t;
    }
}

堆排序

(二叉)堆有序定義:在一顆二叉樹中,若是全部父節點不小於子節點,則該二叉樹就是堆有序
下潛操做定義:若是一個父節點大於兩個子節點或者子節點中的一個,那麼把父節點與子節點中大的那個交換位置,而後把以前的父節點(如今已被交換到子節點的位置)與其新的子節點進行比較並交換,知道該節點大於其子節點,則結束操做。該操做稱爲下潛操做。排序

對於一個有序堆,序號爲0的節點必定是最大的元素,把這個元素去掉,並把最後一個元素放到0位置,該堆又變成了無序堆。而後對0節點進行下潛操做,該堆從新變成了有序堆。而後繼續移除、繼續排序,最終該堆大小爲0,原數組的位置變爲一個有序序列。
這裏寫圖片描述
該圖爲把一個有序堆的最大元素移除、排序最終獲得有序數列的過程。
那麼接下來的問題就是如何把一個無序堆構建成有序堆。
只要把一個堆自下而上進行下潛操做,那麼最終就是一個有序堆。而最後一層節點沒有子節點,因此不須要進行下潛操做。從倒數第二層的最後一個節點開始進行下潛操做,知道第一個元素,那麼就能夠造成一個有序堆,過程以下圖:
這裏寫圖片描述遞歸

算法複雜度:O(nlgn)圖片

實現:

public class Heap {
    // 以下一顆二叉樹數字表示其序號
    //
    // 0
    // 1 2
    // 3 4 5 6
    //
    // 根據二叉樹的定義,序號爲n的節點的父節點(若是存在)序號爲n/2⌉-1,其子節點(若是存在)序號爲n*2+1和n*2+2
    // 若是共有n個元素,則倒數第二次的最後一個節點序號爲(n+1)/2⌉+1

    public static void sort(int[] a) {
        int n = a.length;
        for (int k = (int) (Math.ceil((double) (n + 1) / 2) + 1); k >= 0; k--)
            // 從倒數第二層的最後一個幾點開始建堆
            sink(a, k, n);
        while (n > 1) {
            swap(a, 0, --n);
            sink(a, 0, n);
        }
    }

    public static void sink(int[] a, int k, int n) {// 下潛操做
        while (k * 2 + 2 < n) {
            int j = k * 2 + 1;// j表示第一個子節點
            if (a[j] < a[j + 1])// 若是第一個子節點小於第二個子節點
                j++;// 此時j表示第二個子節點
            if (a[k] > a[j])// 若是大的子節點小於父節點,則表示父節點大於兩個子節點,則不須要繼續下潛了
                break;
            swap(a, k, j);// 不然把父節點和子節點交換
            k = j;
        }
    }

    private static void swap(int[] a, int p, int q) {
        int t = a[p];
        a[p] = a[q];
        a[q] = t;
    }
}
相關文章
相關標籤/搜索