Algorithms and Data Structures - Common Algorithms

1. Binary search algorithm (non-recursive)

  1. We talked about the binary search algorithm earlier, which uses a recursive method. Next, we will explain the non-recursive method of the binary search algorithm.
  2. The binary search method is only suitable for searching from ordered arrays (such as numbers and letters, etc.), sorting the arrays and then searching
  3. Binary search runs in logarithmic time O ( log 2 n ) O(log_2n)O(log2n ) , that is, it only needs log 2 n log_2nat most to find the required target positionlog2n steps, assuming that the target number 30 is found from the queue of [0,99] (100 numbers, ie n=100), the number of search steps islog 2 100 log_2100log21 0 0 , that is, it needs to search up to 7 times (26<100<27)
public class BinarySearchNoRecur {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    0,1,3,5,7,9};
        int i = binarySearch(arr, -2);
        System.out.println(i);
    }

    public static int binarySearch(int[] arr, int target) {
    
    
        int min = 0;
        int max = arr.length - 1;
        int mid ;

        while (min <= max) {
    
    
            mid = (min + max) / 2;
            if (arr[mid] == target) {
    
    
                return mid;
            } else if (arr[mid] > target) {
    
    
                max = mid - 1;
            } else {
    
    
                min = mid + 1;
            }
        }
        return -1;
    }
}

2. Divide and conquer algorithm

1 Introduction

  1. Divide and conquer is a very important algorithm. The literal explanation is "divide and conquer", which is to divide a complex problem into two or more identical or similar sub-problems, and then divide the sub-problems into smaller sub-problems... until finally the sub-problems can be simple Direct solution, the solution of the original problem is the combination of the solutions of the sub-problems.

    This technique is the basis of many efficient algorithms, such as sorting algorithms (quick sort, merge sort), Fourier transform (fast Fourier transform)...

  2. Some classic problems that can be solved by divide and conquer algorithm

    • binary search
    • Multiplication of large integers
    • checkerboard overlay
    • merge sort
    • quick sort
    • linear time selection
    • closest point pair problem
    • Round Robin Schedule
    • Tower of Hanoi

2. Basic steps

  1. break down

    Decompose the original problem into several smaller, independent sub-problems with the same form as the original problem

  2. solve

    If the sub-problems are small and easy to solve, solve them directly; otherwise, solve each sub-problem recursively

  3. merge

    Combine the solutions of each sub-problem into the solution of the original problem.


3. Design Patterns

The Divide-and-Conquer§ algorithm design pattern is as follows:

if |p| <= n0
  then return (ADHOC(P))
// 将p分解为较小的子问题 p1,p2,...,pk
for i <- 1 to k
do yi <- Divide-and-Conquer(Pi) 递归解决Pi
T <- MERGE(y1,y2,...,yk) 合并子问题
return (T);

Where |P| represents the scale of the problem P; n0 is a threshold, which means that when the scale of the problem P does not exceed n0, the problem is easy to solve directly, and there is no need to continue to decompose.

ADHOC§ is the basic sub-algorithm in the divide-and-conquer method, which is used to directly solve the small-scale problem P.

Therefore, when the scale of P does not exceed n0, the algorithm ADHOC§ is directly used to solve it.

Algorithm MERGE(y1,y2...yk) is a merging sub-algorithm in the divide-and-conquer method, which is used to merge the corresponding solutions y1,y2,...,yk of P's sub-problems P1,P2.Pk into P's solution.


4. The Tower of Hanoi problem

The Legend of the Tower of Hanoi

Tower of Hanoi: The Tower of Hanoi (also known as the Tower of Hanoi) is an educational toy that originated from an ancient legend in India. When Brahma created the world, he made three diamond pillars. On one pillar, 64 gold discs were stacked in order of size from bottom to top.

Brahma ordered Brahmin to rearrange the discs on another pillar in order of size from below. And it is stipulated that the disk cannot be enlarged on the small disk, and only one disk can be moved between the three pillars at a time.

How long would it take if it was done once every second? It would take more than 584.554 billion years to remove these gold pieces, and the expected lifespan of the solar system is said to be tens of billions of years. After 584.554 billion years, all life on earth, together with Vatican towers, temples, etc., have long been wiped out.

