1. 程式人生 > >簡單算法匯總

簡單算法匯總

基本思想 reg 變化 轉換成 res 簡單選擇排序 this 復制 位置

一、全排列問題(Permutation)
問題描寫敘述:即給定{1,2,3},返回123,132,213,231,312,321
《Permutation》
1)無順序的全排列問題:
將序列P(n) = {1….. n}的全排列問題看成P(n)={1,P(n-1)} + {2,P(n-1)}…..的問題。即確定第一個元素的值為1。然後和剩下n-1個元素的全排列結果組合到一起;然後再將1和剩下的每一個元素進行交換。然後和其剩下的n-1個元素排列結果進行組合。顯然這是一個遞歸問題。

        // 遞歸實現
        public void permutation(int
[] datas, int index ) { if (datas == null || index < 0 || index >= datas. length) // 差錯控制 return; // 遞歸終點 if (index == datas .length - 1) { print_data( datas); } for (int i = index ; i < datas .length ; i ++) { // 交換(註意i==index時。事實上並沒有交換)
swap( datas, i , index); permutation( datas, index + 1); swap( datas, i , index); } }

2)有反復值的全排列問題:
註意每次遞歸的時候去除反復值就可以,即有反復值就不進行交換。

     // 非反復情況
        public void permutationNoRepeat(int[] datas, int index ) {
               if
(datas == null || index < 0) // 差錯控制 return; if (index >= datas .length - 1) { print_data( datas); return; } for (int i = index ; i < datas .length ; i ++) { // 取出反復值 if ((i != index ) && (datas[i] == datas[index])) continue; swap( datas, i , index); permutationNoRepeat( datas, index + 1); swap( datas, i , index); } }

3)找到下一個更大值(next_permutation)
即1342。找到下一個更大值1423。
解題思路:
基本思想是從後往前遍歷。找到第一個遞增的二元對(即a[j] < a[j+1]);然後從後往前遍歷到j+1位置,找到第一個k值a[k]>a[j],交換k和j,然後將j+1後面的序列反轉;
註意增序列則表示字典序較小,減序列則表示字典序較大;假設遍歷找不到增序列,表示當前數值已經是最大值。
原理:
http://jingyan.baidu.com/article/63acb44a90370061fcc17e18.html

     public boolean next_permutation(int[] datas) {
               if (datas == null || datas.length == 0)
                      return true ;
               int p1 = datas .length - 2;
               int p2 = datas .length - 1;

               for (; p1 >= 0; p1 --) {
                      if (datas [p1 ] < datas [p1 + 1])
                            break;
              }

               if (p1 == -1) {
                     reverse( datas, 0, datas. length - 1);
                      return true ;
              } else {
                      for (; p2 > p1 ; p2 --) {
                            if (datas [p2 ] > datas [p1 ]) {
                                  swap( datas, p1, p2);
                                  reverse( datas, p1 + 1, datas. length - 1);
                           }
                     }
              }
              print_data( datas);
               return false ;
       }



        // 顛倒數組
        private void reverse(int[] datas, int start , int end) {
               while (start < end ) {
                     swap( datas, start ++, end --);
              }
       }

4)有順序的全排列(能夠看做非遞歸實現)
解題思路:使用next_permutation也以獲得全排列,即不斷調用next_permutation來獲得更大值。然後依次輸出

    public void permutation(int[] datas) {
               if (datas == null)
                      return;
               do {
                     print_data( datas);
              } while (!next_permutation(datas )) ;
       }

5)有特殊要求的全排列,比方要求4必須在3前面
解題思路:進行全排列,然後推斷4和3的位置再進行輸出;(?更好方法)

6)查找數字排列組合中的第k個組合:(Permutation Sequence)
解題思路:
即給定{1,2,3},和3,返回全排列(123,132,213,231,312,321)中的第三個,即213
註意:
這裏不須要將全排列計算出來。然後再得到第k個;
或者使用nextPermutation來計算第k個;這兩種時間復雜度都較大;
最好的做法:是直接利用數學知識進行計算:
還是分為兩層來看,第一位確定的話。後面P(n-1)的全排列為(n-1)!。則能夠依據算法推斷出第一位是哪個數值;剩下的類推。

