数据结构常用算法

分治算法

分治算法介绍
  1. 分治法时一种很重要的算法。字面上的解释就是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题…直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  2. 分治算法可以求解的一些经典问题:二分搜索、大整数乘法、棋盘覆盖、归并排序、快速排序、线性时间选择、最近点对、循环赛日程表、汉诺塔。
分治算法基本步骤
  1. 分解:将原问题分解成若干个规模较小,相互独立、与原问题形式相同的子问题。
  2. 解决:若子问题规模较小而容器被解决则直接解决掉,否则递归地求解各个子问题。
  3. 合并:将各个子问题的解合并为原问题的解。
汉诺塔代码实现
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("移动,从%c到%c\n", a, c);
        } else {
            // 大于两个盘的时候,我们可以看作是两个盘的移动
            // 1. 最下面的盘当做一个盘
            // 2. 除最下面的盘外的所有盘当中另一个盘
            // 先将上面的盘移动到从a移动的b,中间用到c
            hanotower(n - 1, a, c, b);
            // 再将另一个盘从a到c
            System.out.println("第" + n + "个盘从" + a + "->" + c);
            // 把b塔的所有盘从b移动到c,中间移动过程用到a
            hanotower(n - 1, b, a, c);
        }
    }
}

动态规划算法

算法介绍
  1. 动态规划算法的核心思想时:将大问题划分为小问题进行求解,从而进一步获取最优解的处理算法。
  2. 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
  3. 与分治算法不同的是,适合于动态规划求解的问题,经分解得到的子问题往往不是相互独立的。(即下一个子阶段的求解是建立再上一个子阶段的解的基础上,进行进一步的求解)。
  4. 动态规划可以通过填表的方式来逐步推进,得到最优解。
0-1背包问题

题目: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算法

参考

算法介绍
  1. KMP是一个解决模式串在文本串是否出现过。如果出现过,最早出现的位置的经典算法。
  2. Knuth-Morris-Pratt字符串查找算法,简称KMP算法。常用于在一个文本串S内查找一个模式串P的出现位置。这个算法由Donald Knuth、Vaughant Pratt、James H·Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。
  3. KMP算法就利用之前判断过信息,通过一个next数组,保持模式串中前后最长公共子序列的长度,每次回溯时,通过next数组找到,前面匹配过的位置,省去了大量的计算时间。

算法详细讲解,看视频

HDU 2087 题解
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;
    }

    // 获取到一个串的部分匹配值表
    public static int[] kmpNext(String dest) {

        int[] next = new int[dest.length()];
        next[0] = 0; // 如果字符串长度为1部分匹配值就是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;
    }
}

贪心算法

贪心算法又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。贪心算法在有最优子结构问题中尤为有效。最优子结果的意思就是局部最优解能决定全局最优解。简单来说,问题能够分解成子问题来求解,子问题的最优解能递推到最终问题的最优解。贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。

HDU 5747 题解
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);
        }
    }
}

普里姆算法

普里姆算法,图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索得到的边的子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现;并在1957年由美国计算机科学家罗伯特·普里姆独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法

最小生成树是一副加权无向连通图中一颗权值最小的生成树。最小生成树包括n和n-1条边。

算法介绍
  1. 普里姆算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连同子图,也就是所谓的极小连通子图。
  2. 普里姆算法如下:
    1. 设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合。
    2. 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1,表示已经被访问过。
    3. 若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1已访问过。
    4. 重复步骤2,直到U与V相等,即所有的顶点都被标记为访问过,此时D中有n-1条边。
HDU 1301 题解
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) { // 一共需要找到n-1条边,所以这里是n-1次循环
            int minWeight = Integer.MAX_VALUE;
            for (int i = 0; i < map.length; i++) { //被访问过的节点
                for (int j = 0; j < map.length; j++) { // 没有被访问过的节点
                    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();
        }
    }
}

克鲁斯卡尔算法

算法介绍
  1. 克鲁斯卡尔算法,是用来求加权连通图的最小生成树的算法。
  2. 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路。
  3. 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入森林中,并使森林中不产生回路,直至森林变成一棵树为止。

算法详细解析,可以看视频

HDU 1301 题解
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; // 图的顶点数
    int edgeNum;    // 图的边的数目
    List<EdgeType> edge; // 存放图边的数组
}

迪杰斯特拉算法(单源最短路径)

可以观看视频,学习算法

算法介绍

迪杰斯特拉算法是典型的最短路径算法,用于计算一个结点到其它结点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

算法过程

设置出发顶点为v,顶点集合V{v1,v2,v3,…},v到V中各个顶点的距离构成距离集合Dis。Dis{d1,d2,d3,…},Dis集合记录着v到图中各顶点的距离(到自身可以看作0,v到vi距离对应为di)

  1. 从Dis中选择值最小的di并移出Dis集合,同时移出V集合中对应的顶点vi,此时的v到vi即为最短路径。
  2. 更新Dis集合,更新规则为:比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱结点为vi,表明是通过vi到达的)
  3. 重复执行两步骤,直到最短路径顶点为目标顶点即可结束。
HDU 2112题解

提醒 起点和终点也需要加入图中,因为起点和终点有可能不在下面的路径中出现。想到这一点,题目就很好通过了。

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; // 将新生成的顶点加入集合S
            U[min] = 0; // 将新生成的顶点从U集合中移除
            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;
}

佛洛依德算法(多源最短路径)

算法介绍
  1. 和Dijkstra算法一样,佛洛伊德算法也是一种用于寻找给定的加群图中顶点最短路径的算法。该算法名称以创始人之一,1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·佛洛伊德命名。
  2. 佛洛伊德算法计算图中各个顶点之间的最短路径
  3. 迪杰斯特拉算法用于计算图中某一个顶点到其它顶点的最短路径。
  4. 迪杰斯特拉算法通过选定的被访问的顶点,求出从出发顶点到其它顶点的最短路径;佛洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其它顶点的最短路径。
HDU 1869题解
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) {
        // 从i节点到j节点的权值计算方法
        // 当加入k节点时候,从i节点到j节点的最短路径 与 从i节点经由k节点到j节点路径,哪个更短
        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;
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值