Common algorithms for data structures

Divide and conquer algorithm

Introduction to divide and conquer algorithm
  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 subproblems, and then divide the subproblem into smaller subproblems... Until finally, the subproblem can be simply solved directly, and the solution of the original problem is the combination of the solutions of the subproblems.
  2. Some classical problems that divide and conquer algorithm can solve: binary search, large integer multiplication, chessboard coverage, merge sort, quick sort, linear time selection, nearest point pair, round robin schedule, Hanoi tower.
Basic steps of divide and conquer algorithm
  1. Decomposition: decompose the original problem into several small-scale, independent sub problems with the same form as the original problem.
  2. Solution: if the subproblem is small and the container is solved, it can be solved directly, otherwise each subproblem can be solved recursively.
  3. Merge: merge the solutions of each sub problem into the solutions of the original problem.
Hanoi Tower code implementation
public class HanoiTower {

    public static void main(String[] args) {

        hanotower(5, 'A', 'B', 'C');
    }

    public static void hanotower(int n, char a, char b,  char c) {

        if (n == 1) {
            System.out.printf("Move, from%c reach%c\n", a, c);
        } else {
            // When it is larger than two disks, we can regard it as the movement of two disks
            // 1. The bottom disk is used as a disk
            // 2. The other disk among all disks except the lowest disk
            // First move the upper disk to b from a and use c in the middle
            hanotower(n - 1, a, c, b);
            // Then move the other disk from a to c
            System.out.println("The first" + n + "Disk from" + a + "->" + c);
            // Move all disks of Tower b from b to c, and use a in the middle movement process
            hanotower(n - 1, b, a, c);
        }
    }
}

dynamic programming algorithm

Algorithm Introduction
  1. The core idea of dynamic programming algorithm is to divide the large problem into small problems for solution, so as to further obtain the optimal solution.
  2. Dynamic programming algorithm is similar to divide and conquer algorithm. Its basic idea is to decompose the problem to be solved into several subproblems. First solve the subproblems, and then get the solution of the original problem from the solutions of these subproblems.
  3. Different from the divide and conquer algorithm, the sub problems obtained by 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 advanced step by step by filling in tables to obtain the optimal solution.
0-1 knapsack problem

Title: http://acm.hdu.edu.cn/showproblem.php?pid=2602

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        int t ;
        Scanner scanner = new Scanner(System.in);
        t = scanner.nextInt();
        while (t-->0) {
            int n, v;
            n = scanner.nextInt();
            v = scanner.nextInt();
            int value[] = new int[1001];
            int volumn[] = new int[1001];
            int pack[][] = new int[1001][1001];
            for (int i = 1; i<=n; ++i){
                value[i] = scanner.nextInt();
            }
            for (int i = 1; i <=n; ++i) {
                volumn[i] = scanner.nextInt();
            }

            for (int i = 1; i <=n; ++i){
                for (int j = 0; j <=v; ++j) {
                    if (volumn[i] > j) {
                        pack[i][j] = pack[i-1][j];
                    } else {
                        pack[i][j] = Math.max(pack[i-1][j], pack[i-1][j-volumn[i]] + value[i]);
                    }
                }
            }
            System.out.println(pack[n][v]);
        }
    }

}

KMP algorithm

reference resources

Algorithm Introduction
  1. KMP is a solution to whether the pattern string appears in the text string. If so, the earliest position of the classical algorithm.
  2. Knuth Morris Pratt string lookup algorithm, referred to as KMP algorithm. It is often used to find the occurrence position of a pattern string P in a text string S. This algorithm was jointly published by Donald Knuth, Vaughant Pratt and James H. Morris in 1977, so it was named after the surnames of these three people.
  3. KMP algorithm uses the previously judged information to maintain the length of the longest common subsequence in the pattern string through a next array. Each time, it finds the previously matched position through the next array, saving a lot of computing time.

Explain the algorithm in detail. Look video