public class Solution { 
    public String getPermutation( int n , int k) { 
        if ((n <= 0) || (n > 9) || ( k <= 0) || ( k > countN( n))) 
            return "" ; 
        // 記錄結果字符串 
        StringBuilder resBder = new StringBuilder(); 
        // 記錄當前數字集合中剩下的未使用數字 
        List<Integer> remainList = new ArrayList <>(); 
        // 初始化remainList 
        for (int i = 1; i <= n ; i ++) 
            remainList.add(i ); 
        k--; 
        while (n > 1) { 
            int count = countN(n - 1); 
            int index = k / count ; 
            // 加入結果數字 
            resBder.append( remainList.get(index )); 

            // 更新,進行下一層循環 
            remainList.remove(index ); 
            k %= count; 
            n--; 
        } 
        resBder.append( remainList.get(0)); 
        return resBder .toString(); 
    } 

    // 計算每一個數字的階乘 
    private int countN(int n ) { 
        int result = 1; 
        while (n > 0) { 
            result *= n--; 
        } 
        return result ; 
    } 
} 

一、二叉樹問題匯總:
1)二叉樹三種遍歷非遞歸實現
2)重建二叉樹
3)推斷樹A是否為樹B的子樹
4)二叉樹鏡像
5)從上往下打印二叉樹
6)二叉搜索樹的後序遍歷
7)二叉搜索樹轉化為雙向鏈表
8)二叉樹中和為某一值的路徑
9)求二叉樹的深度
10)平衡二叉樹

二、遞歸問題匯總
1)魔術索引問題
即一個遞增序列,滿足A[i]=i的稱為魔術索引;
1>無反復值的魔術索引問題:二分法
2>有反復值:縮小範圍法
2)跳臺階、斐波那契
3)機器人走方格:都註意使用空間存儲來優化。
4)N皇後問題:
回溯法:由於每一行僅僅能有一個皇後,所以使用一個一維數組index[]來記錄每一行的皇後的列的位置就可以,然後給數組中的每一個元素賦個初始值-1,表示當前行的位置還沒有確定;
然後從第一個皇後開始賦值,從0開始,再給第二個皇後賦值,也是從0開始遍歷。然後寫一個推斷當前index[]矩陣是否合法的函數,每次給一個皇後賦值的時候。都須要進行一次推斷,假設合法,則繼續給下一個皇後賦值;假設不合法,就取這一層相應的index裏面的記錄的數值比方說是m,然後給這個皇後賦值m+1,繼續推斷是否合法,反復之前操作;
假設在這一層,0-n-1全部都賦值完了,皇後仍然沒有找到合法的位置,那就採用回溯法,把這一層的index值又一次置為-1。回到上一層設置上一層的皇後的位置,往右移一步。反復之間操作。


直到賦值到第N層。全部皇後位置都合法之後,再將結果輸出;
由於可能的結果不止有一種。當輸出一種結果之後。回溯到N-1層,又一次開始之前的設置,檢查操作;

遞歸法:遞歸法比較簡單,比方八皇後問題,就能夠看成已知當中一個皇後位置。求其它7個的位置。然後再確定第二個皇後的位置。求剩下6個皇後的位置;以此類推進行遞歸,直至遞歸到最後一層,輸出結果。

三、最遠距離問題JumpGame:
即推斷[3,1,3,1,1,0,4]是否可到達。


解決方法:非常easy,一直往前走,計算每一步能到達的最遠位置index+A[index];和之前記錄的最遠位置reach做比較。大於則更新reach值。循環的終點是到了終點即i==n了或者i>reach了。這個時候推斷i==n(註意是n。由於在n-1後,還會再i++,然後循環才幹推出)。

public boolean canJump(int[] nums) { 
    int i = 0; 
    int n = nums.length; 
    for ( int reach = 0; i < n && i <= reach; ++i) 
        reach = Math. max(i + nums[i], reach); 
    return i == n; 
}

四、構造順序矩陣和打印循環順序矩陣Matrix:
《leetcode-54 Spiral Matrix 順時針打印矩陣(《劍指offer》面試題20)》
《leetcode 58、Length of Last Word。59、Spiral Matrix II ;60、Permutation Sequence》
解題思路:定義上下左右四個維度的限定值,然後向右。向下,向左,向上進行遍歷,並註意更新相應值。

