經常使用10種算法(一)

1、二分查找算法(非遞歸)

1,遞歸版二分查找算法

  詳情請點擊html

2,非遞歸二分查找算法介紹

  源碼:二分查找(非遞歸)java

  • 二分查找法只適用於從有序的數列中進行查找(好比數字和字母等),將數列排序後再進行查找
  • 二分查找法的運行時間爲對數時間 O(㏒₂n) ,即查找到須要的目標位置最多隻須要㏒₂n

3,代碼實現

public static int search(int[] arr, int val) {
    int left = 0;
    int right = arr.length - 1;
    while (left <= right) {
        int mid = (left + right) / 2;
        if (arr[mid] == val) {
            return mid;
        } else if (arr[mid] > val) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return -1;
}

2、分治算法

  源碼:漢諾塔git

1,分治算法介紹

  分治法是一種很重要的算法。字面上的解釋是「分而治之」,就是把一個複雜的問題分紅兩個或更多的相同或類似的子問題,再把子問題分紅更小的子問題……直到最後子問題能夠簡單的直接求解,原問題的解即子問題的解的合併。算法

  分治算法求解的經典問題:二分搜索、大整數乘法、歸併排序、快排、漢諾塔等數組

2,分治算法基本步驟

  分治法在每一層遞歸上都有三個步驟:app

  • 分解:將原問題分解爲若干個規模較小,相互獨立,與原問題形式相同的子問題
  • 解決:若子問題規模較小而容易被解決則直接解,不然遞歸地解各個子問題
  • 合併:將各個子問題的解合併爲原問題的解。

3,漢諾塔

a)介紹

  以下圖所示,從左到右有A、B、C三根柱子,其中A柱子上面有從小疊到大的n個圓盤,現要求將A柱子上的圓盤移到C柱子上去,期間只有一個原則:一次只能移到一個盤子且大盤子不能在小盤子上面,求移動的步驟和移動的次數優化

          

b)思路

  • 若是只有一個盤時,A -> C
  • 若是有n(大於1)個盤時
    • 把n-1個盤  A -> B (藉助C)
    • 把第n個盤 A -> C
    • 把n-1個盤 B -> C (藉助A)

c)代碼實現

/**
 * 移動盤子
 * @param num  一共有多少個盤子
 * @param a    開始的柱子
 * @param b    輔助的柱子
 * @param c    目標柱子
 */
public static void hanoitower(int num, char a, char b, char c) {

    if (num == 1) {
        System.out.println("第1個盤爲: " + a + " -> " + c);
    } else {
        hanoitower(num - 1, a, c, b);
        System.out.println("" + num + "個盤爲: " + a + " -> " + c);
        hanoitower(num - 1, b, a, c);
    }
}

3、動態規劃

  源碼:揹包問題spa

1,介紹

  小灰版動態規劃詳解code

  • 動態規劃(Dynamic Programming)算法的核心思想是:將大問題劃分爲小問題進行解決,從而一步步獲取最優解的處理算法 
  • 動態規劃算法與分治算法相似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,而後從這些子問題的解獲得原問題的解。
  • 與分治法不一樣的是,適合於用動態規劃求解的問題,經分解獲得子問題每每不是互相獨立的。 ( 即下一個子階段的求解是創建在上一個子階段的解的基礎上,進行進一步的求解 )
  • 動態規劃能夠經過填表的方式來逐步推動,獲得最優解

2,揹包問題

  揹包問題主要是指一個給定容量的揹包、若干具備必定價值和重量的物品,如何選擇物品放入揹包使物品的價值最大。其中又分 01 揹包徹底揹包(徹底揹包指的是:每種物品都有無限件可用)htm

3,案例

  揹包問題:有一個揹包,容量爲 4 磅 , 現有以下物品

    

  • 要求達到的目標爲裝入的揹包的總價值最大,而且重量不超出
  • 要求裝入的物品不能重複

4,案例分析與求解

  

  代碼實現

/**
 * 求解01揹包問題
 *
 * @param v 商品的價值
 * @param w 商品的重量(體積)
 * @param c 商品的最大容量
 */
public static void knapsackDim(int[] v, int[] w, int c) {
    //初始化二維數組,行表示商品的體積w 列表示容量從0->c
    int size = w.length;
    int[][] dp = new int[size + 1][c + 1];
    for (int i = 1; i <= size; i++) {
        for (int j = 0; j <= c; j++) {
            //當前商品的體積 大於 容量j 時 直接取上一行的數據
            dp[i][j] = dp[i - 1][j];
            if (w[i-1] <= j) {
                //①dp[i - 1][j - w[i - 1]]爲上一行的當前可用體積-當前商品體積  獲得減去當前商品重量以後的最大價值 + v[i-1]
                //②dp[i][j]實則爲上一行的數據  與①直接比較大小
                dp[i][j] = Math.max(dp[i][j], v[i - 1] + dp[i - 1][j - w[i - 1]]);
            }
        }
    }
}

  優化爲一維數組

