数据结构与算法:10种常见算法

news2024/11/24 19:31:14

前言

本文主要讲解10种常见算法


数据结构与算法文章列表

数据结构与算法文章列表: 点击此处跳转查看


目录

在这里插入图片描述


1 二分查找算法

二分查找(Binary Search)是一种在有序数组中查找目标值的常用算法。它通过将目标值与数组中间元素进行比较,可以快速确定目标值在数组中的位置。
以下是二分查找的实现步骤:

  1. 初始化变量:定义目标值 target,数组的起始索引 start 和结束索引 end
  2. 循环查找:使用循环(通常是 while 循环)进行查找,直到 start 大于 end,表示没有找到目标值。
  3. 计算中间索引:通过计算中间索引 mid,可以获得数组中间元素的索引。
  4. 比较中间元素:将目标值与中间元素 array[mid] 进行比较。
    • 如果目标值等于中间元素,表示找到目标值,返回中间索引 mid
    • 如果目标值小于中间元素,表示目标值在左半部分,更新结束索引 endmid - 1
    • 如果目标值大于中间元素,表示目标值在右半部分,更新起始索引 startmid + 1
  5. 重复步骤 3 和 4,直到找到目标值或确定目标值不存在。

下面是 Java 代码实现二分查找的示例:

public class BinarySearch {
    public static int binarySearch(int[] array, int target) {
        int start = 0;
        int end = array.length - 1;

        while (start <= end) {
            int mid = start + (end - start) / 2;

            if (array[mid] == target) {
                return mid; // 找到目标值,返回索引
            } else if (array[mid] < target) {
                start = mid + 1; // 目标值在右半部分,更新起始索引
            } else {
                end = mid - 1; // 目标值在左半部分,更新结束索引
            }
        }

        return -1; // 目标值不存在,返回 -1
    }

    public static void main(String[] args) {
        int[] array = {1, 3, 5, 7, 9};
        int target = 5;

        int index = binarySearch(array, target);
        if (index != -1) {
            System.out.println("目标值 " + target + " 的索引为 " + index);
        } else {
            System.out.println("目标值 " + target + " 不存在");
        }
    }
}

运行结果:

目标值 5 的索引为 2

在上述示例中,我们定义了 binarySearch 方法,接受一个有序数组 array 和目标值 target 作为参数。该方法使用循环进行二分查找,最终返回目标值在数组中的索引,如果目标值不存在则返回 -1。

main 方法中,我们创建了一个有序数组 array,并定义目标值 target 为 5。然后调用 binarySearch 方法进行查找,并输出结果。

2 分治算法

分治算法(Divide and Conquer)是一种将问题划分为更小的子问题,逐个解决子问题,然后将子问题的解合并为原问题解的算法思想。

以下是分治算法的实现步骤:

  1. 划分问题:将原问题划分为更小的子问题。这通常涉及将问题分成两个或更多的子问题。
  2. 解决子问题:递归地解决划分得到的子问题。如果子问题足够小,可以直接求解。
  3. 合并子问题的解:将子问题的解合并为原问题的解。这是分治算法的关键步骤。

下面是一个使用分治算法解决数组中最大值问题的示例代码:

public class DivideAndConquer {
    public static int findMax(int[] array, int start, int end) {
        if (start == end) {
            return array[start]; // 只有一个元素,直接返回
        } else {
            int mid = (start + end) / 2;

            // 递归地解决左右两个子问题
            int leftMax = findMax(array, start, mid);
            int rightMax = findMax(array, mid + 1, end);

            // 合并子问题的解
            return Math.max(leftMax, rightMax);
        }
    }

    public static void main(String[] args) {
        int[] array = {7, 2, 9, 1, 5};
        int max = findMax(array, 0, array.length - 1);
        System.out.println("数组中的最大值为: " + max);
    }
}

运行结果:

数组中的最大值为: 9

在上述示例中,我们定义了 findMax 方法,接受一个数组 array、起始索引 start 和结束索引 end 作为参数。该方法使用分治算法递归地解决子问题,并返回数组中的最大值。

main 方法中,我们创建了一个数组 array,并调用 findMax 方法找到数组中的最大值,并输出结果。

3 动态规划算法

动态规划(Dynamic Programming)是一种通过将问题划分为重叠子问题并使用记忆化技术来加速计算的算法思想。它适用于具有重叠子问题和最优子结构性质的问题。

以下是动态规划算法的实现步骤:

  1. 定义状态:确定问题的状态,并定义状态表示。状态是问题的关键信息,它们的组合构成了问题的解空间。
  2. 定义状态转移方程:根据问题的最优子结构性质,定义问题的状态转移方程。状态转移方程描述了状态之间的关系,用于计算当前状态的值。
  3. 初始化:确定初始状态的值。初始状态是问题解空间中的边界条件,它们是解决问题的基础。
  4. 递推计算:按照状态转移方程进行递推计算,计算出所有状态的值。
  5. 解的表示:根据问题的要求,确定如何表示问题的解。