// 構造序列
        public int [][] generateMatrix(int n) { 
        if (n < 0) 
            return null ; 

        int[][] matrix = new int[n][n]; 
        // 記錄上下左右邊界值 
        int left = 0; 
        int right = n - 1; 
        int top = 0; 
        int bottom = n - 1; 
        // 註意起始值為1 
        int count = 1; 

        while ((left <= right ) && (top <= bottom)) { 
            // 往右走進行賦值 
            for (int j = left ; j <= right ; j ++) 
                matrix[ top][ j] = count++; 
            ++ top; // 更新邊界值 

            // 向下走進行賦值 
            for (int i = top ; i <= bottom ; i ++) 
                matrix[ i][ right] = count++; 
            -- right; 

            // 向左走進行賦值 
            for (int j = right ; j >= left ; j --) 
                matrix[ bottom][ j] = count++; 
            -- bottom; 

            // 向上走進行賦值 
            for (int i = bottom ; i >= top ; i --) 
                matrix[ i][ left] = count++; 
            ++ left; 
        } 
        return matrix ; 
    }
        // 打印序列
        public List<Integer> spiralOrder(int[][] matrix) { 
        List<Integer> result = new ArrayList<>(); 

        if ((matrix == null) || (matrix.length == 0) || ( matrix[0]. length == 0)) 
            return result ; 

        int left = 0; int right = matrix[0].length - 1; 
        int top = 0;  int bottom = matrix.length - 1; 

        while ((left <= right ) && (top <= bottom)) {   
            // ==== 先向右遍歷 ===== // 
            for (int j = left ; j <= right ; j ++) 
                result.add( matrix[ top][ j]); 
            top++; // 遍歷後要註意更新四個維度的值 

            // ===== 向下遍歷 ===== // 
            for (int i = top ; i <= bottom ; i ++) 
                result.add( matrix[ i][ right]); 
            right--; 

            // ===== 向左遍歷 ===== // 
            for (int j = right ; j >= left ; j --) 
                result.add( matrix[ bottom][ j]); 
            bottom--; 

            // ===== 向上遍歷 ===== // 
            for (int i = bottom ; i >= top ; i --) 
                result.add( matrix[ i][ left]); 
            left++; 
        } 
        return result ; 
    } 

五、連續子數組的最大和
解題思路:使用一個max記錄當前最大值,使用一個curSum來記錄遍歷數組時候的暫時的和;
遍歷數組,比方來到第i個元素,假設之前的curSum<0,那麽curSum再加上a[i]僅僅會使得相加值更小,因此取更大值。也就是令curSum=a[i],把前面的和序列舍棄掉;
假設curSum>=0,則能夠直接把兩個值相加來作為新的curSum;
然後再將curSum和max做比較,去更大值來更新max值。最後遍歷完一遍。返回max值就是最大的值。

六、數值的整數次方pow:
解題思路:註意處理n<0的情況,n<0時,要把a轉化成1/a;
還要註意處理底數為0。而指數卻為負數的不合法情況;

private double power(double x, int n) { 
    if (n < 0) { 
        n = - n; x = 1.0 / x; 
    } 
    double result = 1.0; 
    for ( double base = x ;n > 0; n >>= 1) { 
        if ((n & 0x1) == 1) 
            result *= base; 
        base *= base; 
    } 
    return result; 
} 

七、推斷是否為同位詞:
即“ate””eat” “tae”為同位詞;從一串字符串中找出同位詞:
解題思路:使用HashMap,先對全部詞進行字典序排序,然後比對。

八、字符串數組排序:
由於字符串實現了Comparable接口,能夠比較兩個字符串之間的大小,因此能夠像實現int數組排序一樣實現字符串數組的排序。

九、k個排序鏈表合並成一個排序鏈表(或者k個排序數組合並成一個排序數組)
解題思路:創建一個k階的最小堆,創建堆的時間復雜度o(n*logn);取最小值的事件復雜度為O(1);刪除最小值。加入一個新值。調整堆的時間復雜度為O(lgn);
每次取最小值。最為新鏈表(新數組)的下一個值;然後再從該數值相應數組中取出一個新值放在堆頂,然後調整堆。反復上述過程;
也能夠使用敗者樹來實現。