HDU 2087 Problem solution
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String str1 = scanner.next();
            if ("#".equals(str1)) {
                break;
            }
            String str2 = scanner.next();

            kmpSearch(str1, str2);
        }
    }

    public static void kmpSearch(String text, String pattern) {

        int ans = 0;
        int[] ints = kmpNext(pattern);
        moveNext(ints);

        int m = text.length();
        int n = pattern.length();
        int i = 0, j = 0;
        while (i < m) {
            if(j == n - 1 && text.charAt(i) == pattern.charAt(j)) {
                ans ++;
                j = 0;
                i++;
                continue;
            }
            if (text.charAt(i) == pattern.charAt(j)) {
                i ++;
                j ++;
            } else {
                j = ints[j];
                if (j == -1) {
                    i ++;
                    j ++;
                }
            }
        }

        System.out.println(ans);
    }

    public static void moveNext(int[] ints) {
        for (int i = ints.length-1; i > 0; --i) {
            ints[i] = ints[i-1];
        }
        ints[0] = -1;
    }

    // Get the partial matching value table of a string
    public static int[] kmpNext(String dest) {

        int[] next = new int[dest.length()];
        next[0] = 0; // If the string length is 1, the matching value is 0
        int len = 0;
        int i = 1;
        while (i < dest.length()) {
            if (dest.charAt(i) == dest.charAt(len)) {
                len ++;
                next[i] = len;
                i++;
            } else {
                if (len >0) {
                    len = next[len - 1];
                } else {
                    next[i] = len;
                    i++;
                }
            }
        }
        return next;
    }
}

Greedy Algorithm

Greedy algorithm, also known as greedy algorithm, is an algorithm that takes the best or optimal (i.e. the most favorable) choice in the current state in each step of selection, so as to lead to the best or optimal result. Greedy algorithm is especially effective in problems with optimal substructure. The optimal sub result means that the local optimal solution can determine the global optimal solution. In short, the problem can be decomposed into subproblems, and the optimal solution of the subproblem can be recursive to the optimal solution of the final problem. The difference between greedy algorithm and dynamic programming is that it makes a choice for the solution of each subproblem and cannot go back. Dynamic planning will save the previous operation results and select the current according to the previous results. It has the function of fallback.

HDU 5747 Problem solution
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        int T;
        Scanner scanner = new Scanner(System.in);
        T = scanner.nextInt();
        while (T-->0) {
            int n, m;
            n = scanner.nextInt();
            m = scanner.nextInt();
            int key = 1;

            boolean flag = false;
            for (int i = 0; i <=m; ++i) {
                int mul = (int)Math.pow(2, i);
                if (mul > n) {
                    flag = true;
                    key = i;
                    break;
                }
            }
            if (!flag) {
                key = m+1;
            }
            int sum = 0;
            for (int i = key-1; i>=0; --i) {
                int v = (int)Math.pow(2, i);
                int j = 1;
                if (j * v > n) {
                    continue;
                }
                while (j*v <= n) {
                    j++;
                }
                j--;
                sum += j;
                n = n - j * v;
                if (n<=0){
                    break;
                }
            }
            System.out.println(sum);
        }
    }
}

Prim algorithm

Prim algorithm, an algorithm in graph theory, can search the minimum spanning tree in the weighted connected graph. That is, the tree composed of a subset of edges searched by this algorithm not only includes all vertices in the connected graph, but also the sum of the weights of all edges is the smallest. The algorithm was discovered by the Czech mathematician voytech yarnik in 1930; It was independently discovered by American computer scientist Robert prim in 1957; In 1959, ezger dikoscher discovered the algorithm again. Therefore, in some cases, PRIM algorithm is also called DJP algorithm, yarnick algorithm or prim yarnick algorithm

The minimum spanning tree is a spanning tree with the smallest weight in a pair of weighted undirected connected graphs. The minimum spanning tree includes n and n-1 edges.