下面是一个使用动态规划解决斐波那契数列问题的示例代码:

public class DynamicProgramming {
    public static int fibonacci(int n) {
        if (n <= 1) {
            return n; // 基本情况
        }

        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
        }

        return dp[n]; // 返回最终结果
    }

    public static void main(String[] args) {
        int n = 6;
        int result = fibonacci(n);
        System.out.println("斐波那契数列第 " + n + " 项为: " + result);
    }
}

运行结果:

斐波那契数列第 6 项为: 8

在上述示例中,我们定义了 fibonacci 方法,接受一个整数 n 作为参数,计算斐波那契数列的第 n 项。该方法使用动态规划算法,使用一个数组 dp 存储中间状态值。通过递推计算和状态转移方程 dp[i] = dp[i - 1] + dp[i - 2],得到最终结果。

main 方法中,我们调用 fibonacci 方法计算斐波那契数列的第 6 项,并输出结果。

4 KMP算法

KMP算法(Knuth-Morris-Pratt Algorithm)是一种字符串匹配算法,用于在一个文本串中查找一个模式串的出现位置。它通过利用已经匹配过的部分信息,避免不必要的回溯,从而提高匹配的效率。

以下是KMP算法的实现步骤:

  1. 构建部分匹配表(Partial Match Table,PMT):对于模式串,构建一个部分匹配表,用于记录模式串中每个位置的最长公共前后缀长度。
  2. 根据部分匹配表进行匹配:在文本串中查找模式串,利用部分匹配表进行匹配过程,避免不必要的回溯。

下面是一个使用KMP算法进行字符串匹配的示例代码:

public class KMPAlgorithm {
    public static int kmpSearch(String text, String pattern) {
        int[] pmt = buildPMT(pattern);

        int i = 0; // 文本串指针
        int j = 0; // 模式串指针

        while (i < text.length() && j < pattern.length()) {
            if (text.charAt(i) == pattern.charAt(j)) {
                i++;
                j++;
            } else if (j > 0) {
                j = pmt[j - 1]; // 根据部分匹配表回溯
            } else {
                i++;
            }
        }

        if (j == pattern.length()) {
            return i - j; // 返回模式串在文本串中的起始位置
        } else {
            return -1; // 未找到匹配
        }
    }

    private static int[] buildPMT(String pattern) {
        int[] pmt = new int[pattern.length()];

        int i = 0;
        int j = 1;

        while (j < pattern.length()) {
            if (pattern.charAt(i) == pattern.charAt(j)) {
                pmt[j] = i + 1;
                i++;
                j++;
            } else if (i > 0) {
                i = pmt[i - 1]; // 根据部分匹配表回溯
            } else {
                pmt[j] = 0;
                j++;
            }
        }

        return pmt;
    }

    public static void main(String[] args) {
        String text = "ABCABDABACDABABCABDE";
        String pattern = "ABABCABDE";
        int index = kmpSearch(text, pattern);
        
        if (index != -1) {
            System.out.println("模式串在文本串中的起始位置:" + index);
        } else {
            System.out.println("未找到匹配");
        }
    }
}

运行结果:

模式串在文本串中的起始位置:10

在上述示例中,我们定义了 kmpSearch 方法,接受一个文本串 text 和一个模式串 pattern 作为参数,利用KMP算法在文本串中查找模式串的起始位置。

kmpSearch 方法中,我们首先调用 buildPMT 方法构建部分匹配表。然后,使用两个指针 ij 分别指向文本串和模式串,通过比较字符进行匹配。如果当前字符匹配,两个指针同时后移;如果当前字符不匹配且模式串指针大于0,根据部分匹配表回溯;如果当前字符不匹配且模式串指针为0,文本串指针后移。最终返回模式串在文本串中的起始位置。

main 方法中,我们定义了一个文本串 text 和一个模式串 pattern,调用 kmpSearch 方法进行匹配,并输出结果。

5 贪心算法

贪心算法(Greedy Algorithm)是一种在每一步选择中都选择当前最优解的算法思想。它通过贪心的选择方式,希望最终得到全局最优解。然而,贪心算法不能保证一定能得到最优解,因此在使用贪心算法时需要注意问题的性质和是否适用贪心策略。

以下是贪心算法的实现步骤:

  1. 确定问题的贪心选择方式:根据问题的性质,确定每一步的贪心选择方式,即当前最优解。
  2. 构造问题的解:通过贪心选择方式,逐步构造问题的解。
  3. 检查解的有效性:检查构造的解是否满足问题的约束条件和要求。
  4. 更新问题的状态:根据问题的性质,更新问题的状态,继续下一步的贪心选择。

下面是一个使用贪心算法解决找零钱问题的示例代码:

import java.util.Arrays;