相關題:找到一個數組中的第k大的值
思路:維護一個k階最小堆。新值和堆頂元素進行比較。假設大於堆頂元素值。則替換堆頂元素,調整堆;

十、求兩個整數的最大公約數。最小公倍數(輾轉相除法)

    public int maxgcp(int x, int y) {
       int num1 = x, num2 = y;

       // 向排序,即將較大值移到前面
       if (x < y) {
               int temp = x ;
               x = y;
               y = temp;
       }

       // 輾轉相除法
       int r = 1;
       while ( r != 0) {
               r = x % y;
               x = y;
               y = r;
       }

       System. out.println("最大公約數:" + x );
       System. out.println("最小公倍數:" + num1 * num2 / x );

       return x;

    }

十一、求格雷碼:
格雷碼是二進制轉化成的編碼,它的相鄰兩個數的格雷碼僅僅有一個位是不同的;最大數和最小數也僅僅有一個位不同;所以適應了真正電氣環境下數值不能連續變化多位的情況;
編碼:二進制轉化為格雷碼: 第一位保持不變,然後從最右邊一位開始,與左邊一位進行異或,得到的異或值即為格雷碼。
1001001010 ==> 1101101111
解碼:格雷碼轉化為二進制: 解碼從最左邊開始。一個為解碼值保持不前,然後從左邊第二位開始,每一位和前一位的解碼值進行異或,得到的值即為解碼值。

十二、兩個鏈表的公共交點:
解法:兩個鏈表相交,則鏈表相交的第一個公共點之後的全部節點一定是相交的;即兩個鏈表是呈Y型的。
1)兩個鏈表無環時的解法:
(1)能夠先遍歷兩條鏈表。得到兩個鏈表的長度m,n;然後雙指針,一個先走m-n步(假設m>n),然後兩個指針同一時候出發,第一個相交的節點即為公共節點;若一直遍歷結束也沒有公共節點,則兩個鏈表不相交;
(2)方法同一,但不須要遍歷完兩個鏈表來獲得兩個鏈表的長度;記兩個鏈表為A、B,能夠設置兩個指針p、q,同一時候出發,當q到達鏈表尾(即為NULL時),此時從鏈表A頭部出發一個指針a;當p到達鏈表尾時,此時從鏈表B頭部出發一個指針b;則a,b轉化為方法一中的問題;(兩個方法的時間復雜度同樣)
(3)假設兩個鏈表相交,由於其公共節點之後的鏈表同樣;此時將當中一個鏈表鏈接到還有一個鏈表之後,形成的新鏈表一定有環,則原問題轉化為求一個有環鏈表的環的公共交點問題。
2)考慮鏈表有環
假設兩個有環的鏈表相交。那麽它們的環必定為公共環。

假設交點不在環上,即在環前面的直鏈上,即轉化為前面的連個無環鏈表求解公共點的問題。第一個公共點就是第一個交點。

可是假設交點在環上,即環的入口點不同,那麽任一環的入口點都可為第一公共點。

十三、刪除字符串中的指定字符:
題目:輸入兩個字符串。從第一字符串中刪除第二個字符串中全部的字符。比如,輸入”They are students.”和”aeiou”。則刪除之後的第一個字符串變成”Thy r stdnts.”。
解題思路:基本思想是遍歷字符串s1,推斷字符串s1中的每一個字符在s2中是否存在。假設存在則刪除;
這裏就須要考慮兩個細節:
1)刪除字符問題
一個是刪除字符的處理;傳統方法是刪除之後。讓字符串數組後的全部字節往前移一位。這樣操作全然部字符的時間復雜度為O(n^2);明顯有能夠優化的空間:
使用兩個指針,pFast,pSlow;當有字符須要刪除時,pSlow不變。pFast前移;當有字符不須要刪除時,將pSlow和pFast指向的字符交換。最後取0-pSlow數組的字符組成的字符串就可以。基本思想是將後面不須要刪除的字符替換到前面來。
2)推斷字符是否須要刪除:
推斷字符是否須要刪除。即該字符是否在s2字符串中;能夠使用的方法如Hash。使用HashMap或則HashSet把s2中的字符存儲進來。然後遍歷s1每一個字符c在HashMap是否已經存在就可以;時間復雜度為O(1)。
同樣的方法能夠是使用一個boolean[256]數組。記錄char(共256個)是否存在;原理相似Hash

