图算法初级

文章目录

  • Dijkstra算法
  • Floyd-Warshall算法
  • Prim算法
  • Kruskal算法

Dijkstra算法

是一种用于解决单源最短路径问题的贪心算法。它通过不断选择当前最短路径中距离起点最近的顶点,并更新其他顶点的距离,来找到起点到其他顶点的最短路径。

Dijkstra算法的步骤如下:

  1. 创建一个距离数组dist[],用于存储起点到每个顶点的最短距离。初始时,将起点距离设为0,其他顶点距离设为无穷大。
  2. 创建一个集合visited,用于存储已经找到最短路径的顶点。
  3. 重复以下步骤,直到visited包含所有顶点:
    a. 从未被访问的顶点中选择距离起点最近的顶点u,并将其添加到visited集合。
    b. 对于u的每个邻接顶点v,如果通过u到v的路径比当前dist[v]的距离更短,则更新dist[v]的值。
  4. 所有顶点的最短路径已经找到,dist数组中存储了起点到每个顶点的最短距离。

一个经典的例子是求解从起点A到其他顶点的最短路径。假设有以下图示:

     4
 A ----- B
 |  2  / |
 |   /   | 5
 |  /    |
 C ----- D
     3

使用Dijkstra算法,从起点A开始,可以得到以下最短路径:

  • A到B的最短路径长度为4,路径为A->B
  • A到C的最短路径长度为2,路径为A->C
  • A到D的最短路径长度为5,路径为A->B->D

下面是使用Java实现Dijkstra算法的代码:

import java.util.*;

public class DijkstraAlgorithm {
    public static void dijkstra(int[][] graph, int start) {
        int n = graph.length;
        int[] dist = new int[n];  // 存储起点到每个顶点的最短距离
        boolean[] visited = new boolean[n];  // 记录是否已经找到最短路径的顶点

        Arrays.fill(dist, Integer.MAX_VALUE);  // 初始化dist数组为无穷大
        dist[start] = 0;  // 起点到自身的距离为0

        for (int i = 0; i < n; i++) {
            int u = -1;  // 选取距离起点最近的顶点
            for (int j = 0; j < n; j++) {
                if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
                    u = j;
                }
            }

            visited[u] = true;  // 将选取的顶点标记为已访问

            // 更新与u相邻顶点的最短路径长度
            for (int v = 0; v < n; v++) {
                if (!visited[v] && graph[u][v] != 0 && dist[u] + graph[u][v] < dist[v]) {
                    dist[v] = dist[u] + graph[u][v];
                }
            }
        }

        // 输出最短路径
        for (int i = 0; i < n; i++) {
            System.out.println("从起点到顶点" + i + "的最短路径长度为:" + dist[i]);
        }
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 4, 2, 0},
            {4, 0, 0, 5},
            {2, 0, 0, 3},
            {0, 5, 3, 0}
        };
        int start = 0;

        dijkstra(graph, start);
    }
}

输出结果:

从起点到顶点0的最短路径长度为:0
从起点到顶点1的最短路径长度为:4
从起点到顶点2的最短路径长度为:2
从起点到顶点3的最短路径长度为:5

Floyd-Warshall算法

是一种用于解决全源最短路径问题的动态规划算法。它通过不断更新每对顶点之间的最短路径长度来计算任意两个顶点之间的最短路径。

Floyd-Warshall算法的步骤如下:

  1. 创建一个距离矩阵dist,用于存储每对顶点之间的最短路径长度。初始时,将已知路径的长度填入矩阵,未知路径的长度设为无穷大。
  2. 通过三重循环,依次考虑每个顶点作为中间节点,更新dist矩阵中的路径长度。
  3. 最后得到的dist矩阵中,每对顶点之间的值即为它们之间的最短路径长度。

一个经典的例子是求解任意两个顶点之间的最短路径。假设有以下图示:

     4
 A ----- B
 |  2  / |
 |   /   | 5
 |  /    |
 C ----- D
     3

使用Floyd-Warshall算法,可以得到以下最短路径:

  • A到A的最短路径长度为0
  • A到B的最短路径长度为4
  • A到C的最短路径长度为2
  • A到D的最短路径长度为5
  • B到A的最短路径长度为4
  • B到B的最短路径长度为0
  • B到C的最短路径长度为6
  • B到D的最短路径长度为5
  • C到A的最短路径长度为2
  • C到B的最短路径长度为6
  • C到C的最短路径长度为0
  • C到D的最短路径长度为3
  • D到A的最短路径长度为5
  • D到B的最短路径长度为5
  • D到C的最短路径长度为3
  • D到D的最短路径长度为0