/**
 * 揹包問題優化  使用一維數組
 *
 * @param v 商品的價值
 * @param w 商品的重量(體積)
 * @param c 商品的最大容量
 */
public static void knapsackSingle(int[] v, int[] w, int c) {
    int[] dp = new int[c + 1];
    //第一次初始化dp
    for (int i = 0; i < c + 1; i++) {
        dp[i] = w[0] > i ?  0 : v[0];
    }

    for (int i = 1; i < w.length; i++) {
        //防止前面數據被覆蓋,從後往前進行遍歷
        for (int j = c; j >=0; j--) {
            if (w[i] <= j) {
                dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
            }
        }
    }
}

4、KMP算法

1,暴力匹配算法

  源碼:暴力匹配

a)思路

若是用暴力匹配的思路,並假設如今 str1 匹配到 i 位置,子串 str2 匹配到 j 位置,則有:
1) 若是當前字符匹配成功(即 str1[i] == str2[j]),則 i++,j++,繼續匹配下一個字符 
2) 若是失配(即 str1[i]! = str2[j]),令 i = i - (j - 1),j = 0。至關於每次匹配失敗時,i 回溯,j 被置爲 03) 用暴力方法解決的話就會有大量的回溯,每次只移動一位,如果不匹配,移動到下一位接着判斷,浪費了大量 的時間。

b)代碼實現

/**
 * 暴力匹配
 * @param str1  原始字符串
 * @param str2  匹配字符串
 */
public static int violenceMatch(String str1,String str2) {
    //表示字符串str2的匹配的索引位置
    int j;
    for (int i = 0; i < str1.length();) {
        j = 0;
        while (i < str1.length() && j < str2.length() && str1.charAt(i) == str2.charAt(j)) {
            i++;
            j++;
        }
        //將j匹配到最後一個字符
        if (j==str2.length()) {
            return i-j;
        }
        i = i - j + 1;
    }
    return -1;
}

2,KMP算法

  源碼:KMP算法

a)思路

  • 尋找最長前綴後綴「ABCDABD」

         

  •  獲取next數組

         

    • 將next 數組至關於「最大長度值」 總體向右移動一位,而後初始值賦爲-1
    • 若p[k] == p[j],則next[j + 1 ] = next [j] + 1 = k + 1;
    • 若p[k ] ≠ p[j],若是此時p[ next[k] ] == p[j ],則next[ j + 1 ] =  next[k] + 1,不然繼續遞歸前綴索引k = next[k],然後重複此過程。
    • 解釋爲: 以下圖所示,假定給定模式串ABCDABCE,且已知next [j] = k(至關於「p0 pk-1」 = 「pj-k pj-1」 = AB,能夠看出k爲2),現要求next [j + 1]等於多少?由於pk = pj = C,因此next[j + 1] = next[j] + 1 = k + 1(能夠看出next[j + 1] = 3)。表明字符E前的模式串中,有長度k+1 的相同前綴後綴。
    • 但若是pk != pj 呢?說明「p0 pk-1 pk」  ≠ 「pj-k pj-1 pj」。換言之,當pk != pj後,字符E前有多大長度的相同前綴後綴呢?很明顯,由於C不一樣於D,因此ABC 跟 ABD不相同,即字符E前的模式串沒有長度爲k+1的相同前綴後綴,也就不能再簡單的令:next[j + 1] = next[j] + 1 。因此,我們只能去尋找長度更短一點的相同前綴後綴
    • /**
       * 求出一個字符數組的next數組
       *
       * @param p 字符數組
       * @return next數組
       */
      public static int[] getNextArray(char[] p) {
          int[] next = new int[p.length];
          next[0] = -1;
          int k = -1;
          int j = 0;
          while (j < p.length - 1) {
              //p[k]表示前綴 p[j]表示後綴
              if (k == -1 || p[j] == p[k]) {
      //                k++;
      //                j++;
                  next[++j] = ++k;
              } else {
                  k = next[k];
              }
          }
          return next;
      }
  • 基於next數組開始進行匹配
    • P[0]跟S[0]匹配失敗。因此執行「若是j != -1,且當前字符匹配失敗(即S[i] != P[j]),則令 i 不變,j = next[j]」,因此j = -1,故轉而執行「若是j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++」,獲得i = 1,j = 0,即P[0]繼續跟S[1]匹配。
    • P[0]跟S[1]又失配,j再次等於-1,i、j繼續自增,從而P[0]跟S[2]匹配。
    • 直到P[0]跟S[4]匹配成功,開始執行此條指令的後半段:「若是j = -1,或者當前字符匹配成功(即S[i] == P[j]),都令i++,j++」。
    • P[1]跟S[5]匹配成功,P[2]跟S[6]也匹配成功, ...,直到當匹配到P[6]處的字符D時失配(即S[10] != P[6]),因爲P[6]處的D對應的next 值爲2,因此下一步用P[2]處的字符C繼續跟S[10]匹配,至關於向右移動:j - next[j] = 6 - 2 =4 位。
    • 向右移動4位後,P[2]處的C再次失配,因爲C對應的next值爲0,因此下一步用P[0]處的字符繼續跟S[10]匹配,至關於向右移動:j - next[j] = 2 - 0 = 2 位。
    •  移動兩位以後,A 跟空格不匹配,模式串後移1 位。

    • P[6]處的D再次失配,由於P[6]對應的next值爲2,故下一步用P[2]字符C繼續跟文本串匹配,至關於模式串向右移動 j - next[j] = 6 - 2 = 4 位。
    • 匹配成功,過程結束。

    • 匹配過程如出一轍。也從側面佐證了,next 數組確實是只要將各個最大前綴後綴的公共元素的長度值右移一位,且把初值賦爲-1 便可。

    • /**
       * 對主串s和模式串t進行KMP模式匹配
       *
       * @param s 主串
       * @param t 模式串
       * @return 若匹配成功,返回t在s中的位置(第一個相同字符對應的位置),若匹配失敗,返回-1
       */
      public static int kmpMatch(String s, String t) {
          char[] s_arr = s.toCharArray();
          char[] t_arr = t.toCharArray();
          int[] next = getNextArray(t_arr);
          int i = 0, j = 0;
          while (i < s_arr.length && j < t_arr.length) {
              if (j == -1 || s_arr[i] == t_arr[j]) {
                  i++;
                  j++;
              } else
                  j = next[j];
          }
          if (j == t_arr.length)
              return i - j;
          else
              return -1;
      }