十四、計算一個字符串中的最長無反復字符串:
問題描寫敘述:計算給定一個字符串。如”abcabcbb” 則其最長無反復字符串為 “abc”;字符串”bbbbb”其最長無反復字符串為”b”;
解題思路:使用一個256的int數組indexs來記錄每一個char在字符串中出現的位置。
假設indexs[i]為-1,表示當前測試字符串中沒有出現過字節i,因此直接將indexs[i]賦值為i,即記錄其出現位置。
假設indexs[i]不為-1,則表示已經出現過。這裏要依據此算法分成兩種情況討論;這裏使用一個start記錄當前測試字符串的起始位置,即當前測試的字符串為start–i。假設indexs[i]小於start,表示該字節出如今測試字符串之前,因此能夠當做-1情況對待;再者,假設已經存在,則當前字符串已經不是滿足要求的唯一性字符串了。因此計算當前的非反復最大值,和系統當前記錄的最大值作比較,記錄更大值;然後測試下一個字符串,為滿足非反復條件。則下一個測試字符串的起始位置須要從index[start]+1開始;

十五、求數組的最大值與最小值
1)主要的遍歷法須要比較2N次。
2)採用雙元素法,記錄max和min;每次比較兩個值,較小值和min做比較,較大值和max作比較;這樣終於的比較次數為1.5*N次。
3)採用分治法;將數組分為兩部分。分別得到兩個子數組的最大值最小值,然後再合並在一起進行比較;

十六、找出數組中僅僅出現一次的數:
1)其它的數都出現了偶數次:直接使用全部異或就可以。
2)其它數出現的是奇數m次:則假設沒有該特殊值。其它全部值二進制時二進制各個位相加之和肯定都能被m整數整除。再加上異常值,則全部位對於n進行取余。得到的值必定是該特殊值的二進制表示。

十七、數組中出現次數超過一半的數:
解題思路:
解法一:將原問題轉化為求數組的中位數,採用高速排序的思想,每一次Partition取末位為哨兵。遍歷將小於、大於哨兵的數分別移至哨兵左右。最後返回哨兵在處理後的數組中的位置。

不斷縮小要處理的數組的長度大小。終於確定返回值為數組長度一半的元素。即為中位數。

解法二:由於題設該數字出現的次數大於其它全部數字出現的次數。故用兩個變量,一個表示數字num_data,一個表示次數;當下一個數字等於num_data時,則times加1;如若不等於,time減1;直至times等於0,則將num_data更換為下一個數字;由題知,最後得到的num_data的結果必為所要求得的值。

十八、旋轉數組:
問題描寫敘述:即給定一個數組如[1,2,3,4,5,6,7] ,及一個k=3,將後面k個數字旋轉到前面來。則旋轉之後的數組為[5,6,7,1,2,3,4].
解題思路:旋轉字符串問題比較相似。即先將數組分為兩部分,後面k個數字為一組,前面n-k個為一組,將兩組分別反轉。然後再將整個數組反轉就可以。反轉能夠採用雙指針法。

十八、推斷一個數是否是2的n次方:
解題思路:
一個數num是2的n次方則二進制表示為(000010000…),則num-1是(000001111111…);其num與num-1二進制位必定沒有一個相等。
因此推斷num&(num - 1)是否等於0就可以。

十九、計算一個數的二進制中1的個數:
解題思路:
解法一:直接轉換成二進制然後循環右移取出二進制中每一位推斷是否為1就可以
解法二:使用n&(n-1);二進制中有幾個1,就循環幾次n&(n-1)得到0

二十、2Sum,3Sum,4Sum問題:
《leetcode-1 Two Sum 找到數組中兩數字和為指定和》
《Leetcode-15 3Sum》
《leetcode-18 4Sum》
《leetcode-16 3Sum Closest》
問題描寫敘述:給定一個數組和一個target結果值,求2個/3個/4個數字的和為target的解法。
**解題思路:**2Sum問題典型的解決方案是使用雙指針。從頭部尾部同一時候往中間走進行推斷;
首先對數組進行排序,時間復雜度為O(NlogN)
然後從i=0,j=end開始和末位的兩個數字開始。計算兩個之和sum,若sum大於目標值target,則須要一個較小的因子,j–。反之,i++;直至找到終於的結果