Algorithm Introduction
  1. Prim algorithm finds the minimum spanning tree, that is, in the connected graph containing n vertices, find the associated subgraph with only (n-1) edges containing all n vertices, that is, the so-called minimal connected subgraph.
  2. Prim's algorithm is as follows:
    1. Let G=(V,E) be a connected network, T=(U,D) be a minimum spanning tree, V,U be a set of vertices, and E,D be a set of edges.
    2. If the minimum spanning tree is constructed from vertex u, take the vertex u from set V and put it into set u, and mark the visited[u]=1 of vertex v, indicating that it has been accessed.
    3. If there are edges between vertex ui in set u and vertex vj in set V-U, find the edge with the smallest weight among these edges, but it cannot form a loop. Add vertex vj to set u, add edge (ui,vj) to set D, and mark visited[vj]=1.
    4. Repeat step 2 until U and V are equal, that is, all vertices are marked as visited, and there are n-1 edges in D.
HDU 1301 Problem solution
public class Main {

    private static int[][] map;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            int n = scanner.nextInt();
            if (n <=0) {
                break;
            }

            map = new int[n][n];
            initMap(map, n, n);

            for (int i = 0; i < n-1; ++i) {
                String start = scanner.next();
                int roads = scanner.nextInt();
                for (int j = 1; j <= roads; j++) {
                    String end = scanner.next();
                    int value = scanner.nextInt();
                    map[start.charAt(0) - 'A'][end.charAt(0) - 'A'] = value;
                    map[end.charAt(0) - 'A'][start.charAt(0) - 'A'] = value;
                }
            }
            prim(map, 0);
            //print(map, n, n);
        }
    }

    public static void prim(int[][] map, int v) {
        boolean visited[] = new boolean[map.length];

        int sum = 0;
        int h2 = -1;
        visited[v] = true;
        for (int k = 1; k < map.length; ++k) { // You need to find n-1 edges altogether, so this is n-1 cycles
            int minWeight = Integer.MAX_VALUE;
            for (int i = 0; i < map.length; i++) { //Visited nodes
                for (int j = 0; j < map.length; j++) { // Nodes that have not been accessed
                    if(visited[i] && !visited[j] && map[i][j] < minWeight && map[i][j] != -1) {
                        minWeight = map[i][j];
                        h2 = j;
                    }
                }
            }
            sum += minWeight;
            visited[h2] = true;
        }
        System.out.println(sum);
    }

    public static void initMap(int[][]map, int row, int col) {
        for (int i = 0; i < row; ++i) {
            for (int j = 0; j < col; ++j) {
                map[i][j] = -1;
            }
        }
    }
    public static void print(int[][] map, int row, int col) {
        for (int i = 0; i < row; ++i) {
            for (int j = 0; j < col; ++j) {
                System.out.printf(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}

Kruskal algorithm

Algorithm Introduction
  1. Kruskal algorithm is used to find the minimum spanning tree of weighted connected graph.
  2. Basic idea: select n-1 edges in the order of weight from small to large, and ensure that these n-1 edges do not form a loop.
  3. Specific methods: firstly, construct a forest with only n vertices, and then select edges from the connected network to join the forest according to the weight from small to large, so that there is no loop in the forest until the forest becomes a tree.

Detailed analysis of the algorithm, you can see video

HDU 1301 Problem solution
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Hdu1301Krusal {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            int vertextNum = scanner.nextInt();
            if (vertextNum <= 0) {
                break;
            }

            int[] parent = new int[vertextNum];
            EdgeGraph edgeGraph = new EdgeGraph();
            edgeGraph.vertextNum = vertextNum;
            List<EdgeType> edges = new ArrayList<>();
            for (int i = 0; i < vertextNum-1; ++i) {
                String start = scanner.next();
                int roads = scanner.nextInt();
                for (int j = 1; j <= roads; j++) {
                    String end = scanner.next();
                    int value = scanner.nextInt();
                    EdgeType edgeType = new EdgeType();
                    edgeType.from = start.charAt(0) - 'A';
                    edgeType.to = end.charAt(0) - 'A';
                    edgeType.weight = value;
                    edges.add(edgeType);
                }
            }
            Collections.sort(edges);
            edgeGraph.edgeNum = edges.size();
            edgeGraph.edge = edges;
            kruskal(edgeGraph, parent);
        }
    }

    public static void kruskal(EdgeGraph edgeGraph, int[] parent) {
        for (int i = 0; i < edgeGraph.vertextNum; ++i) {
            parent[i] = -1;
        }
        int sum = 0;
        for (int num = 0, i = 0; i < edgeGraph.edgeNum; ++i) {
            int vex1 = findRoot(parent, edgeGraph.edge.get(i).from);
            int vex2 = findRoot(parent, edgeGraph.edge.get(i).to);
            if (vex1 != vex2) {
                parent[vex2] = vex1;
                num ++;
                sum += edgeGraph.edge.get(i).weight;
                if (num == edgeGraph.vertextNum - 1) {
                    System.out.println(sum);
                    return;
                }
            }
        }
    }

    public static int findRoot(int parent[], int v) {
        int t = v;
        while (parent[t] > -1) {
            t = parent[t];
        }
        return t;
    }
}

class EdgeType implements Comparable<EdgeType>{
    int from;
    int to;
    int weight;

    @Override
    public int compareTo(EdgeType o) {
        return this.weight - o.weight ;
    }
}

class EdgeGraph {

    int vertextNum; // Number of vertices of graph
    int edgeNum;    // Number of edges of graph
    List<EdgeType> edge; // An array of edges
}

Dijestra algorithm (single source shortest path)

Can watch video , learning algorithm

Algorithm Introduction

Dijestra algorithm is a typical shortest path algorithm, which is used to calculate the shortest path from one node to other nodes. Its main feature is to take the starting point as the center and expand outward layer by layer (breadth first search idea) until it extends to the end point.

Algorithm process

Set the starting vertex as V, the vertex set V{v1,v2,v3,...}, and the distance from V to each vertex in V constitutes the distance set dis. Dis{d1,d2,d3,...}, the dis set records the distance from V to each vertex in the graph (to itself can be regarded as 0, and the distance from V to vi corresponds to di)

  1. Select di with the smallest value from Dis and move out of Dis set, and move out the corresponding vertex vi in V set. At this time, V to vi is the shortest path.
  2. Update the Dis set. The update rule is: compare the distance value from V to the vertex in the V set with the distance value from V through vi to the vertex in the V set, and keep the smaller value (at the same time, the precursor node of the vertex should also be updated to vi, indicating that it arrived through vi)
  3. Repeat the two steps until the shortest path vertex is the target vertex.
HDU 2112 Problem solution

Remind that the start point and end point also need to be added to the diagram, because the start point and end point may not appear in the following path. With this in mind, the topic passed very well.

import java.util.*;

public class Main {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        while (true) {
            int T = scanner.nextInt();
            if (T == -1) {
                break;
            }

            Set<String> set = new HashSet<>();

            String start = scanner.next();
            String end = scanner.next();

            set.add(start);
            set.add(end);

            List<Edge> list = new ArrayList<>();
            for (int i = 0; i < T; ++i) {
                Edge edge = new Edge();
                edge.from = scanner.next();
                edge.to = scanner.next();
                edge.weight = scanner.nextInt();
                list.add(edge);
                set.add(edge.from);
                set.add(edge.to);
            }
            Map<String, Integer> map = new HashMap<>();
            int i = 0;
            for(String str : set) {
                map.put(str, i++);
            }
            int startNum = map.get(start);
            int endNum = map.get(end);

            int[][] adjMatrix = new int[set.size()][set.size()];
            init(adjMatrix);
            for (i = 0; i < list.size(); ++i) {
                Edge edge = list.get(i);
                int from = map.get(edge.from);
                int to = map.get(edge.to);
                adjMatrix[from][to] = edge.weight;
                adjMatrix[to][from] = edge.weight;
            }

            if (start.equals(end)) {
                System.out.println("0");
                continue;
            }
            dijkstra(adjMatrix, startNum, endNum, set.size());
        }
    }

    private static void dijkstra(int[][] adjMatrix, int startNum, int endNum, int size) {
        int[] S = new int[size];
        int[] U = new int[size];
        long[] dis = new long[size];
        int[] path = new int[size];
        S[startNum] = 1;
        for (int i =0; i < size; ++i) {
            if (i != startNum) {
                U[i] = 1;
            }
            dis[i] = adjMatrix[startNum][i];
            if (dis[i] == Integer.MAX_VALUE) {
                path[i] = -1;
            } else {
                path[i] = startNum;
            }
        }
        int num = 1;
        while (num < size) {
            int min = findMinDist(dis, S, size);
            S[min] = 1; // Add the newly generated vertices to the set S
            U[min] = 0; // Removes the newly generated vertices from the U collection
            for(int i = 0; i < size; ++i) {
                if (S[i] == 0 && (dis[i] > dis[min] + adjMatrix[min][i])) {
                    dis[i] = dis[min] + adjMatrix[min][i];
                    path[i] = min;
                }
            }
            num ++;
        }

        if (dis[endNum] == Integer.MAX_VALUE) {
            System.out.println("-1");
        } else {
            System.out.println(dis[endNum]);
        }
    }

    private static int findMinDist(long[] dis, int[] s, int size) {
        long min = Integer.MAX_VALUE;
        int k=0;
        for (int i = 0; i < size; ++i) {
            if (min > dis[i] && s[i] == 0) {
                min = dis[i];
                k = i;
            }
        }
        return k;
    }

    private static void init(int[][] adjMatrix) {
        for (int i = 0; i < adjMatrix.length; ++i) {
            for (int j = 0; j < adjMatrix.length; ++j) {
                if (i == j) {
                    adjMatrix[i][j] = 0;
                } else {
                    adjMatrix[i][j] = Integer.MAX_VALUE;
                }
            }
        }
    }
}