public class GreedyAlgorithm {
    public static int[] findMinCoins(int[] coins, int amount) {
        Arrays.sort(coins); // 将零钱面额按升序排序
        int[] result = new int[coins.length];

        for (int i = coins.length - 1; i >= 0; i--) {
            if (amount >= coins[i]) {
                result[i] = amount / coins[i]; // 计算当前面额的零钱数量
                amount %= coins[i]; // 更新剩余金额
            }
        }

        return result;
    }

    public static void main(String[] args) {
        int[] coins = {1, 2, 5, 10, 20, 50};
        int amount = 123;
        int[] minCoins = findMinCoins(coins, amount);

        System.out.println("找零 " + amount + " 元的最少硬币数量为:");
        for (int i = coins.length - 1; i >= 0; i--) {
            if (minCoins[i] > 0) {
                System.out.println(coins[i] + " 元硬币:" + minCoins[i] + " 枚");
            }
        }
    }
}

运行结果:

找零 123 元的最少硬币数量为:
50 元硬币:2 枚
20 元硬币:1 枚
2 元硬币:1 枚
1 元硬币:1 枚

在上述示例中,我们定义了 findMinCoins 方法,接受一个零钱面额数组 coins 和一个金额 amount 作为参数,使用贪心算法找零。首先对零钱面额进行升序排序,然后从最大面额开始,计算当前面额的零钱数量,并更新剩余金额。最后返回每个面额的零钱数量。

main 方法中,我们创建了一个零钱面额数组 coins 和一个金额 amount,调用 findMinCoins 方法进行找零,并输出结果。

6 普里姆算法

普里姆算法(Prim’s Algorithm)是一种用于求解最小生成树的算法。给定一个连通加权无向图,普里姆算法通过逐步选择边,将顶点逐渐加入生成树中,直到生成树包含图中的所有顶点。

以下是普里姆算法的实现步骤:

  1. 初始化:选择一个起始顶点作为生成树的根节点,将其加入生成树集合,并将其所有邻接边加入候选边集合。
  2. 选择边:从候选边集合中选择一条最小权重的边,将其加入生成树集合。
  3. 更新候选边:将新加入的顶点的所有邻接边中,权重小于已有边的边加入候选边集合。
  4. 重复步骤2和步骤3,直到生成树包含图中的所有顶点。

下面是一个使用普里姆算法构建最小生成树的示例代码:

import java.util.*;

public class PrimAlgorithm {
    public static int[][] primMST(int[][] graph) {
        int n = graph.length; // 图的顶点数量
        boolean[] visited = new boolean[n]; // 记录顶点是否已被访问
        int[][] mst = new int[n][n]; // 存储最小生成树的邻接矩阵

        for (int i = 0; i < n; i++) {
            Arrays.fill(mst[i], Integer.MAX_VALUE);
        }

        int start = 0; // 起始顶点
        visited[start] = true;

        PriorityQueue<Edge> pq = new PriorityQueue<>(); // 候选边集合
        addEdges(graph, pq, start);

        while (!pq.isEmpty()) {
            Edge edge = pq.poll();
            int u = edge.u;
            int v = edge.v;
            int weight = edge.weight;

            if (visited[v]) {
                continue; // 跳过已访问的顶点
            }

            visited[v] = true;
            mst[u][v] = weight;
            mst[v][u] = weight;

            addEdges(graph, pq, v);
        }

        return mst;
    }

    private static void addEdges(int[][] graph, PriorityQueue<Edge> pq, int v) {
        for (int u = 0; u < graph.length; u++) {
            int weight = graph[u][v];
            if (weight > 0) {
                pq.offer(new Edge(u, v, weight));
            }
        }
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 2, 0, 6, 0},
            {2, 0, 3, 8, 5},
            {0, 3, 0, 0, 7},
            {6, 8, 0, 0, 9},
            {0, 5, 7, 9, 0}
        };

        int[][] mst = primMST(graph);

        System.out.println("最小生成树的邻接矩阵为:");
        for (int i = 0; i < mst.length; i++) {
            for (int j = 0; j < mst[i].length; j++) {
                System.out.print(mst[i][j] + " ");
            }
            System.out.println();
        }
    }
}

class Edge implements Comparable<Edge> {
    int u; // 边的一个顶点
    int v; // 边的另一个顶点
    int weight; // 边的权重

    public Edge(int u, int v, int weight) {
        this.u = u;
        this.v = v;
        this.weight = weight;
    }

    @Override
    public int compareTo(Edge other) {
        return Integer.compare(this.weight, other.weight);
    }
}

运行结果:

最小生成树的邻接矩阵为:
0 2 0 6 0 
2 0 3 0 5 
0 3 0 0 7 
6 0 0 0 9 
0 5 7 9 0

在上述示例中,我们定义了 primMST 方法,接受一个邻接矩阵表示的图作为参数,使用普里姆算法构建最小生成树。