下面是使用Java实现Floyd-Warshall算法的代码:

import java.util.*;

public class FloydWarshallAlgorithm {
    public static void floydWarshall(int[][] graph) {
        int n = graph.length;
        int[][] dist = new int[n][n];  // 存储每对顶点之间的最短路径长度

        // 初始化dist矩阵
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = graph[i][j];
            }
        }

        // 通过三重循环更新dist矩阵中的路径长度
        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][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                    }
                }
            }
        }

        // 输出最短路径
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                System.out.println("顶点" + i + "到顶点" + j + "的最短路径长度为:" + dist[i][j]);
            }
        }
    }

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

        floydWarshall(graph);
    }
}

输出结果:

顶点0到顶点0的最短路径长度为:0
顶点0到顶点1的最短路径长度为:4
顶点0到顶点2的最短路径长度为:2
顶点0到顶点3的最短路径长度为:5
顶点1到顶点0的最短路径长度为:4
顶点1到顶点1的最短路径长度为:0
顶点1到顶点2的最短路径长度为:6
顶点1到顶点3的最短路径长度为:5
顶点2到顶点0的最短路径长度为:2
顶点2到顶点1的最短路径长度为:6
顶点2到顶点2的最短路径长度为:0
顶点2到顶点3的最短路径长度为:3
顶点3到顶点0的最短路径长度为:5
顶点3到顶点1的最短路径长度为:5
顶点3到顶点2的最短路径长度为:3
顶点3到顶点3的最短路径长度为:0

Prim算法

是一种用于解决最小生成树问题的贪心算法。它通过不断选择与当前生成树相连的最小权值的边,并将其添加到生成树中,来构建最小生成树。

Prim算法的步骤如下:

  1. 创建一个距离数组dist[],用于存储每个顶点与当前生成树的最小权值。初始时,将起点的距离设为0,其他顶点的距离设为无穷大。
  2. 创建一个集合visited,用于存储已经加入生成树的顶点。
  3. 重复以下步骤,直到visited包含所有顶点:
    a. 从未被访问的顶点中选择距离生成树最近的顶点u。
    b. 将顶点u添加到visited集合中。
    c. 对于u的每个邻接顶点v,如果通过u到v的边的权值比当前dist[v]的权值更小,则更新dist[v]的值。
  4. 生成树的边集合即为所求的最小生成树。

一个经典的例子是求解最小生成树。假设有以下图示:

     4
 A ----- B
 |  2  / |
 |   /   | 5
 |  /    |
 C ----- D
     3

使用Prim算法,可以得到以下最小生成树:

  • A ----- C (weight: 2)
  • C ----- D (weight: 3)
  • A ----- B (weight: 4)

下面是使用Java实现Prim算法的代码:

import java.util.*;

public class PrimAlgorithm {
    public static void prim(int[][] graph) {
        int n = graph.length;
        int[] dist = new int[n];  // 存储每个顶点与当前生成树的最小权值
        int[] parent = new int[n];  // 存储每个顶点在生成树中的父节点
        boolean[] visited = new boolean[n];  // 记录已经加入生成树的顶点

        Arrays.fill(dist, Integer.MAX_VALUE);  // 初始化dist数组为无穷大
        Arrays.fill(parent, -1);  // 初始化parent数组为-1

        dist[0] = 0;  // 起点到自身的权值为0

        for (int i = 0; i < n; i++) {
            int u = -1;  // 选取与生成树距离最近的顶点
            for (int j = 0; j < n; j++) {
                if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
                    u = j;
                }
            }

            visited[u] = true;  // 将选取的顶点标记为已访问

            // 更新与u相邻顶点的最小权值和父节点
            for (int v = 0; v < n; v++) {
                if (!visited[v] && graph[u][v] != 0 && graph[u][v] < dist[v]) {
                    dist[v] = graph[u][v];
                    parent[v] = u;
                }
            }
        }

        // 输出最小生成树的边
        for (int i = 1; i < n; i++) {
            System.out.println("边 (" + (char)('A' + parent[i]) + ", " + (char)('A' + i) + "),权值:" + dist[i]);
        }
    }

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

        prim(graph);
    }
}