class Edge {
    String from;
    String to;
    int weight;
}

Freud algorithm (multi-source shortest path)

Algorithm Introduction
  1. Like Dijkstra algorithm, Freud algorithm is also an algorithm for finding the shortest path of vertices in a given additive group graph. The algorithm is named after Robert Freud, one of the founders, the winner of the Turing prize in 1978 and professor of computer science at Stanford University.
  2. Freud algorithm calculates the shortest path between the vertices in the graph
  3. Dijestra algorithm is used to calculate the shortest path from a vertex to other vertices in the graph.
  4. Dijestra algorithm finds the shortest path from the starting vertex to other vertices through the selected visited vertices; Each vertex in Freud's algorithm is the starting access point, so we need to treat each vertex as the accessed vertex and find the shortest path from each vertex to other vertices.
HDU 1869 Problem solution
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            int m = scanner.nextInt();
            int[][] map = new int[n][n];
            init(map, n);
            for (int i = 0; i < m; ++i) {
                int from = scanner.nextInt();
                int to = scanner.nextInt();
                if (from != to) {
                    map[from][to] = 1;
                    map[to][from] = 1;
                }
            }
            floyd(map, n);
            if (judge(map, n)) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }
        }

    }

    private static boolean judge(int[][] map, int n) {

        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (map[i][j] > 7) {
                    return false;
                }
            }
        }
        return true;
    }

    private static void floyd(int[][] map, int n) {
        // Weight calculation method from i node to j node
        // When adding a k node, which is the shortest path from i node to j node or the path from i node to j node via k node
        for (int k = 0; k < n; ++k) {
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (map[i][j] > map[i][k] + map[k][j] ) {
                        map[i][j] = map[i][k] + map[k][j];
                    }
                }
            }
        }
    }

    private static void init(int[][] map, int n) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                map[i][j] = 10000;
                if (i == j) {
                    map[i][j] = 0;
                }
            }
        }
    }
}

Tags: Algorithm data structure

Posted on Tue, 23 Nov 2021 19:55:00 -0500 by jayshadow