step

  1. If there is a disk, A->C
  2. If we have n>=2 case, we can always see it as two discs
    1. bottom plate
    2. top plate
  3. Put the top disk A->B
  4. Put the bottom disk A->C
  5. Move all disks of Tower B from B->C
public class Hanoitower {
    
    
    public static void main(String[] args) {
    
    
        hanoitower(5, 'A', 'B', 'C');
    }

    public static void hanoitower(int num, char a, char b, char c) {
    
    
        // 如果只有一个盘
        if (num == 1) {
    
    
            System.out.println("第1个盘 " + a + " -> " + c);
        } else {
    
    
            // n >= 2 ,可以总是看做有两个盘,最下面一个和上面所有的
            // 1. 把上面的所有盘 A -> B, 过程中使用C
            hanoitower(num - 1, a, c, b);
            // 2. 最下面的盘 A -> C
            System.out.println("第" + num + "个盘从" + a + " -> " + c);
            // 3. 把B的所有盘 B -> C ,移动中国使用到A
            hanoitower(num - 1, b, a, c);
        }
    }
}

3. Dynamic programming

1 Introduction

  1. The core idea of ​​the Dynamic Programming algorithm is:

    A processing algorithm that divides large problems into small ones and solves them to obtain the optimal solution step by step

  2. The dynamic programming algorithm is similar to the divide-and-conquer algorithm. Its basic idea is to decompose the problem to be solved into several sub-problems, first solve the sub-problems, and then obtain the solution of the original problem from the solutions of these sub-problems.

  3. Different from the divide and conquer method, it is suitable for the problem solved by dynamic programming, and the sub-problems obtained through decomposition are often not independent of each other. (That is, the solution of the next sub-stage is based on the solution of the previous sub-stage for further solution)

  4. Dynamic programming can be gradually advanced by filling in the table to get the optimal solution.

2. The knapsack problem

There is a backpack with a capacity of 4 pounds and the following items:

thing weight price
Guitar (G) 1 1500
Audio (S) 4 3000
computer (L) 3 2000
  1. The goal required to be achieved is that the total value of the loaded knapsack is the largest, and the weight does not exceed
  2. Items required to be loaded cannot be duplicated

3. Idea

  • The knapsack problem mainly refers to a knapsack with a given capacity, if there are items with a certain value and weight, how to choose items to put in the knapsack to maximize the value of the items. Among them, it is divided into 01 backpack and complete backpack (complete backpack refers to: each item has unlimited pieces available)
  • The problem here belongs to the 01 backpack, that is, each item can be placed at most one. And the infinite backpack can be transformed into 01 backpack.

The main idea of ​​the algorithm is to use dynamic programming to solve it. For the i-th item traversed each time, determine whether the item needs to be put into the backpack according to w[i] and v[i]. That is, for a given n items, let v[i] and w[i] be the value and weight of the i-th item respectively, and c be the capacity of the backpack. Let v[i][j] represent the maximum value that can be loaded into a knapsack with a capacity of j among the first i items. Then we have the following result:

  • v[i][0]=v[0][i]=0; // Indicates that the first row and the first column of the filled table are 0

  • When w[i]>j时::v[i][j]=v[i-1][j] //When the capacity of the new product is greater than the current backpack capacity, directly use the result of the previous cell

  • When j>=w[i]:v[i][j]=max{ v[i-1][j], v[i-1][j-w[i]] + v[i]}

    When the capacity of the newly added product to be added is less than or equal to the capacity of the current backpack,

    How to load:

    v[i-1][j]: is the maximum value loaded in the previous cell

    v[i]: Indicates the value of the current commodity

4. Backpack problem solved

thing 0 pounds 1 pound 2 pounds 3 pounds 4 pounds
0 0 0 0 0
Guitar 0 1500 1500 1500 1500
Audio 0 1500 1500 1500 3000
computer 0 1500 1500 2000 3500
  1. If there is only a guitar now, no matter how big the capacity of the backpack is, only one guitar can be placed

  2. If there is a guitar and stereo,

    Validation formula:

    v[1][1] = 1500

    1. i = 1,j = 1

    2. w[i] =w[1] = 1

      v[i][j] = max{v[i-1][j], v[i]+v[i-1][j-w[i]]} = v[1][1] = max {0 ,1500 + 0} = 1500 ]

    v[3][4]

    1. i = 3 ,j = 4
    2. w[3] = 3
    3. v[3][4] = max{3000 ,2000+1500} = 3500