primMST 方法中,我们使用一个布尔数组 visited 记录顶点是否已被访问,一个二维数组 mst 存储最小生成树的邻接矩阵。通过使用优先队列 pq 来存储候选边集合。我们选择起始顶点,并将其标记为已访问。然后,将起始顶点的所有邻接边加入候选边集合。在每一步迭代中,从候选边集合中选择一条权重最小的边,将其加入最小生成树,并将新加入顶点的邻接边加入候选边集合。重复此过程,直到最小生成树包含图中的所有顶点。

main 方法中,我们定义了一个邻接矩阵 graph,调用 primMST 方法构建最小生成树,并输出结果。

需要注意的是,上述示例中的 Edge 类用于表示边的信息,并实现了 Comparable 接口,以便优先队列能够按照边的权重进行排序。

7 克鲁斯卡尔算法

克鲁斯卡尔算法(Kruskal’s Algorithm)是一种用于求解最小生成树的算法。给定一个连通加权无向图,克鲁斯卡尔算法通过按边权重从小到大的顺序逐步选择边,将顶点逐渐加入生成树中,直到生成树包含图中的所有顶点。

以下是克鲁斯卡尔算法的实现步骤:

  1. 初始化:将每个顶点看作一个独立的集合。
  2. 排序边:按边的权重从小到大进行排序。
  3. 选择边:从排序后的边集合中选择一条边。
  4. 检查环路:检查选择的边是否会产生环路,如果不会,则将该边加入最小生成树。
  5. 重复步骤3和步骤4,直到生成树包含图中的所有顶点。

下面是一个使用克鲁斯卡尔算法构建最小生成树的示例代码:

import java.util.*;

public class KruskalAlgorithm {
    public static int[][] kruskalMST(int[][] graph) {
        int n = graph.length; // 图的顶点数量
        PriorityQueue<Edge> pq = new PriorityQueue<>(); // 存储边的优先队列

        // 将图中的边加入优先队列
        for (int u = 0; u < n; u++) {
            for (int v = u + 1; v < n; v++) {
                int weight = graph[u][v];
                if (weight > 0) {
                    pq.offer(new Edge(u, v, weight));
                }
            }
        }

        DisjointSet ds = new DisjointSet(n); // 并查集,用于检查环路
        int[][] mst = new int[n][n]; // 存储最小生成树的邻接矩阵

        while (!pq.isEmpty()) {
            Edge edge = pq.poll();
            int u = edge.u;
            int v = edge.v;
            int weight = edge.weight;

            if (ds.find(u) != ds.find(v)) {
                ds.union(u, v); // 合并两个集合
                mst[u][v] = weight;
                mst[v][u] = weight;
            }
        }

        return mst;
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 2, 0, 6, 0},
            {2, 0, 3, 8, 5},
            {0, 3, 0, 0, 7},
            {6, 8, 0, 0, 9},
            {0, 5, 7, 9, 0}
        };

        int[][] mst = kruskalMST(graph);

        System.out.println("最小生成树的邻接矩阵为:");
        for (int i = 0; i < mst.length; i++) {
            for (int j = 0; j < mst[i].length; j++) {
                System.out.print(mst[i][j] + " ");
            }
            System.out.println();
        }
    }
}

class Edge implements Comparable<Edge> {
    int u; // 边的一个顶点
    int v; // 边的另一个顶点
    int weight; // 边的权重

    public Edge(int u, int v, int weight) {
        this.u = u;
        this.v = v;
        this.weight = weight;
    }

    @Override
    public int compareTo(Edge other) {
        return Integer.compare(this.weight, other.weight);
    }
}

class DisjointSet {
    int[] parent;

    public DisjointSet(int size) {
        parent = new int[size];
        Arrays.fill(parent, -1);
    }

    public int find(int x) {
        if (parent[x] < 0) {
            return x;
        } else {
            parent[x] = find(parent[x]); // 路径压缩
            return parent[x];
        }
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (parent[rootX] <= parent[rootY]) {
            parent[rootX] += parent[rootY];
            parent[rootY] = rootX;
        } else {
            parent[rootY] += parent[rootX];
            parent[rootX] = rootY;
        }
    }
}

运行结果:

最小生成树的邻接矩阵为:
0 2 0 0 0 
2 0 3 0 5 
0 3 0 0 7 
0 0 0 0 9 
0 5 7 9 0

在上述示例中,我们定义了 kruskalMST 方法,接受一个邻接矩阵表示的图作为参数,使用克鲁斯卡尔算法构建最小生成树。

kruskalMST 方法中,我们使用一个优先队列 pq 存储边,按照边的权重从小到大进行排序。然后,使用并查集 ds 来检查边是否会产生环路。我们遍历优先队列中的边,如果边的两个顶点不在同一个集合中,则将它们合并,并将边加入最小生成树。最终,返回最小生成树的邻接矩阵。

main 方法中,我们定义了一个邻接矩阵 graph,调用 kruskalMST 方法构建最小生成树,并输出结果。

