演算法路漫漫(一) 簡單排序
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 * 109) 3.執行時間的上限。(因為執行時間的上限是對使用者的承諾。)
演算法分析要保持大局觀(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 : 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已經正確