5. Code

package B_常用算法.B03_动态规划;

public class Page1 {
    
    

    public static void main(String[] args) {
    
    
        int[] w = {
    
    1, 4, 3}; //物品重量
        int[] val = {
    
    1500, 3000, 2000}; // 物品的价值
        int m = 4;// 背包容量
        int n = val.length; // 物品的个数

        // 创建二维数组
        // v[i][j] =容量范围内最大价值
        int[][] v = new int[n + 1][m + 1];

        // 记录商品情况
        int[][] path = new int[n + 1][m + 1];

        // 初始化第一行和第一列
        for (int i = 0; i < v.length; i++) {
    
    
            v[i][0] = 0;
        }
        for (int i = 0; i < v[0].length; i++) {
    
    
            v[0][i] = 0;
        }

        // 动态规划处理

        for (int i = 1; i < v.length; i++) {
    
    
            for (int j = 1; j < v[0].length; j++) {
    
    
                // 套用公式
                if (w[i - 1] > j) {
    
     //
                    v[i][j] = v[i - 1][j];
                } else {
    
    
                    // yi因为我们的i从1开始的,所以公式需要进行调整
//                    v[i][j] = Math.max(v[i - 1][j], val[i-1] + v[i - 1][j - w[i - 1]]);

                    // 为了记录商品存放到背包的情况,我们不能简单的使用公式
                    if (v[i - 1][j] > val[i - 1] + v[i - 1][j - w[i - 1]]) {
    
    
                        v[i][j] = v[i - 1][j];
                    } else {
    
    
                        v[i][j] = val[i - 1] + v[i - 1][j - w[i - 1]];
                        path[i][j] = 1;
                    }

                }
            }
        }

        // 输出
        for (int i = 0; i < v.length; i++) {
    
    
            for (int j = 0; j < v[i].length; j++) {
    
    
                System.out.println(v[i][j] + " ");
            }
            System.out.println();
        }

        int i = path.length - 1;
        int j = path[0].length - 1;
        System.out.println(path[i][j]);
        while (i > 0 && j > 0) {
    
     // 从后往前遍历
            if (path[i][j] == 1) {
    
    
                System.out.printf("第%d个商品放入到背包\n", i);
                j = w[i - 1];
                i--;
            }
        }

    }


}


4. KMP Algorithm

1. Application scenarios

String matching problem:

  1. There is a string str1 = ""Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley You, Silicon Valley Hello", and a substring str2 = "Silicon Valley, Silicon Valley, Silicon Valley, Silicon Valley, Silicon You"
  2. Now it is necessary to judge whether str1 contains str2, if it exists, return the position of the first occurrence, if not, return -1

2. Violent matching algorithm

If you use the idea of ​​brute force matching, and assume that now str1 selects the position of package i, and the substring str2 is less matched to position j, then there are:

  1. If the current character matches successfully (ie str1[i] == str2[ij]), then it+, j++, continue to match the next character
  2. If there is a mismatch (ie str1[i]! = str2[j]), let i= i-(j-1), j=0. It is equivalent to every time the matching fails, i backtracks and j is set to 0.
  3. If you use a violent method to solve it, there will be a lot of backtracking, and you will only move one bit at a time. If it does not match, move to the next bit and then judge, wasting a lot of time. (not feasible!)
  4. Realization of brute force matching algorithm.
public class 暴力匹配 {
    
    
    public static void main(String[] args) {
    
    
        String str1 = "硅硅谷尚硅谷你尚硅尚硅谷你尚硅谷你尚硅你好";
        String str2 = "尚硅谷你尚硅你";
        System.out.println(violenceMatch(str1, str2));
        System.out.println(str1.indexOf(str2));
    }

    public static int violenceMatch(String str1, String str2) {
    
    
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();

        int s1Len = s1.length;
        int s2Len = s2.length;

        int i = 0; // i 索引指向s1
        int j = 0; // j 索引指向s2
        while (i < s1Len && j < s2Len) {
    
    
            if (s1[i] == s2[j]) {
    
     // 匹配成功
                i++;
                j++;
            } else {
    
    

                i = i - (j - 1);
                j = 0;
            }
        }

        // 判断是否匹配成功
        if (j == s2Len) {
    
    
            return i - j;
        } else {
    
    
            return -1;
        }
    }
}