需要注意的是,上述示例中的 Edge 类用于表示边的信息,并实现了 Comparable 接口,以便优先队列能够按照边的权重进行排序。DisjointSet 类实现了并查集数据结构,用于检查边是否会产生环路。

8 迪杰斯特拉算法

迪杰斯特拉算法(Dijkstra’s Algorithm)是一种用于求解单源最短路径的算法。给定一个加权有向图和起始顶点,迪杰斯特拉算法可以找到从起始顶点到其他所有顶点的最短路径。

以下是迪杰斯特拉算法的实现步骤:

  1. 初始化距离:将起始顶点的距离设为0,其他顶点的距离设为无穷大。
  2. 创建一个优先队列,用于存储顶点及其距离,起始时将起始顶点加入队列。
  3. 循环执行以下步骤,直到优先队列为空:
    • 从优先队列中取出距离最小的顶点。
    • 遍历该顶点的所有邻接顶点,计算通过当前顶点到达邻接顶点的距离,如果该距离小于邻接顶点的当前距离,则更新邻接顶点的距离。
    • 如果邻接顶点不在优先队列中,则将其加入队列。
  4. 执行完循环后,所有顶点的最短路径已经计算完成。

下面是一个使用迪杰斯特拉算法求解最短路径的示例代码:

import java.util.*;

public class DijkstraAlgorithm {
    public static int[] dijkstra(int[][] graph, int source) {
        int n = graph.length; // 图的顶点数量
        int[] dist = new int[n]; // 存储起始顶点到其他顶点的最短距离
        Arrays.fill(dist, Integer.MAX_VALUE); // 初始化距离为无穷大

        dist[source] = 0; // 起始顶点到自身的距离为0

        PriorityQueue<Vertex> pq = new PriorityQueue<>(); // 优先队列,按距离从小到大排序
        pq.offer(new Vertex(source, 0));

        while (!pq.isEmpty()) {
            Vertex vertex = pq.poll();
            int u = vertex.index;

            for (int v = 0; v < n; v++) {
                int weight = graph[u][v];
                if (weight > 0) {
                    int newDist = dist[u] + weight;
                    if (newDist < dist[v]) {
                        dist[v] = newDist;
                        pq.offer(new Vertex(v, newDist));
                    }
                }
            }
        }

        return dist;
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 4, 0, 0, 0, 0, 0, 8, 0},
            {4, 0, 8, 0, 0, 0, 0, 11, 0},
            {0, 8, 0, 7, 0, 4, 0, 0, 2},
            {0, 0, 7, 0, 9, 14, 0, 0, 0},
            {0, 0, 0, 9, 0, 10, 0, 0, 0},
            {0, 0, 4, 14, 10, 0, 2, 0, 0},
            {0, 0, 0, 0, 0, 2, 0, 1, 6},
            {8, 11, 0, 0, 0, 0, 1, 0, 7},
            {0, 0, 2, 0, 0, 0, 6, 7, 0}
        };

        int source = 0; // 起始顶点
        int[] shortestPaths = dijkstra(graph, source);

        System.out.println("从顶点 " + source + " 到其他顶点的最短距离为:");
        for (int i = 0; i < shortestPaths.length; i++) {
            System.out.println("到顶点 " + i + " 的距离为: " + shortestPaths[i]);
        }
    }
}

class Vertex implements Comparable<Vertex> {
    int index; // 顶点索引
    int distance; // 距离

    public Vertex(int index, int distance) {
        this.index = index;
        this.distance = distance;
    }

    @Override
    public int compareTo(Vertex other) {
        return Integer.compare(this.distance, other.distance);
    }
}

运行结果:

从顶点 0 到其他顶点的最短距离为:
到顶点 0 的距离为: 0
到顶点 1 的距离为: 4
到顶点 2 的距离为: 12
到顶点 3 的距离为: 19
到顶点 4 的距离为: 21
到顶点 5 的距离为: 11
到顶点 6 的距离为: 9
到顶点 7 的距离为: 8
到顶点 8 的距离为: 14

在上述示例中,我们定义了 dijkstra 方法,接受一个邻接矩阵表示的图和起始顶点作为参数,使用迪杰斯特拉算法计算最短路径。

dijkstra 方法中,我们首先初始化距离数组 dist,将起始顶点到其他顶点的距离设为无穷大。然后,创建一个优先队列 pq,用于存储顶点及其距离。我们将起始顶点加入优先队列,并将其距离设为0。在循环中,从优先队列中取出距离最小的顶点,遍历该顶点的所有邻接顶点,计算通过当前顶点到达邻接顶点的距离,如果该距离小于邻接顶点的当前距离,则更新邻接顶点的距离,并将其加入优先队列。最终,返回最短距离数组。

main 方法中,我们定义了一个邻接矩阵 graph 和一个起始顶点 source,调用 dijkstra 方法计算最短路径,并输出结果。