二十一、排序算法
技術分享
1、冒泡排序(交換排序):
在循環遍歷中,每一次遍歷數組將最大的數字通過交換沈到最後一位
時間復雜度(O(n^2)):最壞O(n^2),最好O(n);空間復雜度:O(1); 穩定
優化:
1)設置一個flag標示,當一次遍歷有交換設置為true。沒有交換時則表示前面數組已經有序,無需再做遍歷
2)記錄每一次遍歷發生交換的最後位置。由於這個位置之後的數組肯定是有序的。最後交換位置為0時。循環結束

2、直接插入排序:
在循環遍歷中,第j次遍歷過程向已經排好序的數組a[1..j-1]插入a[j]
時間復雜度(O(n^2)):最壞O(n^2),最好O(n);空間復雜度:O(1)。穩定
優化:查找插入排序。在插入的時候使用二分法

3、希爾排序(插入排序):將數組分為非常多小序列,然後分別進行直接插入排序;待整個數組基本有序的時候,最後進行一次插入排序
時間復雜度(O(n^(1-2))):最壞O(n^2)。最好O(n)。空間復雜度:O(1);不穩定
實現。選取一個增量序列(遞減到1)(比方x/2序列)

    void ShellInsertSort(int a[], int n, int dk) {
           for(int i = dk ; i < n ; ++i ){
               if(a [i] < a [i - dk]){          //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表後插入
                   int j = i - dk ;
                   int x = a [i];           //復制為哨兵。即存儲待排序元素
                   a[i] = a[i - dk];         //首先後移一個元素
                   while(x  < a[j]){     //查找在有序表的插入位置
                       a[j + dk] = a[j];
                       j -= dk;             //元素後移
                   }
                   a[j + dk] = x;            //插入到正確位置
               }
               print(a, n,i );
           }
       }

        /**
        * 先按增量d(n/2,n為要排序數的個數進行希爾排序
        *
        */
        void shellSort(int a[], int n){
           int dk = n /2;
           while(dk >= 1){
               ShellInsertSort( a, n, dk);
               dk = dk/2;
           }
       }

4、高速排序:
每一次循環選取一個中間值,將比這個值大的元素移動到中間的後面,比中間值小的移到中間值前面;這樣分成了兩個子序列,然後再對子序列進行高速排序。


時間復雜度:O(nlogn), 最好O(nlogn),最壞(基本有序時退化成冒泡)O(n^2);空間復雜度:O(1)+(遞歸棧的緩存空間最大O(n),最小O(logn)) 不穩定

    private void quickSort(int[] a, int start , int end) {
               if (start < end ) {
                      int mid = partition(a, start, end);
                     quickSort( a, start, mid - 1);
                     quickSort( a, mid + 1, end);
              }
       }

        private int partition(int[] a, int low , int high) {
               int temp = a [low ];

               while (low < high ) {
                      while ((low < high ) && (a[high] >= temp))
                           -- high;
                     swap( a, low, high);

                      while ((low < high ) && (a[low] <= temp))
                           ++ low;
                     swap( a, low, high);
              }

               a[ low] = temp;
               return low ;
       }

實現:單指針法,雙指針法
高速排序的改進:
1)中樞值的選取:傳統方法中使用最左元素或者最右元素,這樣在數組基本有序時的性能較差(會退化成冒泡算法)。
改進方法一:中樞值 pivot使用隨機數來代替(可是產生隨機數也會帶來性能損耗)
方法二:pivot選取first-middle-last中的中間大小的那個值。時間復雜度會降低到12/7 ln(n)
方法三:median-of-three對小數組來說有非常大的概率選擇到一個比較好的pivot。可是對於大數組來說就不足以保證能夠選擇出一個好的pivot,因此還有個辦法是所謂median-of-nine。這個怎麽做呢?它是先從數組中分三次取樣,每次取三個數,三個樣品各取出中數。然後從這三個中數當中再取出一個中數作為pivot,也就是median-of-medians。

取樣也不是亂來,各自是在左端點、中點和右端點取樣。什麽時候採用median-of-nine去選擇pivot。這裏也有個數組大小的閥值。這個值也全然是經驗值,設定在40,大小大於40的數組使用median-of-nine選擇pivot。大小在7到40之間的數組使用median-of-three選擇中數,大小等於7的數組直接選擇中數。大小小於7的數組則直接使用插入排序