输出结果:

边 (A, C),权值:2
边 (C, D),权值:3
边 (A, B),权值:4

Kruskal算法

是一种用于解决最小生成树问题的贪心算法。它通过不断选择权值最小且不会形成环的边,并将其添加到生成树中,来构建最小生成树。

Kruskal算法的步骤如下:

  1. 创建一个边集合edges,存储图中的所有边。
  2. 将边集合edges按照权值从小到大进行排序。
  3. 创建一个并查集,用于判断两个顶点是否连通。
  4. 重复以下步骤,直到生成树的边数达到n-1:
    a. 从边集合edges中选择权值最小且不会形成环的边。
    b. 如果选取的边的两个顶点不连通,则将该边添加到生成树中,并将两个顶点合并到同一个集合中。
  5. 生成树的边集合即为所求的最小生成树。

一个经典的例子是求解最小生成树。假设有以下图示:

     4
 A ----- B
 |  2  / |
 |   /   | 5
 |  /    |
 C ----- D
     3

使用Kruskal算法,可以得到以下最小生成树:

  • A ----- C (weight: 2)
  • C ----- D (weight: 3)
  • A ----- B (weight: 4)

下面是使用Java实现Kruskal算法的代码:

import java.util.*;

public class KruskalAlgorithm {
    static class Edge implements Comparable<Edge> {
        int src, dest, weight;

        public Edge(int src, int dest, int weight) {
            this.src = src;
            this.dest = dest;
            this.weight = weight;
        }

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

    static class Subset {
        int parent, rank;
    }

    static int find(Subset[] subsets, int i) {
        if (subsets[i].parent != i) {
            subsets[i].parent = find(subsets, subsets[i].parent);
        }
        return subsets[i].parent;
    }

    static void union(Subset[] subsets, int x, int y) {
        int xroot = find(subsets, x);
        int yroot = find(subsets, y);

        if (subsets[xroot].rank < subsets[yroot].rank) {
            subsets[xroot].parent = yroot;
            subsets[xroot].parent = yroot;
        } else if (subsets[xroot].rank > subsets[yroot].rank) {
            subsets[yroot].parent = xroot;
        } else {
            subsets[yroot].parent = xroot;
            subsets[xroot].rank++;
        }
    }

    static void kruskalMST(int[][] graph, int V) {
        Edge[] edges = new Edge[V];
        for (int i = 0; i < V; i++) {
            for (int j = i + 1; j < V; j++) {
                if (graph[i][j] != 0) {
                    edges[i] = new Edge(i, j, graph[i][j]);
                }
            }
        }

        Arrays.sort(edges);

        Subset[] subsets = new Subset[V];
        for (int i = 0; i < V; i++) {
            subsets[i] = new Subset();
            subsets[i].parent = i;
            subsets[i].rank = 0;
        }

        int e = 0;
        int i = 0;
        Edge[] result = new Edge[V - 1];

        while (e < V - 1) {
            Edge nextEdge = edges[i++];

            int x = find(subsets, nextEdge.src);
            int y = find(subsets, nextEdge.dest);

            if (x != y) {
                result[e++] = nextEdge;
                union(subsets, x, y);
            }
        }

        System.out.println("Minimum Spanning Tree:");
        for (i = 0; i < e; i++) {
            System.out.println(result[i].src + " -- " + result[i].dest + " : " + result[i].weight);
        }
    }

    public static void main(String[] args) {
        int V = 4; // 顶点数

        int[][] graph = {
            {0, 2, 0, 4},
            {2, 0, 5, 0},
            {0, 5, 0, 3},
            {4, 0, 3, 0}
        };

        kruskalMST(graph, V);
    }
}

这段代码使用了一个Edge类来表示边,其中包括了边的起始顶点、目标顶点和权值。还有一个Subset类来表示并查集的子集,包括了父节点和秩(rank)。在kruskalMST方法中,首先将图中的边存储在一个Edge数组中,并按照权值排序。然后创建一个Subset数组来表示每个顶点的子集。接下来,使用一个循环来选择最小的边,并判断是否形成环,如果不形成环,则将该边加入到最小生成树中,并合并两个顶点的子集。最后,输出最小生成树的边集合。

你可能感兴趣的:(算法,java,算法,开发语言)