1. 程式人生 > 其它 >演算法路漫漫(一) 簡單排序

演算法路漫漫(一) 簡單排序

1.認識時間複雜度

常數時間的操作:一個操作如果和資料量沒有關係,每次都是固定時間內完成的操作,叫做常數操作。
時間複雜度為一個演算法流程中,常數運算元量的指標。常用O(讀作big O)來表示。具體來說,在常數運算元量的表示式中,只要高階項,不要低階項,也不要高階項的係數,
剩下的部分如果記為f(N),那麼時間複雜度為O(f(N))。評價一個演算法流程的好壞,先看時間複雜度的指標,然後再分析不同資料樣本下的實際執行時間,也就是常數項時間。

衡量演算法複雜度

1.記憶體(Memory)
2.時間(Time)
3.指令的數量(Number of Steps)
4.特定操作的數量
    磁碟訪問數量
    網路包數量
5.漸進複雜度(Asymptotic Complexity)

演算法的執行時間與什麼相關

1.輸入的資料。(例如:如果資料已經是排好序的,時間消耗可能會減少。)
2.輸入資料的規模。(例如:6 和 6 * 1093.執行時間的上限。(因為執行時間的上限是對使用者的承諾。)

演算法分析要保持大局觀(Big Idea),其基本思路:

1.忽略掉那些依賴於機器的常量。
2.關注執行時間的增長趨勢。

比如:T(n) = n3+ 99n3+ 9999 的趨勢就相當於 T(n) = Θ(n3)。

漸近記號(Asymptotic Notation)通常有 O、 Θ和Ω 記號法。big O 表示按演算法最差表現估算,bigθ 表示按演算法平均表現估算,bigΩ 表示按演算法最好表現估算。儘管技術上Θ 記號較為準確,但通常仍然使用 O 記號表示。

使用 O 記號法(Big O Notation)表示最壞執行情況的上界。例如,

1.線性複雜度 O(n) 表示每個元素都要被處理一次。
2.平方複雜度 O(n2) 表示每個元素都要被處理 n 次。
Notation Intuition Informal Definition

f is bounded above by g asymptotically

Two definitions :
Number theory:

f is not dominated by g asymptotically

Complexity theory:

f is bounded below by g asymptotically

f is bounded both above and below by g asymptotically

2.簡單排序

2.1 選擇排序

選擇排序(Select Sort) 是直觀的排序,通過確定一個 Key 最大或最小值,再從帶排序的的數中找出最大或最小的交換到對應位置。再選擇次之。雙重迴圈時間複雜度為 O(n^2)

    private void sellectionSorted(int[] arr){
        if(arr == null || arr.length <=1){
            return;
        }
        // i ~ N-1
        for(int i=0;i<arr.length-1;i++){
            int minIndex=i;
            // i ~ N-1 get min
            for(int j=i+1;j<arr.length;j++){
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;    
            }
            swap(arr ,minIndex,i);
        }
    }


    private void swap(int[] arr, int a, int b){
        int tmp = arr[a];
        arr[a] = arr[b];
        arr[b] = tmp;
    }

2.2 氣泡排序

氣泡排序(Bubble Sort) 最為簡單的一種排序,通過重複走完陣列的所有元素,通過打擂臺的方式兩個兩個比較,直到沒有數可以交換的時候結束這個數,再到下個數,直到整個陣列排好順序。因一個個浮出所以叫氣泡排序。雙重迴圈時間 O(n^2)

    private void bubbleSorted(int[] arr){
        if(arr == null || arr.length <=1){
            return;
        }
        // i ~ N-1
        for(int i=arr.length-1;i>0;i--){
            // i ~ N-1 set max to the end of arr
            for(int j=0;j<i;j++){
                if(arr[j+1] < arr[j]) {
                    swap(arr ,j+1,j);
                } 
            }
            
        }
    }

    // 異或運算
    private void swap(int[] arr, int a, int b){
        arr[a] = arr[a]^arr[b];
        arr[b] = arr[a]^arr[b];
        arr[a] = arr[a]^arr[b];
    }

2.3 插入排序

插入排序(Insertion-Sort)的演算法描述是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。

    private void insertSorted(int[] arr){
        if(arr == null || arr.length <=1){
            return;
        }
        // 0 ~1 compare and swap
        // 0 ~2 compare and swap
        for(int i=0;i<arr.length;i++){
            // i ~ N-1 compare and swap
            for(int j=i;j>0;j--){
                if(arr[j-1] > arr[j]) {
                    swap(arr ,j-1,j);
                } 
            }
            
        }
    }

    // 異或運算
    private void swap(int[] arr, int a, int b){
        arr[a] = arr[a]^arr[b];
        arr[b] = arr[a]^arr[b];
        arr[a] = arr[a]^arr[b];
    }

2.4 二分法

二分法查詢,也稱為折半法,是一種在有序陣列中查詢特定元素的搜尋演算法。首先,從陣列的中間元素開始搜尋,如果該元素正好是目標元素,則搜尋過程結束,否則執行下一步。如果目標元素大於/小於中間元素,則在陣列大於/小於中間元素的那一半區域查詢,然後重複上面的的操作。如果某一步陣列為空,則表示找不到目標元素。二分法查詢的時間複雜度O(logn)。

    private int searchMatch(int[] arr, int low, int high, int target){
        if(low > high){
            return -1;
        }
        int mid = low + ((high-low)>>1);

        // 
        if(arr[mid] == target){
            return mid;
        } else if(arr[mid] < target){
            // search in [mid+1, high]
            return searchMatch(arr, mid+1, high, target);
        } else {
            // search in [low, mid-1]
            return searchMatch(arr, low, mid-1, target);
        }
    }

一般下面幾個場景適合二分查詢

1.在一個有序陣列中,查詢某個數是否存在

2.在一個有序陣列中,查詢>=某個數最左側位置

3. 在一個無序陣列中,任何相鄰兩數不相等,尋找區域性最小值

3.補充

3.1 異或運算

1. 0^N=N, N^N=0

2.異或運算滿足交換率和結合率
A^B = B^A, A^(B^C) = (A^B)^C
3.同樣一批數,異或運算,先後順序變化 不影響結果
A^B^...^Z = Z^Y^...^A
4.無進位加法,相同為0,不同為1;
int a= 3= 011
int b= 5= 101
int c= a^b = 011^101=110=6

3.2 取出一個數對應二進位制最右側的1對應的整數

// 與上自己的取反加1
int
a= A & (~A+1);

3.3 對數器

1.有一個你想要測的方法A
2.實現一個絕對正確但是複雜度不好的方法B
3.實現一個隨機樣本產生器
4.實現比對的方法
5.把方法A和方法B比對很多次來驗證方法B是否正確。
6.如果有一個樣本使得比對出錯,列印樣本分析是哪個方法出錯
7.當樣本數量很多時比對測試(假設10萬次測試)依然正確,可以確定方法A已經正確