5、歸並排序:
通過分治的思想,對數組序列進行分割。分割到最後每一個子序列中僅僅有一個元素的時候,然後再兩兩合並,最後每一次循環都是兩個有序數組合並成一個有序數組的操作。 穩定
O(nlogn); O(nlogn); O(nlogn); 空間復雜度O(n);

    private void mergeSort(int[] datas, int[] copy , int start, int end) {
               if (start < end ) {
                      int mid = (end + start ) / 2;
                     mergeSort( datas, copy, start, mid);
                     mergeSort( datas, copy, mid + 1, end);
                     merge( datas, copy, start, mid, end);
              }
       }

        private void merge(int[] datas, int[] copy, int start , int mid, int end) {
               int i = start ;
               int j = mid + 1;
               int index = start ;
               while ((i <= mid ) && (j <= end)) {
                      if (datas [i ] <= datas [j ])
                            copy[ index++] = datas[ i++];
                      else
                            copy[ index++] = datas[ j++];
              }

               while (i <= mid )
                      copy[ index++] = datas[ i++];
               while (j <= end )
                      copy[ index++] = datas[ j++];

              System. arraycopy(copy, start, datas, start, end - start + 1);
       }
非遞歸實現方法:
       void mergeSort2(int n){
           int s =2,i ;
           while(s <=n ){
               i=0;
               while(i +s <=n ){
                   merge(i,i+s-1,i+s/2-1);
                   i+= s;
               }
               //處理末尾殘余部分
               merge(i,n-1,i+s/2-1);
               s*=2;
           }
           //最後再從頭到尾處理一遍
           merge(0,n-1,s/2-1);
       }

6、簡單選擇排序:第i次遍歷的時候。從i-n序列中找到最小值,與第i個元素進行交換。也就是每一趟遍歷都選取一個最小元素作為第i元素值。
時間復雜度:O(n^2);最優: O(n^2);最差:O(n^2); 空間復雜度O(1);不穩定

7、堆排序:
葉子節點的值都大於或等於根節點的值(最小堆)
建堆:從n/2+1開始剩下的都為葉子節點。因此從n/2到0遞減順序,分別以每一個節點為根節點建立最小堆。


堆的調整:比較左右子節點的值得到最小值。然後比較根節點與最小值。假設根節點的值要大。則不滿足最小堆的概念,須要進行調整。將兩者進行交換。然後再以交換後的位置為根節點,反復前面過程。
時間復雜度:O(nlogn);最優: O(nlogn);最差:O(nlogn); 空間復雜度O(1);不穩定

    private void heapSort(int[] datas) {
              buildHeap( datas);

               for (int i = datas .length - 1; i >= 0; i--) {
                     System. out.printf("%d " , datas [0]);

                      // 交換data[0]和data[i];
                     swap( datas, i, 0);
                      // 註意這裏的length為i
                     HeapAdjust( datas, 0, i);
              }
       }

        // 建立堆
        private void buildHeap(int[] datas) {
               for (int i = (datas .length - 1) / 2; i >= 0; i--) {
                     HeapAdjust( datas, i, datas. length);
              }
       }

        // 調整堆中節點位置
        private void HeapAdjust(int[] datas, int s , int length) {
               int child = 2 * s + 1;
               while (child < length ) {
                      // 假設右節點存在,而且小於左節點的值
                      if ((child + 1 < length) && ( datas[ child] > datas[ child + 1]))
                            child++;
                      // 假設大於子節點的值。則不滿足最小堆的概念。故要進行調整
                      if (datas [s ] > datas [child ]) {
                           swap( datas, s, child);
                            s = child;
                            child = 2 * s + 1;
                     } else {
                            break;
                     }
              }
       }

8、基數排序/桶排序:線性。時間復雜度為O(n);

排序算法性能比較:
時間復雜度:
O(n^2)的有:直接插入排序,冒泡排序,簡單選擇排序
O(n^(1-2)): 希爾排序
O(nlogn):高速排序,歸並排序,堆排序
O(n):線性:桶排序;基數排序