5、貪心算法

1,應用場景

  假設存在下面須要付費的廣播臺,以及廣播臺信號能夠覆蓋的地區。 如何選擇最少的廣播臺,讓全部的地區均可以接收到信號。

  

2,貪心算法介紹

  • 貪婪算法(貪心算法)是指在對問題進行求解時,在每一步選擇中都採起最好或者最優(即最有利)的選擇,從而但願可以致使結果是最好或者最優的算法
  • 貪婪算法所獲得的結果不必定是最優的結果(有時候會是最優解),可是都是相對近似(接近)最優解的結果

3,問題求解

a)思路分析

  • 使用窮舉法實現,列出每一個可能的廣播臺的集合,這被稱爲冪集。假設總的有 n 個廣播臺,則廣播臺的組合總共有2ⁿ -1
  • 使用貪婪算法,效率高
    • 遍歷全部的廣播電臺,找到一個覆蓋了最多未覆蓋的地區的電臺(採用retainAll方法,將當前集合與選擇集合的交集賦值給當前集合)
    • 將這個電臺加入到集合中,去除該電臺覆蓋的地區
    • 重複以上,直至覆蓋全部的地區

b)代碼實現

public static void main(String[] args) {
    Map<String, Set<String>> map = new HashMap<>();
    Set<String> set1 = new HashSet<>();
    set1.add("北京");
    set1.add("上海");
    set1.add("天津");

    Set<String> set2 = new HashSet<>();
    set2.add("廣州");
    set2.add("北京");
    set2.add("深圳");

    Set<String> set3 = new HashSet<>();
    set3.add("成都");
    set3.add("上海");
    set3.add("杭州");

    Set<String> set4 = new HashSet<>();
    set4.add("上海");
    set4.add("天津");

    Set<String> set5 = new HashSet<>();
    set5.add("杭州");
    set5.add("大連");

    map.put("K1", set1);
    map.put("K2", set2);
    map.put("K3", set3);
    map.put("K4", set4);
    map.put("K5", set5);

    Set<String> allAreas = new HashSet<>();
    allAreas.addAll(set1);
    allAreas.addAll(set2);
    allAreas.addAll(set3);
    allAreas.addAll(set4);
    allAreas.addAll(set5);

    //存儲選擇的key
    List<String> selects = new ArrayList<>();

    //定義此時最大的key
    String maxKey;
    //臨時存儲的set集合
    Set<String> tempSet = new HashSet<>();
    //若是allArea不爲空則一直刪除
    while (allAreas.size() != 0) {
        //清空臨時set
        tempSet.clear();
//            maxSize = 0;
        maxKey = null;
        for (Map.Entry<String, Set<String>> entry : map.entrySet()) {
            tempSet = entry.getValue();
            tempSet.retainAll(allAreas);
            if (tempSet.size() > 0 && (maxKey == null || tempSet.size() > map.get(maxKey).size())) {
                maxKey = entry.getKey();
            }
        }
        if (maxKey != null) {
            tempSet = map.get(maxKey);
            selects.add(maxKey);
            allAreas.removeAll(tempSet);
            //此時能夠將對應的key去除,這樣能在遍歷map的時候提升效率
            map.remove(maxKey);
        }
    }

    System.out.println(selects);
}
相關文章
相關標籤/搜索