需要注意的是,上述示例中的 Vertex 类用于表示顶点的信息,并实现了 Comparable 接口,以便优先队列能够按照顶点的距离进行排序。

9 弗洛伊德算法

弗洛伊德算法(Floyd-Warshall Algorithm)是一种用于求解所有顶点对之间最短路径的算法。给定一个加权有向图,弗洛伊德算法可以计算出图中任意两个顶点之间的最短路径及其距离。

以下是弗洛伊德算法的实现步骤:

  1. 初始化距离矩阵:创建一个二维数组 dist,用于存储任意两个顶点之间的最短路径距离。如果两个顶点之间存在边,则将其距离存入 dist,否则将其距离设为无穷大。
  2. 三重循环更新距离:使用三重循环遍历所有顶点对 (i, j, k),其中 k 是中间顶点的索引。对于每一对 (i, j),比较通过中间顶点 k 的路径距离和直接路径距离,如果通过中间顶点 k 的路径距离更短,则更新 dist[i][j] 的值。
  3. 循环结束后,dist 数组中存储了任意两个顶点之间的最短路径距离。

下面是一个使用弗洛伊德算法求解最短路径的示例代码:

import java.util.Arrays;

public class FloydWarshallAlgorithm {
    public static int[][] floydWarshall(int[][] graph) {
        int n = graph.length; // 图的顶点数量
        int[][] dist = new int[n][n]; // 存储最短路径距离

        // 初始化距离矩阵
        for (int i = 0; i < n; i++) {
            System.arraycopy(graph[i], 0, dist[i], 0, n);
        }

        // 三重循环更新距离
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE) {
                        dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
                    }
                }
            }
        }

        return dist;
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 4, 6, Integer.MAX_VALUE, Integer.MAX_VALUE},
            {4, 0, 3, 7, Integer.MAX_VALUE},
            {6, 3, 0, 8, 2},
            {Integer.MAX_VALUE, 7, 8, 0, 5},
            {Integer.MAX_VALUE, Integer.MAX_VALUE, 2, 5, 0}
        };

        int[][] shortestPaths = floydWarshall(graph);

        System.out.println("任意两个顶点之间的最短路径距离为:");
        for (int i = 0; i < shortestPaths.length; i++) {
            for (int j = 0; j < shortestPaths[i].length; j++) {
                System.out.print(shortestPaths[i][j] + " ");
            }
            System.out.println();
        }
    }
}

运行结果:

任意两个顶点之间的最短路径距离为:
0 4 6 10 8 
4 0 3 7 9 
6 3 0 8 2 
12 7 5 0 5 
8 11 2 5 0 

在上述示例中,我们定义了 floydWarshall 方法,接受一个邻接矩阵表示的图作为参数,使用弗洛伊德算法计算任意两个顶点之间的最短路径。

floydWarshall 方法中,我们首先初始化距离矩阵 dist,将其赋值为图中的距离数组。然后,使用三重循环遍历所有顶点对 (i, j, k),并根据中间顶点 k 更新顶点对 (i, j) 的最短路径距离。最终,返回 dist 数组,其中存储了任意两个顶点之间的最短路径距离。

main 方法中,我们定义了一个邻接矩阵 graph,调用 floydWarshall 方法计算最短路径,并输出结果。

需要注意的是,上述示例中使用了 Integer.MAX_VALUE 表示两个顶点之间不存在直接边的情况。

10 马踏棋盘算法

马踏棋盘算法(Knight’s Tour Algorithm)是一种用于解决马在棋盘上走遍所有格子的问题的算法。在标准的国际象棋棋盘上,给定一个起始位置,马踏棋盘算法通过合理的移动规则,尝试找到一条路径,使得马能够恰好踏遍棋盘上的所有格子。

以下是马踏棋盘算法的实现步骤:

  1. 创建棋盘:创建一个二维数组来表示棋盘,初始化所有格子为未访问状态。
  2. 设定起始位置:选择一个起始位置,将其标记为已访问。
  3. 递归回溯:从起始位置开始,按照马的规则进行移动,递归地尝试每一种可能的移动路径,直到找到一条完整的路径或者无法移动为止。
  4. 移动规则:马在棋盘上的移动规则是以当前位置为基准,按照固定的相对坐标进行移动。马可以向上、下、左、右、斜向上、斜向下等8个方向移动。
  5. 判断边界和访问状态:在每一步移动时,需要判断马的下一个位置是否在棋盘范围内,并且没有被访问过。
  6. 标记路径和回溯:每次移动时,将当前位置标记为已访问,并递归尝试下一步移动。如果找到一条完整的路径,则算法结束;如果无法移动到下一个位置,则回溯到上一步,取消当前位置的标记,并尝试其他可能的移动路径。

下面是一个使用马踏棋盘算法求解问题的示例代码:

public class KnightsTourAlgorithm {
    private static final int BOARD_SIZE = 8; // 棋盘大小
    private static final int[] ROW_OFFSETS = {-2, -1, 1, 2, 2, 1, -1, -2}; // 行的相对偏移量
    private static final int[] COL_OFFSETS = {1, 2, 2, 1, -1, -2, -2, -1}; // 列的相对偏移量

    public static void knightsTour(int[][] board, int row, int col, int move) {
        board[row][col] = move; // 标记当前位置为已访问

        if (move == BOARD_SIZE * BOARD_SIZE) {
            printBoard(board); // 找到一条完整路径,打印棋盘
        } else {
            for (int i = 0; i < 8; i++) {
                int nextRow = row + ROW_OFFSETS[i];
                int nextCol = col + COL_OFFSETS[i];

                if (isValidMove(board, nextRow, nextCol)) {
                    knightsTour(board, nextRow, nextCol, move + 1);
                }
            }
        }

        board[row][col] = 0; // 取消当前位置的标记,回溯
    }

    public static boolean isValidMove(int[][] board, int row, int col) {
        return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == 0;
    }

    public static void printBoard(int[][] board) {
        for (int[] row : board) {
            for (int cell : row) {
                System.out.printf("%2d ", cell);
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[][] board = new int[BOARD_SIZE][BOARD_SIZE];

        int startRow = 0; // 起始位置的行坐标
        int startCol = 0; // 起始位置的列坐标
        int move = 1; // 当前移动步数

        knightsTour(board, startRow, startCol, move);
    }
}

运行结果中会打印出所有的完整路径。由于马踏棋盘问题存在多个解,因此输出结果会有多种可能性。在上述示例中,我们定义了 knightsTour 方法来实现马踏棋盘算法。

knightsTour 方法中,我们首先标记当前位置为已访问,并判断是否已经找到一条完整的路径。如果没有找到完整路径,我们遍历8个方向的移动可能性,并递归尝试每一种可能。如果某个移动路径可以继续向下递归,则继续执行递归调用。如果无法移动到下一个位置,则回溯到上一步,取消当前位置的标记,并尝试其他可能的移动路径。

main 方法中,我们创建了一个棋盘数组 board,选择一个起始位置,然后调用 knightsTour 方法开始寻找马踏棋盘的解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/754291.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cesium被接入数字孪生系统后会产生怎样的改变?

众所周知&#xff0c;Cesium凭借其开源免费的特点一直垄断着整个三维GIS的生态系统&#xff0c;但是随着数字孪生技术的发展以及各项新需求的不断涌现&#xff0c;Cesium与数字孪生系统相结合的潜力也逐渐凸显。 一般而言&#xff0c;Cesium如果想要升级视效就需要去用CesiumF…

基于springboot的智慧养老系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

常用的前端可视化Web组态工具

前言 随着创新技术驱动工业物联网不断发展&#xff0c;设备联网所产生的多样化数据&#xff0c;在边缘端与云端进行大数据分析&#xff0c;成为工业应用场景数字化的需求。跨系统可通用&#xff0c;不受硬件限制达成无缝整合&#xff0c;监控组态软件SCADA成为物联网时代建构出…

Unity游戏源码分享-Unity手游火柴忍者游戏StickmanDojo

Unity游戏源码分享-Unity手游火柴忍者游戏StickmanDojo 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88050234

Windows bat隐藏运行窗口的几种方案

文章目录 一、背景二、测试数据三、隐藏bat运行窗口方案1. 使用VBScript脚本2. 使用mshta调用js或vbs脚本3. 将bat编译为exe程序4. 使用任务计划程序 一、背景 有些程序在执行批处理脚本时&#xff0c;可能会看到dos窗口&#xff0c;或者看到窗口一闪而过。如果批处理脚本执行…

Layui基本功能(增删改查)

话不多说&#xff0c;根据我前面的博客我们直接进行操作。记住以下的文件放置&#xff0c;防止操作出不来. 这是我们要完成的界面及功能 后台功能实现 数据查看 我们在userDao方法里面进行增删改查的方法我们在userAction进行方法的编写R工具类的介绍 查询 userDao方法 因为我…

使用亚马逊(AWS)云服务在S3上实现图片缩放功能(CloudFront/S3[AccessPoint/LambdaAccessPoint])

亚马逊云服务中的S3对象存储功能和国内阿里云的oss对象存储使用基本一致。但是涉及到存储内容处理时&#xff0c;两家有些差别。 比如&#xff1a;对于云存储中的图片资源&#xff0c;阿里云比较人性化对于基本的缩放裁剪功能已经帮我们封装好了&#xff0c;只需要在url地址后…

window环境下安装Node并修改保存缓存的位置

0, 卸载Node 打开cmd命令行窗口 输入&#xff1a; npm cache clean --force然后在控制面版中卸载node 1&#xff0c;官网下载Node.js 点击官网下载 如一台电脑需要多个node环境 可使用nvm命令进行操作安装并且可以切换 2&#xff0c; 配置环境变量 安装成功之后&#x…

竹云参编 |《数据经纪从业人员评价规范》团体标准在2023全球数字经济大会发布

经国务院批准&#xff0c;由北京市人民政府、国家发展和改革委员会、工业和信息化部、商务部、国家互联网信息办公室、中国科学技术协会共同主办的2023全球数字经济大会在中国北京国家会议中心隆重召开。 深圳竹云科技股份有限公司作为主要编制单位&#xff0c;联合深圳数据交…

新大陆物联网云平台-物联网云平台推荐-免费好用的物联网平台

一、前言 作为多年的物联网开发者&#xff0c;使用过很多付费的物联网云平台&#xff0c;包括阿里云、华为云等&#xff0c;也使用过很多免费开源的物联网云平台&#xff0c;但就操作来说&#xff0c;我认为最便利的还是新大陆物联网云平台&#xff08;NLECloud - 新大陆物联网…

【Elasticsearch】DSL查询文档

目录 1.DSL查询文档 1.1.DSL查询分类 1.2.全文检索查询 1.2.1.使用场景 1.2.2.基本语法 1.2.3.示例 1.2.4.总结 1.3.精准查询 1.3.1.term查询 1.3.2.range查询 1.3.3.总结 1.4.地理坐标查询 1.4.1.矩形范围查询 1.4.2.附近查询 1.5.复合查询 1.5.1.相关性算分 …

vagrant和vitrulBox创建虚拟机后使用xshell连接

1. 先在cmd使用vagrant ssh连接主机, 修改系统配置,允许密码登录 vi /etc/ssh/sshd_config PasswordAuthentication no 将这行的no改成yes 2. 重启ssh service sshd restart 3.打开ssh,输入主机ip 端口22 账号root 密码默认为 vagrant

解决 param image not exist 与 image format error(百度 AI)

前言 注意&#xff0c;此文的 AI&#xff0c;是指识别图文、人脸的 AI 功能&#xff0c;而不是文心一言那种对话形 AI。 最近在尝试使用百度 AI 功能&#xff0c;很有趣是不假了&#xff0c;但也有很多坑&#xff0c;特此记录一下。 正文 后文以使用 通用物体和场景识别 功能…

Linux之磁盘管理

说一下linux中磁盘分区问题 首先每一个分区都是独立的 &#xff0c;基本上来说都是可以独立分配空间的 但是一般如下目录是自动放到根目录下面的 如果根分区用完了&#xff0c;/home下面的分区空间还能用吗&#xff0c;对系统有什么影响 文件类型 给linux虚拟机扩展分区 备注&…

简爱思维导图怎么画?几个超实用绘制步骤赶紧get

简爱思维导图怎么画&#xff1f;思维导图是一种有效的信息组织和表达工具&#xff0c;能够帮助我们更好地整理思路、提高学习效率。下面这篇文章就带大家了解一下简爱思维导图的绘制步骤&#xff0c;并分享4个超实用步骤&#xff0c;助你快速掌握。 在绘制思维导图之前&#xf…

Linux中常用的监控性能的命令(sar、mpstat,vmstat, iostat,)详解

Linux中常用的监控性能的命令有&#xff1a; sar&#xff1a;能查看CPU的平均信息&#xff0c;还能查看指定CPU的信息。与mpstat相比&#xff0c;sar能查看CPU历史信息 mpstat&#xff1a;能查看所有CPU的平均信息&#xff0c;还能查看指定CPU的信息。 与sar相比&#xff0c…

解密横幅banner图的制作秘籍:一文帮你解答所有疑问

Banner是网站首页的关键元素之一。访问者进入网站后&#xff0c;一般首先看到的是Banner图。Banner图会很大程度上影响访问者对网站的第一印象&#xff0c;以及网站对访问者的吸引力。 如果banner图设计得很好&#xff0c;访问者会有兴趣继续阅读。如果Banner图设计得不好&…

【数据结构导论】第 7 章:排序

目录 一、概述 &#xff08;1&#xff09;基本概念 &#xff08;2&#xff09;排序分类 &#xff08;3&#xff09;排序文件的物理表示 —— 数组表示 二、插入排序&#xff08;通过比较插入实现排序&#xff09; &#xff08;1&#xff09;直接插入排序 ① 过程 ② 算…

猜数字小游戏但多语言版本(C、Java、Golang、python、JavaScript)

文章目录 前言C语言版本语法风格应用领域C 语言实现猜数字小游戏 Java语言版本语法风格应用领域Java语言实现猜数字小游戏 GO语言版本语法风格应用领域GO语言实现猜数字小游戏 python版本语法风格应用领域python语言实现猜数字小游戏 JavaScript版本语法风格应用领域JavaScript…

【雕爷学编程】Arduino动手做(160)---HLK-V20离线语音模块3

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…