選擇上:
基本有序時,選擇直接插入排序,希爾排序;而高速排序會退化成冒泡排序;
平均性能上最優的是高速排序;在最壞情況下,性能上不如堆排序和歸並排序,而n較大時。歸並排序優於堆排序,可是其須要的存儲空間要大。
直接插入排序在基本有序或者n較小時最佳。

穩定性:
即值同樣的關鍵字在排序前後位置先後順序不變
穩定的:冒泡、插入、歸並、基數
不穩定:選擇、希爾、高速、堆;

設待排序元素的個數為n.
1)當n較大,則應採用時間復雜度為O(nlog2n)的排序方法:高速排序、堆排序或歸並排序序。


高速排序:是眼下基於比較的內部排序中被覺得是最好的方法。當待排序的關鍵字是隨機分布時,高速排序的平均時間最短。
堆排序 : 假設內存空間同意且要求穩定性的。
歸並排序:它有一定數量的數據移動。所以我們可能過與插入排序組合,先獲得一定長度的序列。然後再合並。在效率上將有所提高。
2) 當n較大,內存空間同意,且要求穩定性 =》歸並排序
3)當n較小。可採用直接插入或直接選擇排序。
直接插入排序:當元素分布有序,直接插入排序將大大降低比較次數和移動記錄的次數。


直接選擇排序 :元素分布有序,假設不要求穩定性,選擇直接選擇排序
5)一般不使用或不直接使用傳統的冒泡排序。


6)基數排序
它是一種穩定的排序算法,但有一定的局限性:
1、關鍵字可分解。
2、記錄的關鍵字位數較少,假設密集更好
3、假設是數字時,最好是無符號的。否則將添加相應的映射復雜度,可先將其正負分開排序。

二十二、外部排序:
1)依據內存能夠緩存的大小,將全部數據進行分段,加入到內存,進行內部排序。
2)然後進行k-路歸並;k路歸並使用敗者樹;
http://blog.csdn.net/whz_zb/article/details/7425152
勝者樹:錦標賽排序
敗者樹:使用節點來記錄失敗的元素;勝者往上一層進行比較,擴展一個節點來記錄終於的冠軍;
敗者樹重構僅僅須要新加入的元素和父節點進行比較(即敗者進行比較)。而勝者樹則是須要和右節點進行比較;

二十四、二分查找:
考慮有數值的同樣情況

    private static int binarySearch(int[] datas, int start , int end, int target) {
               int mid = 0;
               while (start <= end ) {
                      mid = start + ( end - start) / 2;
                      if (datas [mid ] > target ) {
                            end = mid - 1;
                     } else
                            start = mid + 1;
              }
               // return mid表示查找到同樣值。或者未查找到時的可插入位置
               // 假設是查找順序的可插入位置,則須要返回start
               return mid ;
       }

三、KMP:

// 獲取next數組
        private void getNext(char[] p, int[] next ) {
               // 初始化的值
               int j = 0;
               next[ j] = -1;
               int k = next [0];

               // j相應的獲取next[j+1]
               while (j < p .length - 1) {
                      // 註意k的初始值
                      if (k == -1 || p[j] == p[k]) {
                            next[++ j] = ++ k;
                     } else {
                            k = next[ k];
                     }
              }
       }

        private int KMP(String s, String p) {
               if (s .length() < p.length())
                      return -1;
               int i = 0;
               int j = 0;

               // 獲取next數組
               int[] next = new int[p.length()];
              getNext( p.toCharArray(), next);

               while ((i < s .length()) && (j < p.length())) {
                      if (j == - 1 || s.charAt(i) == p.charAt(j)) {
                           ++ i; ++ j;
                     } else {
                            j = next[ j];
                     }
              }
               // 返回匹配位置
               if (j >= p .length())
                      return i - p .length();
               return -1;
       }


改進的算法:
        // 獲取nextval數組
        private void getNextval(char[] p, int[] next ) {
               int j = 0;
               next[ j] = -1;
               int k = next [0];

               while (j < p .length - 1) {
                      if (k == -1 || p[k] == p[j]) {
                            k++;
                            j++;
                            if (p [k ] != p [j ])
                                   next[ j] = k;
                            else
                                   next[ j] = next[ k];
                     } else {
                            k = next[ k];
                     }
              }
       }

簡單算法匯總