3. KMP algorithm

Just read this blog directly, I don’t want to write here,

The blog KMP algorithm
is more recommended to read this
KMP algorithm detailed explanation

1 Introduction

  1. KMP is a classic algorithm to solve whether the pattern string has appeared in the text string, and if so, the earliest position
  2. The Knuth-Morris-Pratt string search algorithm, referred to as "KMP algorithm", is often used to find the occurrence position of a pattern string P in a text string S. This algorithm was developed by Donald Knuth, Vaughan Pratt, and James H. Morris. It was jointly published in 1977, so the algorithm was named after the surnames of these three people.
  3. The KMP method algorithm uses the previously judged information, and saves the length of the longest common subsequence in the pattern string through a next array. When backtracking each time, it finds the previously matched position through the next array, saving a lot of calculations time
  4. References: https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html

2) Application

String matching problem::

  1. There is a string str1 = "BBCABCDAB ABCDABCDABDE", and a substring str2 = "ABCDABD"
  2. Now it is necessary to judge whether str1 contains str2, if it exists, return the position of the first occurrence, if not, return -1
  3. Requirements: use the KMP algorithm to complete the judgment, and cannot use simple brute force matching algorithms.

3) Code

package B_常用算法.B04_KMP;
/*
    Date:2022/5/20
    author: Blue Friday
    describe: //todo
*/

public class KMP_test {
    
    
    public static void main(String[] args) {
    
    
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        System.out.println(kmpSearch(str1, str2, kmpNext(str2)));
        System.out.println(str1.indexOf(str2));

        KMP kmp = new KMP(str2);
        System.out.println(kmp.search(str1));


    }

    // 获取字符串部分匹配值
    public static int[] kmpNext(String dest) {
    
    
        // 创建next数组保存部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0; // 如果字符串长度为1,这部分匹配值永远是0;
        for (int i = 1, j = 0; i < dest.length(); i++) {
    
    

            // 从next[j-1]获取新的j
            // 直到 发现上面条件满足时退出
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
    
    
                j = next[j - 1];
            }
            if (dest.charAt(i) == dest.charAt(j)) {
    
     // 满足时,部分匹配值就是 +1;
                j++;
            }
            next[i] = j;
        }
        return next;
    }

    // kmp 搜索算法
    public static int kmpSearch(String str1, String str2, int[] next) {
    
    
        // 遍历 str1
        for (int i = 0, j = 0; i < str1.length(); i++) {
    
    
            // kmp算法核心 调整 j 的大小
            while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
    
    
                j = next[j - 1];
            }

            if (str1.charAt(i) == str2.charAt(j)) {
    
    
                j++;
            }
            if (j == str2.length()) {
    
    
                return i - j + 1;
            }
        }
        return -1;

    }
}

4) Zhihu version

public class KMP {
    
    
    private int[][] dp;
    private String pat;

    public KMP(String pat) {
    
    
        this.pat = pat;
        int M = pat.length();
        // dp[状态][字符] = 下一个状态
        dp = new int[M][256];
        dp[0][pat.charAt(0)] = 1;
        // 影子状态 x 初始为 0
        int x = 0;
        // 当前状态 j 从 1开始
        for (int j = 1; j < M; j++) {
    
    
            for (int c = 0; c < 256; c++) {
    
    
                if (pat.charAt(j) == c) {
    
    
                    dp[j][c] = j + 1;
                } else {
    
    
                    dp[j][c] = dp[x][c];
                }
            }
            // 更新影子状态
            x = dp[x][pat.charAt(j)];
        }
        System.out.println(Arrays.deepToString(dp));


    }

    public int search(String txt) {
    
    
        int M = pat.length();
        int N = txt.length();
        // pat 的初始状态为 0
        int j = 0;
        for (int i = 0; i < N; i++) {
    
    
            // 当前状态 j ,遇到 txt[i],变化状态
            j = dp[j][txt.charAt(i)];
            // 达到终止状态,返回开头匹配的索引
            if (j == M) return i - M + 1;
        }
        return -1;
    }
}

Guess you like

Origin blog.csdn.net/m0_56186460/article/details/124875409