1基于Java语言,实现图的邻接表表示法。

Java中,可以使用邻接表来表示图。邻接表是一种常用的数据结构,用于存储图中的顶点和边。每个顶点都有一个链表(或数组)来存储与其相连的其他顶点。
下面是一个简单的实现,包括图的基本操作:添加顶点、添加边、打印图等。
import java.util.*;

class Graph {
    private int numVertices; // 顶点的数量
    private LinkedList<Integer>[] adjLists; // 邻接表

    // 构造函数
    public Graph(int vertices) {
        numVertices = vertices;
        adjLists = new LinkedList[vertices];
        for (int i = 0; i < vertices; i++) {
            adjLists[i] = new LinkedList<>();
        }
    }

    // 添加边
    public void addEdge(int src, int dest) {
        adjLists[src].add(dest);
        adjLists[dest].add(src); // 如果是有向图,这行可以去掉
    }

    // 打印图
    public void printGraph() {
        for (int v = 0; v < numVertices; v++) {
            System.out.print("Vertex " + v + ":");
            for (Integer pCrawl : adjLists[v]) {
                System.out.print(" -> " + pCrawl);
            }
            System.out.println("\n");
        }
    }

    public static void main(String[] args) {
        // 创建一个包含5个顶点的图
        Graph g = new Graph(5);

        // 添加边
        g.addEdge(0, 1);
        g.addEdge(0, 4);
        g.addEdge(1, 2);
        g.addEdge(1, 3);
        g.addEdge(1, 4);
        g.addEdge(2, 3);
        g.addEdge(3, 4);

        // 打印图
        g.printGraph();
    }
}

1.1代码说明:

  1. Graph:定义了一个图的数据结构。
    • numVertices:存储顶点的数量。
    • adjLists:使用一个链表数组来存储每个顶点的邻接表。
  2. 构造函数:初始化图的顶点数量和邻接表。
  3. addEdge方法:添加一条边到图中。对于无向图,需要将边添加到两个顶点的邻接表中;对于有向图,只需添加一次。
  4. printGraph方法:打印图的邻接表表示法。
  5. main方法:创建一个包含5个顶点的图,并添加一些边,最后打印图的邻接表。
这个实现是无向图的邻接表表示法。如果需要实现有向图,只需在addEdge方法中删除添加反向边的那行代码即可。

2基于Java语言,使用DFSBFS进行图的遍历。

Java中,可以使用深度优先搜索(DFS)和广度优先搜索(BFS)来遍历图。以下是使用邻接表表示法的图结构,并实现DFSBFS遍历的代码示例。

2.1图的邻接表表示法

首先,我们定义一个图的数据结构:
import java.util.*;

class Graph {
    private int numVertices; // 顶点的数量
    private LinkedList<Integer>[] adjLists; // 邻接表

    // 构造函数
    public Graph(int vertices) {
        numVertices = vertices;
        adjLists = new LinkedList[vertices];
        for (int i = 0; i < vertices; i++) {
            adjLists[i] = new LinkedList<>();
        }
    }

    // 添加边
    public void addEdge(int src, int dest) {
        adjLists[src].add(dest);
        adjLists[dest].add(src); // 如果是有向图,这行可以去掉
    }

    // 打印图
    public void printGraph() {
        for (int v = 0; v < numVertices; v++) {
            System.out.print("Vertex " + v + ":");
            for (Integer pCrawl : adjLists[v]) {
                System.out.print(" -> " + pCrawl);
            }
            System.out.println("\n");
        }
    }

    // DFS遍历
    public void DFS(int startVertex) {
        boolean[] visited = new boolean[numVertices];
        DFSUtil(startVertex, visited);
    }

    private void DFSUtil(int vertex, boolean[] visited) {
        visited[vertex] = true;
        System.out.print(vertex + " ");

        for (int adjVertex : adjLists[vertex]) {
            if (!visited[adjVertex]) {
                DFSUtil(adjVertex, visited);
            }
        }
    }

    // BFS遍历
    public void BFS(int startVertex) {
        boolean[] visited = new boolean[numVertices];
        LinkedList<Integer> queue = new LinkedList<>();

        visited[startVertex] = true;
        queue.add(startVertex);

        while (queue.size() != 0) {
            int vertex = queue.poll();
            System.out.print(vertex + " ");

            for (int adjVertex : adjLists[vertex]) {
                if (!visited[adjVertex]) {
                    visited[adjVertex] = true;
                    queue.add(adjVertex);
                }
            }
        }
    }

    public static void main(String[] args) {
        // 创建一个包含5个顶点的图
        Graph g = new Graph(5);

        // 添加边
        g.addEdge(0, 1);
        g.addEdge(0, 4);
        g.addEdge(1, 2);
        g.addEdge(1, 3);
        g.addEdge(1, 4);
        g.addEdge(2, 3);
        g.addEdge(3, 4);

        // 打印图
        g.printGraph();

        // DFS遍历
        System.out.println("DFS traversal starting from vertex 0:");
        g.DFS(0);
        System.out.println();

        // BFS遍历
        System.out.println("BFS traversal starting from vertex 0:");
        g.BFS(0);
        System.out.println();
    }
}

2.2代码说明:

  1. Graph:定义了一个图的数据结构。
    • numVertices:存储顶点的数量。
    • adjLists:使用一个链表数组来存储每个顶点的邻接表。
  2. 构造函数:初始化图的顶点数量和邻接表。
  3. addEdge方法:添加一条边到图中。对于无向图,需要将边添加到两个顶点的邻接表中;对于有向图,只需添加一次。
  4. printGraph方法:打印图的邻接表。
  5. DFS方法:从指定的起始顶点开始进行深度优先搜索遍历。
    • DFSUtil是一个辅助递归方法,用于实际执行DFS遍历。
  6. BFS方法:从指定的起始顶点开始进行广度优先搜索遍历。
    • 使用一个队列来实现BFS遍历。
  7. main方法:创建一个包含5个顶点的图,并添加一些边,最后分别进行DFSBFS遍历,并打印结果。
这个实现展示了如何使用DFSBFS遍历图,并且可以根据需要修改为有向图或无向图。

3基于Java语言,寻找图中的最短路径(如Dijkstra算法)。

Dijkstra算法是一种用于在加权图中找到从单个源点到所有其他顶点的最短路径的算法。以下是使用Java实现Dijkstra算法的示例代码:
import java.util.*;

class Graph {
    private int numVertices;
    private LinkedList<Edge>[] adjacencyList;

    static class Edge {
        int targetVertex;
        int weight;

        Edge(int targetVertex, int weight) {
            this.targetVertex = targetVertex;
            this.weight = weight;
        }
    }

    public Graph(int numVertices) {
        this.numVertices = numVertices;
        adjacencyList = new LinkedList[numVertices];
        for (int i = 0; i < numVertices; i++) {
            adjacencyList[i] = new LinkedList<>();
        }
    }

    public void addEdge(int source, int target, int weight) {
        adjacencyList[source].add(new Edge(target, weight));
    }

    public void dijkstra(int startVertex) {
        boolean[] visited = new boolean[numVertices];
        int[] distances = new int[numVertices];
        Arrays.fill(distances, Integer.MAX_VALUE);
        distances[startVertex] = 0;

        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(edge -> edge.weight));
        priorityQueue.add(new Edge(startVertex, 0));

        while (!priorityQueue.isEmpty()) {
            Edge currentEdge = priorityQueue.poll();
            int currentVertex = currentEdge.targetVertex;

            if (visited[currentVertex]) continue;
            visited[currentVertex] = true;

            for (Edge edge : adjacencyList[currentVertex]) {
                if (!visited[edge.targetVertex]) {
                    int newDistance = distances[currentVertex] + edge.weight;
                    if (newDistance < distances[edge.targetVertex]) {
                        distances[edge.targetVertex] = newDistance;
                        priorityQueue.add(new Edge(edge.targetVertex, newDistance));
                    }
                }
            }
        }

        printShortestPaths(distances, startVertex);
    }

    private void printShortestPaths(int[] distances, int startVertex) {
        System.out.println("Shortest paths from vertex " + startVertex + ":");
        for (int i = 0; i < distances.length; i++) {
            System.out.println("To vertex " + i + " is " + distances[i]);
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph(6);
        graph.addEdge(0, 1, 4);
        graph.addEdge(0, 2, 3);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 2);
        graph.addEdge(2, 3, 4);
        graph.addEdge(3, 4, 2);
        graph.addEdge(4, 5, 6);

        graph.dijkstra(0);
    }
}

3.1代码说明:

  1. Graph:表示一个加权图,包含顶点数和邻接表。
  2. Edge:表示图中的一条边,包括目标顶点和权重。
  3. addEdge方法:向图中添加一条边。
  4. dijkstra方法:实现Dijkstra算法,计算从起始顶点到所有其他顶点的最短路径。
  5. printShortestPaths方法:打印从起始顶点到所有其他顶点的最短路径距离。
  6. main方法:创建一个图并调用Dijkstra算法进行测试。

3.2运行结果:

程序将输出从起始顶点(例如0)到所有其他顶点的最短路径距离。

4基于Java语言,解决最小生成树问题(如Prim算法或Kruskal算法)。

最小生成树(Minimum Spanning Tree, MST)是图论中的一个重要问题,它指的是在加权无向图中找到一个包含所有顶点的子图,使得总权重最小。Prim算法和Kruskal算法是两种常用的求解MST的算法。以下是使用Java实现这两种算法的示例代码:

4.1Prim算法

import java.util.*;

class Edge implements Comparable<Edge> {
    int source;
    int destination;
    int weight;

    Edge(int source, int destination, int weight) {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }

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

class Graph {
    private int numVertices;
    private LinkedList<Edge>[] adjacencyList;

    public Graph(int numVertices) {
        this.numVertices = numVertices;
        adjacencyList = new LinkedList[numVertices];
        for (int i = 0; i < numVertices; i++) {
            adjacencyList[i] = new LinkedList<>();
        }
    }

    public void addEdge(int source, int destination, int weight) {
        adjacencyList[source].add(new Edge(source, destination, weight));
        adjacencyList[destination].add(new Edge(destination, source, weight)); // Since the graph is undirected
    }

    public void primMST() {
        boolean[] visited = new boolean[numVertices];
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>();
        int startVertex = 0;
        visited[startVertex] = true;

        for (Edge edge : adjacencyList[startVertex]) {
            priorityQueue.add(edge);
        }

        List<Edge> mst = new ArrayList<>();

        while (!priorityQueue.isEmpty()) {
            Edge currentEdge = priorityQueue.poll();
            int targetVertex = currentEdge.destination;

            if (visited[targetVertex]) continue;
            visited[targetVertex] = true;
            mst.add(currentEdge);

            for (Edge edge : adjacencyList[targetVertex]) {
                if (!visited[edge.destination]) {
                    priorityQueue.add(edge);
                }
            }
        }

        printMST(mst);
    }

    private void printMST(List<Edge> mst) {
        System.out.println("Edges in the MST:");
        for (Edge edge : mst) {
            System.out.println(edge.source + " - " + edge.destination + " : " + edge.weight);
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph(6);
        graph.addEdge(0, 1, 4);
        graph.addEdge(0, 2, 3);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 2);
        graph.addEdge(2, 3, 4);
        graph.addEdge(3, 4, 2);
        graph.addEdge(4, 5, 6);

        graph.primMST();
    }
}

4.2Kruskal算法

import java.util.*;

class Edge implements Comparable<Edge> {
    int source;
    int destination;
    int weight;

    Edge(int source, int destination, int weight) {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }

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

class UnionFind {
    private int[] parent;
    private int[] rank;

    public UnionFind(int size) {
        parent = new int[size];
        rank = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
            rank[i] = 0;
        }
    }

    public int find(int node) {
        if (parent[node] != node) {
            parent[node] = find(parent[node]); // Path compression
        }
        return parent[node];
    }

    public void union(int node1, int node2) {
        int root1 = find(node1);
        int root2 = find(node2);
        if (root1 != root2) {
            if (rank[root1] > rank[root2]) {
                parent[root2] = root1;
            } else if (rank[root1] < rank[root2]) {
                parent[root1] = root2;
            } else {
                parent[root2] = root1;
                rank[root1]++;
            }
        }
    }
}

class Graph {
    private int numVertices;
    private List<Edge> edges;

    public Graph(int numVertices) {
        this.numVertices = numVertices;
        edges = new ArrayList<>();
    }

    public void addEdge(int source, int destination, int weight) {
        edges.add(new Edge(source, destination, weight));
    }

    public void kruskalMST() {
        Collections.sort(edges); // Sort edges by weight
        UnionFind unionFind = new UnionFind(numVertices);
        List<Edge> mst = new ArrayList<>();

        for (Edge edge : edges) {
            int root1 = unionFind.find(edge.source);
            int root2 = unionFind.find(edge.destination);
            if (root1 != root2) {
                mst.add(edge);
                unionFind.union(root1, root2);
            }
        }

        printMST(mst);
    }

    private void printMST(List<Edge> mst) {
        System.out.println("Edges in the MST:");
        for (Edge edge : mst) {
            System.out.println(edge.source + " - " + edge.destination + " : " + edge.weight);
        }
    }

    public static void main(String[] args) {
        Graph graph = new Graph(6);
        graph.addEdge(0, 1, 4);
        graph.addEdge(0, 2, 3);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 2);
        graph.addEdge(2, 3, 4);
        graph.addEdge(3, 4, 2);
        graph.addEdge(4, 5, 6);

        graph.kruskalMST();
    }
}

4.3代码说明:


5基于Java语言,实现拓扑排序。

拓扑排序是一种对有向无环图(DAG)的顶点进行线性排序的方法,使得对于图中的每一条有向边 (u, v),顶点 u 在排序中都出现在顶点 v 之前。拓扑排序常用于任务调度、编译顺序等场景。
下面是一个基于Java语言实现拓扑排序的示例代码:
import java.util.*;

public class TopologicalSort {
    private int numVertices; // 图中的顶点数
    private LinkedList<Integer>[] adjacencyList; // 邻接表表示图

    // 构造函数
    public TopologicalSort(int numVertices) {
        this.numVertices = numVertices;
        adjacencyList = new LinkedList[numVertices];
        for (int i = 0; i < numVertices; i++) {
            adjacencyList[i] = new LinkedList<>();
        }
    }

    // 添加边到图中
    public void addEdge(int source, int destination) {
        adjacencyList[source].add(destination);
    }

    // 执行拓扑排序
    public List<Integer> topologicalSort() {
        Stack<Integer> stack = new Stack<>();
        boolean[] visited = new boolean[numVertices];

        // 对所有顶点进行DFS
        for (int i = 0; i < numVertices; i++) {
            if (!visited[i]) {
                topologicalSortUtil(i, visited, stack);
            }
        }

        // 将栈中的元素弹出并添加到结果列表中
        List<Integer> result = new ArrayList<>();
        while (!stack.isEmpty()) {
            result.add(stack.pop());
        }
        return result;
    }

    // 辅助函数:递归执行DFS
    private void topologicalSortUtil(int vertex, boolean[] visited, Stack<Integer> stack) {
        visited[vertex] = true;
        for (int neighbor : adjacencyList[vertex]) {
            if (!visited[neighbor]) {
                topologicalSortUtil(neighbor, visited, stack);
            }
        }
        stack.push(vertex); // 当前顶点的所有邻居都已访问,将其压入栈中
    }

    public static void main(String[] args) {
        TopologicalSort graph = new TopologicalSort(6);
        graph.addEdge(5, 2);
        graph.addEdge(5, 0);
        graph.addEdge(4, 0);
        graph.addEdge(4, 1);
        graph.addEdge(2, 3);
        graph.addEdge(3, 1);

        List<Integer> result = graph.topologicalSort();
        System.out.println("Topological Sort of the given graph:");
        for (int vertex : result) {
            System.out.print(vertex + " ");
        }
    }
}

5.1代码说明:

  1. 类定义TopologicalSort 类包含图的顶点数和邻接表。
  2. 构造函数:初始化图的顶点数和邻接表。
  3. addEdge 方法:添加一条从 sourcedestination 的有向边。
  4. topologicalSort 方法:执行拓扑排序,返回一个包含拓扑排序结果的列表。
  5. topologicalSortUtil 方法:辅助函数,使用深度优先搜索(DFS)来遍历图,并将顶点按完成时间的顺序压入栈中。
  6. main 方法:创建一个图并添加边,然后调用 topologicalSort 方法进行拓扑排序,并输出结果。
这个实现使用了深度优先搜索(DFS)来遍历图,并在回溯时将顶点压入栈中,从而得到拓扑排序的结果。

6基于Java语言,检测图中的环。

检测图中的环是一个经典的问题,通常可以通过深度优先搜索(DFS)来实现。在有向图中,如果存在一个顶点通过一系列边可以回到自身,则该图包含环。
下面是一个基于Java语言实现检测有向图中环的示例代码:
import java.util.*;

public class CycleDetection {
    private int numVertices; // 图中的顶点数
    private LinkedList<Integer>[] adjacencyList; // 邻接表表示图

    // 构造函数
    public CycleDetection(int numVertices) {
        this.numVertices = numVertices;
        adjacencyList = new LinkedList[numVertices];
        for (int i = 0; i < numVertices; i++) {
            adjacencyList[i] = new LinkedList<>();
        }
    }

    // 添加边到图中
    public void addEdge(int source, int destination) {
        adjacencyList[source].add(destination);
    }

    // 检测图中的环
    public boolean hasCycle() {
        boolean[] visited = new boolean[numVertices];
        boolean[] recStack = new boolean[numVertices];

        // 对所有顶点进行DFS
        for (int i = 0; i < numVertices; i++) {
            if (detectCycleUtil(i, visited, recStack)) {
                return true;
            }
        }
        return false;
    }

    // 辅助函数:递归执行DFS并检测环
    private boolean detectCycleUtil(int vertex, boolean[] visited, boolean[] recStack) {
        if (recStack[vertex]) {
            return true; // 如果当前顶点在递归栈中,说明存在环
        }
        if (visited[vertex]) {
            return false; // 如果当前顶点已经访问过且不在递归栈中,说明没有环
        }

        visited[vertex] = true;
        recStack[vertex] = true;

        for (int neighbor : adjacencyList[vertex]) {
            if (detectCycleUtil(neighbor, visited, recStack)) {
                return true;
            }
        }

        recStack[vertex] = false; // 回溯时将当前顶点从递归栈中移除
        return false;
    }

    public static void main(String[] args) {
        CycleDetection graph = new CycleDetection(4);
        graph.addEdge(0, 1);
        graph.addEdge(1, 2);
        graph.addEdge(2, 3);
        graph.addEdge(3, 1); // 添加一条形成环的边

        if (graph.hasCycle()) {
            System.out.println("Graph contains a cycle");
        } else {
            System.out.println("Graph does not contain any cycle");
        }
    }
}

6.1代码说明:

  1. 类定义CycleDetection 类包含图的顶点数和邻接表。
  2. 构造函数:初始化图的顶点数和邻接表。
  3. addEdge 方法:添加一条从 sourcedestination 的有向边。
  4. hasCycle 方法:检测图中是否存在环,返回布尔值。
  5. detectCycleUtil 方法:辅助函数,使用深度优先搜索(DFS)来遍历图,并在递归过程中检测环。使用两个数组 visitedrecStack 分别记录已访问的顶点和递归栈中的顶点。
  6. main 方法:创建一个图并添加边,然后调用 hasCycle 方法检测图中是否存在环,并输出结果。
这个实现使用了DFS来遍历图,并通过递归栈来检测环的存在。如果在递归过程中遇到已经在递归栈中的顶点,则说明存在环。

7基于Java语言,实现网络流算法(如Ford-Fulkerson算法、Edmonds-Karp算法等)。

网络流算法是解决最大流问题的一种方法,其中最著名的算法之一是Edmonds-Karp算法。Edmonds-Karp算法实际上是Ford-Fulkerson算法的一个具体实现,它使用广度优先搜索(BFS)来寻找增广路径,从而找到最大流。
下面是一个基于Java语言实现Edmonds-Karp算法的示例代码:
import java.util.*;

public class EdmondsKarp {
    private int numVertices; // 图中的顶点数
    private int[][] capacity; // 容量矩阵
    private int source, sink; // 源点和汇点

    // 构造函数
    public EdmondsKarp(int numVertices) {
        this.numVertices = numVertices;
        capacity = new int[numVertices][numVertices];
    }

    // 添加边到图中
    public void addEdge(int from, int to, int cap) {
        capacity[from][to] = cap;
    }

    // 执行Edmonds-Karp算法
    public int maxFlow(int source, int sink) {
        this.source = source;
        this.sink = sink;
        int[][] residualCapacity = new int[numVertices][numVertices];
        for (int i = 0; i < numVertices; i++) {
            System.arraycopy(capacity[i], 0, residualCapacity[i], 0, numVertices);
        }

        int maxFlow = 0;
        int[] parent = new int[numVertices];

        while (bfs(residualCapacity, parent)) {
            int pathFlow = Integer.MAX_VALUE;
            for (int v = sink; v != source; v = parent[v]) {
                int u = parent[v];
                pathFlow = Math.min(pathFlow, residualCapacity[u][v]);
            }

            for (int v = sink; v != source; v = parent[v]) {
                int u = parent[v];
                residualCapacity[u][v] -= pathFlow;
                residualCapacity[v][u] += pathFlow;
            }

            maxFlow += pathFlow;
        }

        return maxFlow;
    }

    // 辅助函数:使用广度优先搜索(BFS)查找增广路径
    private boolean bfs(int[][] residualCapacity, int[] parent) {
        boolean[] visited = new boolean[numVertices];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(source);
        visited[source] = true;
        parent[source] = -1;

        while (!queue.isEmpty()) {
            int u = queue.poll();

            for (int v = 0; v < numVertices; v++) {
                if (!visited[v] && residualCapacity[u][v] > 0) {
                    if (v == sink) {
                        parent[v] = u;
                        return true;
                    }
                    queue.add(v);
                    visited[v] = true;
                    parent[v] = u;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        EdmondsKarp graph = new EdmondsKarp(6);
        graph.addEdge(0, 1, 16);
        graph.addEdge(0, 2, 13);
        graph.addEdge(1, 2, 10);
        graph.addEdge(1, 3, 12);
        graph.addEdge(2, 1, 4);
        graph.addEdge(2, 4, 14);
        graph.addEdge(3, 2, 9);
        graph.addEdge(3, 5, 20);
        graph.addEdge(4, 3, 7);
        graph.addEdge(4, 5, 4);

        System.out.println("The maximum possible flow is " + graph.maxFlow(0, 5));
    }
}

7.1代码说明:

  1. 类定义EdmondsKarp 类包含图的顶点数、容量矩阵以及源点和汇点。
  2. 构造函数:初始化图的顶点数和容量矩阵。
  3. addEdge 方法:添加一条从 fromto 的有向边,并设置其容量为 cap
  4. maxFlow 方法:执行Edmonds-Karp算法,返回从源点到汇点的最大流量。该方法使用一个残差容量矩阵来跟踪剩余的容量。
  5. bfs 方法:辅助函数,使用广度优先搜索(BFS)查找增广路径。如果找到增广路径,则返回 true,否则返回 false
  6. main 方法:创建一个图并添加边,然后调用 maxFlow 方法计算从源点到汇点的最大流量,并输出结果。
这个实现使用了Edmonds-Karp算法,通过反复寻找增广路径并更新残差容量矩阵,最终找到从源点到汇点的最大流量。

8基于Java语言,解决欧拉路径和欧拉回路问题。

欧拉路径和欧拉回路是图论中的经典问题。欧拉路径是指经过图中每条边恰好一次的路径,而欧拉回路是指经过图中每条边恰好一次且回到起点的回路。

8.1欧拉路径和欧拉回路的条件:

  1. 无向图
    • 欧拉路径:图中最多有两个顶点的度数为奇数。
    • 欧拉回路:所有顶点的度数都是偶数。
  2. 有向图
    • 欧拉路径:最多有一个顶点的出度比入度大1,最多有一个顶点的入度比出度大1,其余顶点的出度等于入度。
    • 欧拉回路:所有顶点的出度等于入度。

8.2Java实现思路:

  1. 使用邻接表表示图。
  2. 检查每个顶点的度数(对于有向图,分别检查入度和出度)。
  3. 根据条件判断是否存在欧拉路径或欧拉回路。
  4. 如果存在欧拉路径或欧拉回路,使用Fleury算法或Hierholzer算法找到具体的路径。
下面是一个基于Java语言的示例代码,解决欧拉路径和欧拉回路问题:
import java.util.*;

public class EulerPathAndCircuit {
    private int numVertices; // 图中的顶点数
    private LinkedList<Integer>[] adjacencyList; // 邻接表

    // 构造函数
    @SuppressWarnings("unchecked")
    public EulerPathAndCircuit(int numVertices) {
        this.numVertices = numVertices;
        adjacencyList = new LinkedList[numVertices];
        for (int i = 0; i < numVertices; i++) {
            adjacencyList[i] = new LinkedList<>();
        }
    }

    // 添加边到图中
    public void addEdge(int v, int w) {
        adjacencyList[v].add(w);
        adjacencyList[w].add(v); // 无向图需要添加双向边
    }

    // 检查是否存在欧拉回路或欧拉路径
    public boolean hasEulerianPathOrCircuit() {
        int oddDegreeCount = 0;
        for (int i = 0; i < numVertices; i++) {
            if (adjacencyList[i].size() % 2 != 0) {
                oddDegreeCount++;
            }
        }
        return oddDegreeCount == 0 || oddDegreeCount == 2;
    }

    // 使用Hierholzer算法找到欧拉路径或欧拉回路
    public List<Integer> findEulerianPathOrCircuit() {
        if (!hasEulerianPathOrCircuit()) {
            return null; // 不存在欧拉路径或欧拉回路
        }

        Stack<Integer> stack = new Stack<>();
        LinkedList<Integer> path = new LinkedList<>();
        int currentVertex = 0; // 从第一个顶点开始

        stack.push(currentVertex);
        while (!stack.isEmpty()) {
            if (adjacencyList[currentVertex].size() > 0) {
                stack.push(currentVertex);
                int nextVertex = adjacencyList[currentVertex].removeFirst();
                adjacencyList[nextVertex].remove((Integer) currentVertex); // 删除反向边
                currentVertex = nextVertex;
            } else {
                path.add(currentVertex);
                currentVertex = stack.pop();
            }
        }
        return path;
    }

    public static void main(String[] args) {
        EulerPathAndCircuit graph = new EulerPathAndCircuit(5);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(1, 2);
        graph.addEdge(1, 3);
        graph.addEdge(2, 3);
        graph.addEdge(3, 4);
        graph.addEdge(4, 0);

        if (graph.hasEulerianPathOrCircuit()) {
            List<Integer> eulerianPath = graph.findEulerianPathOrCircuit();
            System.out.println("Eulerian Path or Circuit: " + eulerianPath);
        } else {
            System.out.println("No Eulerian Path or Circuit exists.");
        }
    }
}

8.3代码说明:

  1. 类定义EulerPathAndCircuit 类包含图的顶点数、邻接表以及相关方法。
  2. 构造函数:初始化图的顶点数和邻接表。
  3. addEdge 方法:添加一条无向边到图中。如果是有向图,需要分别添加正向和反向边。
  4. hasEulerianPathOrCircuit 方法:检查是否存在欧拉路径或欧拉回路。如果满足条件,返回 true,否则返回 false
  5. findEulerianPathOrCircuit 方法:使用Hierholzer算法找到欧拉路径或欧拉回路。如果不存在欧拉路径或欧拉回路,返回 null
  6. main 方法:创建一个图并添加边,然后调用上述方法检查并输出结果。
这个实现使用了Hierholzer算法来找到欧拉路径或欧拉回路,适用于无向图。对于有向图,可以类似地修改条件检查和路径查找逻辑。

9基于Java语言,实现匹配算法(如匈牙利算法、KM算法等)。

匈牙利算法(Hungarian Algorithm)是一种用于解决二分图最大匹配问题的算法。它通常用于任务分配问题,其中每个任务只能由一个工人完成,每个工人也只能完成一个任务。
下面是一个基于Java语言实现的匈牙利算法示例:
public class HungarianAlgorithm {
    private int[][] costMatrix;
    private int[] u, v, p, way;
    private boolean[] used;

    public HungarianAlgorithm(int[][] costMatrix) {
        this.costMatrix = costMatrix;
        u = new int[costMatrix.length + 1];
        v = new int[costMatrix[0].length + 1];
        p = new int[costMatrix[0].length + 1];
        way = new int[costMatrix.length + 1];
        used = new boolean[costMatrix.length + 1];
    }

    public int findMaxMatching() {
        for (int i = 1; i <= costMatrix.length; i++) {
            p[0] = i;
            int j0 = 0;
            Arrays.fill(used, false);
            int[] minv = new int[costMatrix.length + 1];
            Arrays.fill(minv, Integer.MAX_VALUE);
            do {
                used[j0] = true;
                int i0 = p[j0], delta = Integer.MAX_VALUE, j1 = 0;
                for (int j = 1; j <= costMatrix[0].length; j++) {
                    if (!used[j]) {
                        int cur = costMatrix[i0 - 1][j - 1] - u[i0] - v[j];
                        if (cur < minv[j]) {
                            minv[j] = cur;
                            way[j] = j0;
                        }
                        if (minv[j] < delta) {
                            delta = minv[j];
                            j1 = j;
                        }
                    }
                }
                for (int j = 0; j <= costMatrix[0].length; j++) {
                    if (used[j]) {
                        u[p[j]] += delta;
                        v[j] -= delta;
                    } else {
                        minv[j] -= delta;
                    }
                }
                j0 = j1;
            } while (p[j0] != 0);
            do {
                int j1 = way[j0];
                p[j0] = p[j1];
                j0 = j1;
            } while (j0 != 0);
        }
        return -v[0];
    }

    public static void main(String[] args) {
        int[][] costMatrix = {
            {4, 2, 8},
            {4, 3, 7},
            {3, 5, 6}
        };
        HungarianAlgorithm ha = new HungarianAlgorithm(costMatrix);
        System.out.println("Maximum Matching Cost: " + ha.findMaxMatching());
    }
}
在这个例子中,我们定义了一个HungarianAlgorithm类,它接受一个成本矩阵作为输入,并实现了匈牙利算法来找到最大匹配的成本。在主函数中,我们创建了一个实例并调用findMaxMatching方法来获取结果。

10基于Java语言,解决独立集问题。

独立集问题(Independent Set Problem)是图论中的一个经典问题,它要求在一个图中找到一个最大的顶点集合,使得集合中的任意两个顶点都不相邻。这个问题在计算机科学中有很多应用,比如任务调度、网络设计等。
下面是一个基于Java语言实现的解决独立集问题的示例代码:
import java.util.*;

public class IndependentSet {
    private int V; // 顶点数
    private LinkedList<Integer>[] adj; // 邻接表

    // 构造函数
    @SuppressWarnings("unchecked")
    public IndependentSet(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; ++i)
            adj[i] = new LinkedList();
    }

    // 添加边到图中
    void addEdge(int v, int w) {
        adj[v].add(w);
        adj[w].add(v);
    }

    // 使用贪心算法找到最大独立集
    public List<Integer> findMaximalIndependentSet() {
        boolean[] visited = new boolean[V];
        Arrays.fill(visited, false);
        List<Integer> independentSet = new ArrayList<>();

        for (int u = 0; u < V; u++) {
            if (!visited[u]) {
                independentSet.add(u);
                visited[u] = true;
                // 标记所有与u相邻的顶点为已访问
                for (int v : adj[u]) {
                    visited[v] = true;
                }
            }
        }
        return independentSet;
    }

    public static void main(String[] args) {
        IndependentSet g = new IndependentSet(5);
        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 3);
        g.addEdge(1, 4);
        g.addEdge(2, 4);

        List<Integer> maximalIndependentSet = g.findMaximalIndependentSet();
        System.out.println("Maximum Independent Set: " + maximalIndependentSet);
    }
}

10.1代码说明:

  1. 类定义IndependentSet 类包含顶点数 V 和邻接表 adj
  2. 构造函数:初始化顶点数和邻接表。
  3. addEdge 方法:添加一条无向边到图中。
  4. findMaximalIndependentSet 方法:使用贪心算法找到最大独立集。对于每个未访问的顶点,将其添加到独立集中,并标记所有相邻的顶点为已访问。
  5. main 方法:创建一个图实例,添加边,然后调用 findMaximalIndependentSet 方法来获取结果并打印。
这个实现使用了贪心算法来近似解决独立集问题,虽然不能保证找到最优解,但在很多情况下可以得到一个较好的近似解。对于更精确的解决方案,可能需要使用更高级的算法或启发式方法。

11基于Java语言,实现最大流和最小割算法(如Dinic算法、Ford-Fulkerson算法等)。

最大流问题和最小割问题是网络流理论中的基本问题,它们在许多实际应用中都有重要用途。Dinic算法是一种高效的求解最大流问题的算法,它基于分层图的思想,通过多次增广路径来逐步增加流量。
下面是一个基于Java语言实现的Dinic算法示例代码:
import java.util.*;

public class DinicAlgorithm {
    private static final int INF = Integer.MAX_VALUE;
    private int[] level, ptr;
    private List<Integer>[] graph;
    private Edge[] edges;

    static class Edge {
        int to, rev, flow, cap;
        Edge(int to, int rev, int cap) {
            this.to = to;
            this.rev = rev;
            this.cap = cap;
            this.flow = 0;
        }
    }

    @SuppressWarnings("unchecked")
    public DinicAlgorithm(int n) {
        graph = new List[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
    }

    public void addEdge(int from, int to, int cap) {
        Edge e1 = new Edge(to, graph[to].size(), cap);
        Edge e2 = new Edge(from, graph[from].size(), 0); // reverse edge with 0 capacity
        graph[from].add(e1);
        graph[to].add(e2);
    }

    private boolean bfs(int s, int t) {
        level = new int[graph.length];
        Arrays.fill(level, -1);
        level[s] = 0;
        Queue<Integer> queue = new LinkedList<>();
        queue.add(s);
        while (!queue.isEmpty()) {
            int u = queue.poll();
            for (Edge e : graph[u]) {
                if (level[e.to] < 0 && e.flow < e.cap) {
                    level[e.to] = level[u] + 1;
                    queue.add(e.to);
                }
            }
        }
        return level[t] >= 0;
    }

    private int dfs(int u, int flow, int t) {
        if (u == t) return flow;
        for (; ptr[u] < graph[u].size(); ++ptr[u]) {
            Edge e = graph[u].get(ptr[u]);
            if (level[e.to] == level[u] + 1 && e.flow < e.cap) {
                int pushed = dfs(e.to, Math.min(flow, e.cap - e.flow), t);
                if (pushed > 0) {
                    e.flow += pushed;
                    graph[e.to].get(e.rev).flow -= pushed;
                    return pushed;
                }
            }
        }
        return 0;
    }

    public int maxFlow(int s, int t) {
        int totalFlow = 0;
        while (bfs(s, t)) {
            ptr = new int[graph.length];
            while (true) {
                int flow = dfs(s, INF, t);
                if (flow == 0) break;
                totalFlow += flow;
            }
        }
        return totalFlow;
    }

    public static void main(String[] args) {
        DinicAlgorithm dinic = new DinicAlgorithm(6);
        dinic.addEdge(0, 1, 16);
        dinic.addEdge(0, 2, 13);
        dinic.addEdge(1, 2, 10);
        dinic.addEdge(1, 3, 12);
        dinic.addEdge(2, 4, 14);
        dinic.addEdge(3, 5, 20);
        dinic.addEdge(4, 3, 7);
        dinic.addEdge(4, 5, 4);

        System.out.println("Maximum Flow: " + dinic.maxFlow(0, 5));
    }
}

11.1代码说明:

  1. 类定义DinicAlgorithm 类包含图的表示、边的表示以及相关方法。
  2. 构造函数:初始化图的邻接表。
  3. addEdge 方法:添加一条边到图中,包括正向边和反向边(容量为0)。
  4. bfs 方法:使用广度优先搜索构建分层图,确定每个节点的层次。
  5. dfs 方法:深度优先搜索寻找增广路径,并更新流量。
  6. maxFlow 方法:主方法,通过多次调用 bfsdfs 来计算最大流。
  7. main 方法:创建一个图实例,添加边,然后调用 maxFlow 方法来获取结果并打印。
这个实现展示了如何使用 Dinic 算法来解决最大流问题。Dinic 算法的时间复杂度为 O(V^2 * E),适用于大多数实际应用场景。

12基于Java语言,解决旅行商问题(TSP)。

旅行商问题(Traveling Salesman Problem, TSP)是一个经典的组合优化问题,其目标是找到一条经过所有给定城市且每个城市只访问一次的最短路径。由于TSPNP难问题,对于大规模实例,精确算法往往不可行,因此通常使用近似算法或启发式方法来求解。
下面是一个基于Java语言实现的简单贪心算法来解决TSP问题的示例代码:
import java.util.*;

public class TSPGreedy {
    private int numberOfCities;
    private int[][] distanceMatrix;
    private boolean[] visited;
    private List<Integer> tour = new ArrayList<>();
    private double totalDistance = 0;

    public TSPGreedy(int numberOfCities, int[][] distanceMatrix) {
        this.numberOfCities = numberOfCities;
        this.distanceMatrix = distanceMatrix;
        this.visited = new boolean[numberOfCities];
    }

    public void solve() {
        // Start from the first city (index 0)
        int currentCity = 0;
        tour.add(currentCity);
        visited[currentCity] = true;

        for (int i = 1; i < numberOfCities; i++) {
            int nextCity = findNearestUnvisitedCity(currentCity);
            tour.add(nextCity);
            visited[nextCity] = true;
            totalDistance += distanceMatrix[currentCity][nextCity];
            currentCity = nextCity;
        }

        // Return to the starting city
        totalDistance += distanceMatrix[currentCity][tour.get(0)];
        tour.add(tour.get(0)); // Adding the start city again to complete the cycle
    }

    private int findNearestUnvisitedCity(int currentCity) {
        int nearestCity = -1;
        int minDistance = Integer.MAX_VALUE;

        for (int i = 0; i < numberOfCities; i++) {
            if (!visited[i] && distanceMatrix[currentCity][i] < minDistance) {
                nearestCity = i;
                minDistance = distanceMatrix[currentCity][i];
            }
        }

        return nearestCity;
    }

    public List<Integer> getTour() {
        return tour;
    }

    public double getTotalDistance() {
        return totalDistance;
    }

    public static void main(String[] args) {
        int numberOfCities = 5;
        int[][] distanceMatrix = {
            {0, 29, 20, 21, 16},
            {29, 0, 15, 17, 28},
            {20, 15, 0, 22, 27},
            {21, 17, 22, 0, 10},
            {16, 28, 27, 10, 0}
        };

        TSPGreedy tspGreedy = new TSPGreedy(numberOfCities, distanceMatrix);
        tspGreedy.solve();

        System.out.println("Tour: " + tspGreedy.getTour());
        System.out.println("Total Distance: " + tspGreedy.getTotalDistance());
    }
}

12.1代码说明:

  1. 类定义TSPGreedy 类包含解决TSP问题所需的数据和方法。
  2. 构造函数:初始化城市数量、距离矩阵和访问标记数组。
  3. solve 方法:使用贪心算法构建一个近似解。从第一个城市开始,每次选择最近的未访问城市,直到所有城市都被访问。最后返回到起始城市以完成循环。
  4. findNearestUnvisitedCity 方法:在当前城市的所有未访问城市中寻找最近的城市。
  5. getTour 方法:返回生成的路径。
  6. getTotalDistance 方法:返回生成路径的总距离。
  7. main 方法:创建一个示例距离矩阵并调用 solve 方法,然后输出结果。
这个实现展示了如何使用贪心算法解决TSP问题。虽然贪心算法不能保证找到最优解,但它通常能在合理的时间内找到一个较好的近似解。对于更复杂和大规模的TSP问题,可以考虑使用更高级的启发式或元启发式算法,如遗传算法、模拟退火等。

13基于Java语言,实现A*寻路算法。

A*(A-star)算法是一种用于图搜索的启发式算法,常用于路径规划和导航。它结合了Dijkstra算法的优点和贪心最佳优先搜索的优点,通过使用启发函数来估计从当前节点到目标节点的代价,从而更高效地找到最短路径。
下面是一个基于Java语言实现A*寻路算法的示例代码:
import java.util.*;

class Node implements Comparable<Node> {
    int x, y;
    double gCost, hCost, fCost;
    Node parent;

    public Node(int x, int y) {
        this.x = x;
        this.y = y;
        this.gCost = 0;
        this.hCost = 0;
        this.fCost = 0;
        this.parent = null;
    }

    @Override
    public int compareTo(Node other) {
        return Double.compare(this.fCost, other.fCost);
    }
}

public class AStarPathfinding {
    private static final int[][] DIRECTIONS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // Right, Down, Left, Up

    public static List<Node> findPath(int[][] grid, Node start, Node goal) {
        PriorityQueue<Node> openList = new PriorityQueue<>();
        Set<Node> closedList = new HashSet<>();

        start.gCost = 0;
        start.hCost = heuristic(start, goal);
        start.fCost = start.gCost + start.hCost;
        openList.add(start);

        while (!openList.isEmpty()) {
            Node current = openList.poll();
            if (current.x == goal.x && current.y == goal.y) {
                return reconstructPath(current);
            }

            closedList.add(current);

            for (int[] direction : DIRECTIONS) {
                int newX = current.x + direction[0];
                int newY = current.y + direction[1];

                if (isValid(grid, newX, newY) && grid[newX][newY] == 0) {
                    Node neighbor = new Node(newX, newY);
                    if (closedList.contains(neighbor)) {
                        continue;
                    }

                    double tentativeGCost = current.gCost + 1; // Assuming cost between adjacent nodes is 1
                    boolean inOpenList = openList.contains(neighbor);

                    if (!inOpenList || tentativeGCost < neighbor.gCost) {
                        neighbor.parent = current;
                        neighbor.gCost = tentativeGCost;
                        neighbor.hCost = heuristic(neighbor, goal);
                        neighbor.fCost = neighbor.gCost + neighbor.hCost;

                        if (!inOpenList) {
                            openList.add(neighbor);
                        }
                    }
                }
            }
        }
        return Collections.emptyList(); // No path found
    }

    private static boolean isValid(int[][] grid, int x, int y) {
        return x >= 0 && y >= 0 && x < grid.length && y < grid[0].length;
    }

    private static double heuristic(Node a, Node b) {
        // Using Manhattan distance as the heuristic function
        return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
    }

    private static List<Node> reconstructPath(Node node) {
        List<Node> path = new ArrayList<>();
        while (node != null) {
            path.add(node);
            node = node.parent;
        }
        Collections.reverse(path);
        return path;
    }

    public static void main(String[] args) {
        int[][] grid = {
            {0, 1, 0, 0, 0},
            {0, 1, 0, 1, 0},
            {0, 0, 0, 1, 0},
            {0, 1, 1, 1, 0},
            {0, 0, 0, 0, 0}
        };

        Node start = new Node(0, 0);
        Node goal = new Node(4, 4);

        List<Node> path = findPath(grid, start, goal);
        if (path.isEmpty()) {
            System.out.println("No path found");
        } else {
            System.out.println("Path found:");
            for (Node node : path) {
                System.out.println("(" + node.x + ", " + node.y + ")");
            }
        }
    }
}

13.1代码说明:

  1. Node:表示网格中的一个节点,包含坐标、代价(gCost、hCost、fCost)、父节点以及比较方法。
  2. AStarPathfinding:包含A*算法的主要逻辑。
  3. findPath方法:执行A*算法,返回从起点到终点的路径。如果找不到路径,则返回空列表。
  4. isValid方法:检查给定坐标是否在网格范围内且可通行。
  5. heuristic方法:计算启发函数值,这里使用的是曼哈顿距离。
  6. reconstructPath方法:根据父节点信息重建路径。
  7. main方法:定义一个示例网格并调用findPath方法,输出结果。
这个实现展示了如何使用A*算法在一个二维网格中寻找从起点到终点的最短路径。可以根据具体需求调整启发函数和其他参数。

14基于Java语言,解决最大匹配问题。

最大匹配问题(Maximum Matching Problem)是图论中的一个经典问题,通常用于解决二分图的最大匹配。在二分图中,节点被分成两个不相交的集合,边只连接这两个集合中的节点。
下面是一个基于Java语言的解决方案,使用匈牙利算法来解决最大匹配问题。匈牙利算法是一种经典的求解二分图最大匹配的算法。
import java.util.*;

public class BipartiteMatching {
    private int[] match; // 记录每个右集合节点的匹配情况
    private boolean[] visited; // 记录访问状态
    private List<Integer>[] graph; // 邻接表表示的图

    public BipartiteMatching(int n) {
        match = new int[n];
        Arrays.fill(match, -1);
        visited = new boolean[n];
        graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
    }

    // 添加边
    public void addEdge(int u, int v) {
        graph[u].add(v);
    }

    // 匈牙利算法核心部分:尝试为左集合节点u找到一个匹配
    private boolean dfs(int u) {
        for (int v : graph[u]) {
            if (!visited[v]) {
                visited[v] = true;
                if (match[v] == -1 || dfs(match[v])) {
                    match[v] = u;
                    return true;
                }
            }
        }
        return false;
    }

    // 计算最大匹配数
    public int maxMatching() {
        int result = 0;
        for (int u = 0; u < graph.length; u++) {
            Arrays.fill(visited, false);
            if (dfs(u)) {
                result++;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        // 示例:创建一个包含5个节点的二分图
        BipartiteMatching bipartiteMatching = new BipartiteMatching(5);
        bipartiteMatching.addEdge(0, 1);
        bipartiteMatching.addEdge(0, 2);
        bipartiteMatching.addEdge(1, 2);
        bipartiteMatching.addEdge(1, 3);
        bipartiteMatching.addEdge(2, 4);

        // 计算并输出最大匹配数
        int maxMatch = bipartiteMatching.maxMatching();
        System.out.println("最大匹配数: " + maxMatch);
    }
}

14.1代码说明:

  1. BipartiteMatching:该类封装了二分图和匈牙利算法的实现。
    • match数组记录每个右集合节点的匹配情况。
    • visited数组记录DFS过程中节点的访问状态。
    • graph是邻接表表示的图。
  2. addEdge方法:用于向图中添加边。
  3. dfs方法:深度优先搜索,尝试为左集合节点找到匹配。如果成功,则更新匹配信息。
  4. maxMatching方法:遍历所有左集合节点,调用DFS寻找匹配,并统计最大匹配数。
  5. main方法:示例代码,创建了一个包含5个节点的二分图,并计算其最大匹配数。
这个解决方案适用于一般的二分图最大匹配问题,可以根据具体需求进行扩展和优化。

15基于Java语言,解决最小顶点覆盖问题。

最小顶点覆盖问题(Minimum Vertex Cover Problem)是图论中的一个经典问题,目标是找到最小的顶点集合,使得每条边至少有一个端点在该集合中。这个问题是NP完全问题,因此没有已知的多项式时间算法来解决所有情况。
然而,对于二分图,最小顶点覆盖问题可以通过最大匹配问题来简化解决。具体来说,对于一个二分图,最小顶点覆盖的大小等于最大匹配的大小。这是因为在二分图中,每个顶点要么在最大匹配中被覆盖,要么不在最大匹配中被覆盖。
下面是一个基于Java语言的解决方案,使用匈牙利算法来解决二分图的最大匹配问题,从而间接解决最小顶点覆盖问题。
import java.util.*;

public class BipartiteMatching {
    private int[] match; // 记录每个右集合节点的匹配情况
    private boolean[] visited; // 记录访问状态
    private List<Integer>[] graph; // 邻接表表示的图

    public BipartiteMatching(int n) {
        match = new int[n];
        Arrays.fill(match, -1);
        visited = new boolean[n];
        graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
    }

    // 添加边
    public void addEdge(int u, int v) {
        graph[u].add(v);
    }

    // 匈牙利算法核心部分:尝试为左集合节点u找到一个匹配
    private boolean dfs(int u) {
        for (int v : graph[u]) {
            if (!visited[v]) {
                visited[v] = true;
                if (match[v] == -1 || dfs(match[v])) {
                    match[v] = u;
                    return true;
                }
            }
        }
        return false;
    }

    // 计算最大匹配数
    public int maxMatching() {
        int result = 0;
        for (int u = 0; u < graph.length; u++) {
            Arrays.fill(visited, false);
            if (dfs(u)) {
                result++;
            }
        }
        return result;
    }

    // 计算最小顶点覆盖数
    public int minVertexCover() {
        int maxMatch = maxMatching();
        return graph.length - maxMatch;
    }

    public static void main(String[] args) {
        // 示例:创建一个包含5个节点的二分图
        BipartiteMatching bipartiteMatching = new BipartiteMatching(5);
        bipartiteMatching.addEdge(0, 1);
        bipartiteMatching.addEdge(0, 2);
        bipartiteMatching.addEdge(1, 2);
        bipartiteMatching.addEdge(1, 3);
        bipartiteMatching.addEdge(2, 4);

        // 计算并输出最小顶点覆盖数
        int minVertexCover = bipartiteMatching.minVertexCover();
        System.out.println("最小顶点覆盖数: " + minVertexCover);
    }
}

15.1代码说明:

  1. BipartiteMatching:该类封装了二分图和匈牙利算法的实现。
    • match数组记录每个右集合节点的匹配情况。
    • visited数组记录DFS过程中节点的访问状态。
    • graph是邻接表表示的图。
  2. addEdge方法:用于向图中添加边。
  3. dfs方法:深度优先搜索,尝试为左集合节点找到匹配。如果成功,则更新匹配信息。
  4. maxMatching方法:遍历所有左集合节点,调用DFS寻找匹配,并统计最大匹配数。
  5. minVertexCover方法:通过最大匹配数计算最小顶点覆盖数。对于二分图,最小顶点覆盖数等于总节点数减去最大匹配数。
  6. main方法:示例代码,创建了一个包含5个节点的二分图,并计算其最小顶点覆盖数。
这个解决方案适用于一般的二分图最小顶点覆盖问题,可以根据具体需求进行扩展和优化。

16基于Java语言,实现最小连通支配集问题。

最小连通支配集问题(Minimum Connected Dominating Set Problem)是图论中的一个经典问题,目标是找到一个最小的节点集合,使得每个节点要么在这个集合中,要么与这个集合中的某个节点相邻。这个问题也是NP完全问题,因此没有已知的多项式时间算法来解决所有情况。
下面是一个基于Java语言的解决方案,使用回溯法来尝试找到最小连通支配集。这种方法虽然不是最优的,但可以用于小规模图的求解。
import java.util.*;

public class MinConnectedDominatingSet {
    private int n; // 节点数
    private List<Integer>[] graph; // 邻接表表示的图
    private boolean[] visited; // 访问状态
    private int minSize; // 最小连通支配集的大小
    private Set<Integer> bestSet; // 最佳连通支配集

    public MinConnectedDominatingSet(int n) {
        this.n = n;
        graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        visited = new boolean[n];
        minSize = Integer.MAX_VALUE;
        bestSet = new HashSet<>();
    }

    // 添加边
    public void addEdge(int u, int v) {
        graph[u].add(v);
        graph[v].add(u);
    }

    // 检查当前集合是否为连通支配集
    private boolean isConnectedDominatingSet(Set<Integer> set) {
        boolean[] covered = new boolean[n];
        for (int node : set) {
            covered[node] = true;
            for (int neighbor : graph[node]) {
                covered[neighbor] = true;
            }
        }
        for (boolean b : covered) {
            if (!b) return false;
        }
        return true;
    }

    // 回溯法寻找最小连通支配集
    private void backtrack(Set<Integer> currentSet, int start) {
        if (isConnectedDominatingSet(currentSet)) {
            if (currentSet.size() < minSize) {
                minSize = currentSet.size();
                bestSet = new HashSet<>(currentSet);
            }
            return;
        }
        for (int i = start; i < n; i++) {
            currentSet.add(i);
            backtrack(currentSet, i + 1);
            currentSet.remove(i);
        }
    }

    // 计算并返回最小连通支配集
    public Set<Integer> findMinConnectedDominatingSet() {
        backtrack(new HashSet<>(), 0);
        return bestSet;
    }

    public static void main(String[] args) {
        // 示例:创建一个包含6个节点的图
        MinConnectedDominatingSet mcds = new MinConnectedDominatingSet(6);
        mcds.addEdge(0, 1);
        mcds.addEdge(0, 2);
        mcds.addEdge(1, 3);
        mcds.addEdge(2, 4);
        mcds.addEdge(3, 5);
        mcds.addEdge(4, 5);

        // 计算并输出最小连通支配集
        Set<Integer> minCDS = mcds.findMinConnectedDominatingSet();
        System.out.println("最小连通支配集: " + minCDS);
    }
}

16.1代码说明:

  1. MinConnectedDominatingSet:该类封装了图的表示和求解最小连通支配集的方法。
    • n:节点数。
    • graph:邻接表表示的图。
    • visited:访问状态数组。
    • minSize:记录最小连通支配集的大小。
    • bestSet:记录最佳连通支配集。
  2. addEdge方法:用于向图中添加边。
  3. isConnectedDominatingSet方法:检查当前集合是否为连通支配集。
  4. backtrack方法:使用回溯法尝试找到最小连通支配集。如果当前集合是连通支配集且大小小于已知最小值,则更新最小值和最佳集合。
  5. findMinConnectedDominatingSet方法:调用回溯法,返回最小连通支配集。
  6. main方法:示例代码,创建了一个包含6个节点的图,并计算其最小连通支配集。
这个解决方案适用于小规模图的求解,可以根据具体需求进行扩展和优化。对于大规模图,可以考虑使用启发式算法或近似算法来提高效率。

17基于Java语言,解决最大权独立集问题。

最大权独立集问题(Maximum Weight Independent Set Problem)是图论中的一个经典问题,目标是找到一个节点集合,使得该集合中的任意两个节点都不相邻,并且这个集合的权重和最大。这个问题也是NP完全问题,因此没有已知的多项式时间算法来解决所有情况。
下面是一个基于Java语言的解决方案,使用回溯法来尝试找到最大权独立集。这种方法虽然不是最优的,但可以用于小规模图的求解。
import java.util.*;

public class MaxWeightIndependentSet {
    private int n; // 节点数
    private List<Integer>[] graph; // 邻接表表示的图
    private int[] weights; // 每个节点的权重
    private boolean[] visited; // 访问状态
    private int maxWeight; // 最大权独立集的权重和
    private Set<Integer> bestSet; // 最佳权独立集

    public MaxWeightIndependentSet(int n, int[] weights) {
        this.n = n;
        this.weights = weights;
        graph = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new ArrayList<>();
        }
        visited = new boolean[n];
        maxWeight = Integer.MIN_VALUE;
        bestSet = new HashSet<>();
    }

    // 添加边
    public void addEdge(int u, int v) {
        graph[u].add(v);
        graph[v].add(u);
    }

    // 检查当前集合是否为独立集
    private boolean isIndependentSet(Set<Integer> set) {
        for (int node : set) {
            for (int neighbor : graph[node]) {
                if (set.contains(neighbor)) return false;
            }
        }
        return true;
    }

    // 计算当前集合的权重和
    private int calculateWeight(Set<Integer> set) {
        int weightSum = 0;
        for (int node : set) {
            weightSum += weights[node];
        }
        return weightSum;
    }

    // 回溯法寻找最大权独立集
    private void backtrack(Set<Integer> currentSet, int start) {
        if (isIndependentSet(currentSet)) {
            int currentWeight = calculateWeight(currentSet);
            if (currentWeight > maxWeight) {
                maxWeight = currentWeight;
                bestSet = new HashSet<>(currentSet);
            }
        }
        for (int i = start; i < n; i++) {
            currentSet.add(i);
            backtrack(currentSet, i + 1);
            currentSet.remove(i);
        }
    }

    // 计算并返回最大权独立集
    public Set<Integer> findMaxWeightIndependentSet() {
        backtrack(new HashSet<>(), 0);
        return bestSet;
    }

    public static void main(String[] args) {
        // 示例:创建一个包含5个节点的图,并设置每个节点的权重
        int[] weights = {3, 2, 5, 7, 4};
        MaxWeightIndependentSet mwis = new MaxWeightIndependentSet(5, weights);
        mwis.addEdge(0, 1);
        mwis.addEdge(0, 2);
        mwis.addEdge(1, 3);
        mwis.addEdge(2, 4);

        // 计算并输出最大权独立集及其权重和
        Set<Integer> maxWIS = mwis.findMaxWeightIndependentSet();
        System.out.println("最大权独立集: " + maxWIS);
        System.out.println("最大权独立集的权重和: " + mwis.calculateWeight(maxWIS));
    }
}

17.1代码说明:

  1. MaxWeightIndependentSet:该类封装了图的表示、节点权重以及求解最大权独立集的方法。
    • n:节点数。
    • graph:邻接表表示的图。
    • weights:每个节点的权重。
    • visited:访问状态数组。
    • maxWeight:记录最大权独立集的权重和。
    • bestSet:记录最佳权独立集。
  2. addEdge方法:用于向图中添加边。
  3. isIndependentSet方法:检查当前集合是否为独立集。
  4. calculateWeight方法:计算当前集合的权重和。
  5. backtrack方法:使用回溯法尝试找到最大权独立集。如果当前集合是独立集且其权重和大于已知最大值,则更新最大值和最佳集合。
  6. findMaxWeightIndependentSet方法:调用回溯法,返回最大权独立集。
  7. main方法:示例代码,创建了一个包含5个节点的图,并设置每个节点的权重,然后计算并输出最大权独立集及其权重和。
这个解决方案适用于小规模图的求解,可以根据具体需求进行扩展和优化。对于大规模图,可以考虑使用启发式算法或近似算法来提高效率。

18基于Java语言,实现最大割问题。

最大割问题(Maximum Cut Problem)是图论中的一个经典问题,目标是找到一个切割,使得被切割的边权重和最大。这个问题也是NP完全问题,因此没有已知的多项式时间算法来解决所有情况。
下面是一个基于Java语言的解决方案,使用回溯法来尝试找到最大割。这种方法虽然不是最优的,但可以用于小规模图的求解。
import java.util.*;

public class MaxCut {
    private int n; // 节点数
    private List<int[]> edges; // 边的列表,每个元素是一个包含三个整数的数组 [u, v, w],表示边 (u, v) 的权重 w
    private boolean[] visited; // 访问状态
    private int maxCutWeight; // 最大割的权重和
    private Set<Integer> bestSetA; // 最佳集合 A
    private Set<Integer> bestSetB; // 最佳集合 B

    public MaxCut(int n) {
        this.n = n;
        edges = new ArrayList<>();
        visited = new boolean[n];
        maxCutWeight = Integer.MIN_VALUE;
        bestSetA = new HashSet<>();
        bestSetB = new HashSet<>();
    }

    // 添加边
    public void addEdge(int u, int v, int w) {
        edges.add(new int[]{u, v, w});
    }

    // 计算当前切割的权重和
    private int calculateCutWeight(Set<Integer> setA, Set<Integer> setB) {
        int weightSum = 0;
        for (int[] edge : edges) {
            int u = edge[0];
            int v = edge[1];
            int w = edge[2];
            if (setA.contains(u) && setB.contains(v)) {
                weightSum += w;
            } else if (setA.contains(v) && setB.contains(u)) {
                weightSum += w;
            }
        }
        return weightSum;
    }

    // 回溯法寻找最大割
    private void backtrack(Set<Integer> setA, Set<Integer> setB, int start) {
        if (start == n) {
            int currentWeight = calculateCutWeight(setA, setB);
            if (currentWeight > maxCutWeight) {
                maxCutWeight = currentWeight;
                bestSetA = new HashSet<>(setA);
                bestSetB = new HashSet<>(setB);
            }
            return;
        }
        setA.add(start);
        backtrack(setA, setB, start + 1);
        setA.remove(start);
        setB.add(start);
        backtrack(setA, setB, start + 1);
        setB.remove(start);
    }

    // 计算并返回最大割
    public int findMaxCut() {
        Set<Integer> setA = new HashSet<>();
        Set<Integer> setB = new HashSet<>();
        backtrack(setA, setB, 0);
        return maxCutWeight;
    }

    public static void main(String[] args) {
        // 示例:创建一个包含4个节点的图,并设置每条边的权重
        MaxCut maxCut = new MaxCut(4);
        maxCut.addEdge(0, 1, 3);
        maxCut.addEdge(0, 2, 2);
        maxCut.addEdge(1, 2, 1);
        maxCut.addEdge(1, 3, 4);
        maxCut.addEdge(2, 3, 5);

        // 计算并输出最大割及其权重和
        int maxCutWeight = maxCut.findMaxCut();
        System.out.println("最大割的权重和: " + maxCutWeight);
    }
}

18.1代码说明:

  1. MaxCut:该类封装了图的表示、边的权重以及求解最大割的方法。
    • n:节点数。
    • edges:边的列表,每个元素是一个包含三个整数的数组 [u, v, w],表示边 (u, v) 的权重 w
    • visited:访问状态数组。
    • maxCutWeight:记录最大割的权重和。
    • bestSetAbestSetB:记录最佳集合 A 和 B。
  2. addEdge方法:用于向图中添加边。
  3. calculateCutWeight方法:计算当前切割的权重和。
  4. backtrack方法:使用回溯法尝试找到最大割。如果当前切割的权重和大于已知最大值,则更新最大值和最佳集合。
  5. findMaxCut方法:调用回溯法,返回最大割的权重和。
  6. main方法:示例代码,创建了一个包含4个节点的图,并设置每条边的权重,然后计算并输出最大割及其权重和。
这个解决方案适用于小规模图的求解,可以根据具体需求进行扩展和优化。对于大规模图,可以考虑使用启发式算法或近似算法来提高效率。

19基于Java语言,解决N皇后问题。

N皇后问题是一个经典的回溯算法问题,目标是在N×N的棋盘上放置N个皇后,使得它们不能相互攻击。具体来说,任何两个皇后都不能位于同一行、同一列或同一对角线上。
下面是一个基于Java语言的解决方案:
import java.util.ArrayList;
import java.util.List;

public class NQueens {
    private List<List<String>> solutions = new ArrayList<>();
    private int[] queens;
    private boolean[] columns;
    private boolean[] diagonals1;
    private boolean[] diagonals2;

    public List<List<String>> solveNQueens(int n) {
        queens = new int[n];
        columns = new boolean[n];
        diagonals1 = new boolean[2 * n - 1];
        diagonals2 = new boolean[2 * n - 1];
        backtrack(0, n);
        return solutions;
    }

    private void backtrack(int row, int n) {
        if (row == n) {
            addSolution(n);
            return;
        }
        for (int col = 0; col < n; col++) {
            if (columns[col] || diagonals1[row + col] || diagonals2[row - col + n - 1]) {
                continue;
            }
            queens[row] = col;
            columns[col] = true;
            diagonals1[row + col] = true;
            diagonals2[row - col + n - 1] = true;
            backtrack(row + 1, n);
            queens[row] = -1;
            columns[col] = false;
            diagonals1[row + col] = false;
            diagonals2[row - col + n - 1] = false;
        }
    }

    private void addSolution(int n) {
        List<String> solution = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            for (int j = 0; j < n; j++) {
                row[j] = '.';
            }
            row[queens[i]] = 'Q';
            solution.add(new String(row));
        }
        solutions.add(solution);
    }

    public static void main(String[] args) {
        NQueens nQueens = new NQueens();
        int n = 8; // 可以修改为任意正整数
        List<List<String>> solutions = nQueens.solveNQueens(n);
        for (List<String> solution : solutions) {
            for (String row : solution) {
                System.out.println(row);
            }
            System.out.println();
        }
        System.out.println("Total solutions: " + solutions.size());
    }
}

19.1代码说明:

  1. 类和成员变量
    • solutions:存储所有可能的解决方案。
    • queens:数组,其中 queens[i] 表示第 i 行的皇后所在的列。
    • columns:布尔数组,标记哪些列已经被占用。
    • diagonals1diagonals2:布尔数组,分别标记主对角线和副对角线是否被占用。
  2. solveNQueens方法:初始化必要的数据结构并开始回溯搜索。
  3. backtrack方法:递归地尝试在每一行放置皇后,如果当前位置合法,则继续下一行;否则回溯。
  4. addSolution方法:将当前找到的解决方案添加到解决方案列表中。
  5. main方法:测试代码,输出所有可能的解决方案以及总解数。
这个解决方案使用了回溯法来解决问题,通过逐行尝试放置皇后,并在发现冲突时进行回溯。这样可以有效地找到所有可能的解。

20基于Java语言,实现骑士巡游问题。

骑士巡游问题(Knight's Tour)是一个经典的回溯算法问题。目标是在国际象棋棋盘上,让骑士从某个位置出发,访问每一个格子恰好一次,并最终回到起始位置。
下面是一个基于Java语言的解决方案:
public class KnightTour {
    private static final int N = 8; // 棋盘大小
    private static final int[] xMoves = {2, 1, -1, -2, -2, -1, 1, 2};
    private static final int[] yMoves = {1, 2, 2, 1, -1, -2, -2, -1};

    private int[][] solutionMatrix;

    public KnightTour() {
        solutionMatrix = new int[N][N];
        // 初始化棋盘,所有位置都设置为-1
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                solutionMatrix[i][j] = -1;
            }
        }
    }

    public boolean solveKnightTour(int startX, int startY) {
        solutionMatrix[startX][startY] = 0; // 起始位置设为0
        if (!solve(startX, startY, 1)) {
            System.out.println("No solution exists");
            return false;
        } else {
            printSolution();
            return true;
        }
    }

    private boolean solve(int x, int y, int moveCount) {
        if (moveCount == N * N) {
            return true; // 如果已经访问了所有格子,返回true
        }

        for (int k = 0; k < 8; k++) {
            int nextX = x + xMoves[k];
            int nextY = y + yMoves[k];
            if (isSafe(nextX, nextY)) {
                solutionMatrix[nextX][nextY] = moveCount;
                if (solve(nextX, nextY, moveCount + 1)) {
                    return true;
                } else {
                    solutionMatrix[nextX][nextY] = -1; // 回溯
                }
            }
        }
        return false;
    }

    private boolean isSafe(int x, int y) {
        return (x >= 0 && x < N && y >= 0 && y < N && solutionMatrix[x][y] == -1);
    }

    private void printSolution() {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                System.out.printf("%2d ", solutionMatrix[i][j]);
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        KnightTour knightTour = new KnightTour();
        knightTour.solveKnightTour(0, 0); // 从(0, 0)开始巡游
    }
}

20.1代码说明:

  1. 常量和成员变量
    • N:棋盘的大小,这里设定为8×8。
    • xMovesyMoves:表示骑士的8个可能移动方向。
    • solutionMatrix:用于存储骑士巡游的路径。
  2. 构造函数:初始化棋盘,将所有位置设置为-1,表示未访问。
  3. solveKnightTour方法:设置起始位置并调用递归解决函数。如果找到解决方案,则打印出来;否则输出无解信息。
  4. solve方法:递归地尝试骑士的每一个可能移动。如果找到一个合法的移动,则继续递归;否则进行回溯。
  5. isSafe方法:检查骑士的下一个移动是否在棋盘范围内且未被访问过。
  6. printSolution方法:打印出骑士巡游的路径。
  7. main方法:创建KnightTour对象并从指定位置开始巡游。
这个解决方案使用了回溯法来解决问题,通过逐格尝试骑士的每一个可能移动,并在发现冲突时进行回溯。这样可以有效地找到骑士巡游的所有可能路径。

21基于Java语言,解决哈密顿路径问题。

哈密顿路径问题(Hamiltonian Path Problem)是一个经典的图论问题,目标是在给定的图中找到一个包含所有顶点且每个顶点恰好访问一次的路径。
下面是一个基于Java语言的解决方案:
import java.util.ArrayList;
import java.util.List;

public class HamiltonianPath {
    private int V; // 顶点数
    private int[] path; // 存储路径
    private int[][] graph; // 邻接矩阵表示的图

    public HamiltonianPath(int[][] graph) {
        this.graph = graph;
        this.V = graph.length;
        this.path = new int[V];
        for (int i = 0; i < V; i++) {
            path[i] = -1;
        }
    }

    public boolean findHamiltonianPath() {
        path[0] = 0; // 从第一个顶点开始
        if (!hamiltonianPathUtil(1)) {
            System.out.println("No Hamiltonian Path exists");
            return false;
        } else {
            printSolution();
            return true;
        }
    }

    private boolean hamiltonianPathUtil(int pos) {
        if (pos == V) {
            return true; // 如果所有顶点都访问过,返回true
        }

        for (int v = 1; v < V; v++) {
            if (isSafe(v, pos)) {
                path[pos] = v;
                if (hamiltonianPathUtil(pos + 1)) {
                    return true;
                }
                path[pos] = -1; // 回溯
            }
        }
        return false;
    }

    private boolean isSafe(int v, int pos) {
        if (graph[path[pos - 1]][v] == 0) {
            return false; // 检查边是否存在
        }
        for (int i = 0; i < pos; i++) {
            if (path[i] == v) {
                return false; // 检查顶点是否已经被访问过
            }
        }
        return true;
    }

    private void printSolution() {
        System.out.println("Hamiltonian Path exists:");
        for (int i = 0; i < V; i++) {
            System.out.print(path[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int[][] graph = {
            {0, 1, 0, 1, 0},
            {1, 0, 1, 1, 1},
            {0, 1, 0, 0, 1},
            {1, 1, 0, 0, 1},
            {0, 1, 1, 1, 0}
        };
        HamiltonianPath hamiltonianPath = new HamiltonianPath(graph);
        hamiltonianPath.findHamiltonianPath();
    }
}

21.1代码说明:

  1. 类和成员变量
    • V:顶点数。
    • path:用于存储找到的哈密顿路径。
    • graph:用邻接矩阵表示的图。
  2. 构造函数:初始化图、顶点数和路径数组。
  3. findHamiltonianPath方法:设置起始点并调用递归解决函数。如果找到解决方案,则打印出来;否则输出无解信息。
  4. hamiltonianPathUtil方法:递归地尝试每一个可能的顶点,如果找到一个合法的移动,则继续递归;否则进行回溯。
  5. isSafe方法:检查当前顶点是否可以安全地添加到路径中。包括检查边是否存在以及顶点是否已经被访问过。
  6. printSolution方法:打印出找到的哈密顿路径。
  7. main方法:创建一个图并调用findHamiltonianPath方法来寻找哈密顿路径。
这个解决方案使用了回溯法来解决问题,通过逐顶点尝试每一个可能的移动,并在发现冲突时进行回溯。这样可以有效地找到图中的所有哈密顿路径。

22基于Java语言,实现中国邮路问题。

中国邮路问题(Chinese Postman Problem,CPP)是一个经典的图论问题,目标是在给定的无向图中找到一个最短的回路,使得每条边至少被遍历一次。这个问题可以通过欧拉图的概念来解决:如果一个图是欧拉图,那么它有一个欧拉回路;否则,需要添加一些边使其成为欧拉图,并保证总长度最小。
下面是一个基于Java语言的解决方案:
import java.util.*;

public class ChinesePostmanProblem {
    private int V; // 顶点数
    private LinkedList<Edge>[] adj; // 邻接表表示的图

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

    @SuppressWarnings("unchecked")
    public ChinesePostmanProblem(int V) {
        this.V = V;
        adj = new LinkedList[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new LinkedList<>();
        }
    }

    public void addEdge(int src, int dest, int weight) {
        adj[src].add(new Edge(src, dest, weight));
        adj[dest].add(new Edge(dest, src, weight)); // 无向图
    }

    public int chinesePostman() {
        int oddDegreeVertices = 0;
        int[] degree = new int[V];
        for (int i = 0; i < V; i++) {
            degree[i] = adj[i].size();
            if (degree[i] % 2 != 0) {
                oddDegreeVertices++;
            }
        }

        if (oddDegreeVertices == 0) {
            return 0; // 已经是欧拉图
        }

        // 使用最小权匹配算法找到最小权完美匹配
        int[][] minCost = new int[V][V];
        for (int i = 0; i < V; i++) {
            Arrays.fill(minCost[i], Integer.MAX_VALUE);
        }
        for (int i = 0; i < V; i++) {
            for (Edge edge : adj[i]) {
                minCost[edge.src][edge.dest] = edge.weight;
            }
        }

        int[] match = new int[V];
        Arrays.fill(match, -1);
        boolean[] used = new boolean[V];
        for (int v = 0; v < V; v++) {
            if (degree[v] % 2 != 0) {
                int u = findMinMatch(v, minCost, used);
                if (u != -1) {
                    match[v] = u;
                    match[u] = v;
                    used[v] = true;
                    used[u] = true;
                }
            }
        }

        // 计算最小权完美匹配的总权重
        int totalWeight = 0;
        for (int i = 0; i < V; i++) {
            if (match[i] != -1 && i < match[i]) {
                totalWeight += minCost[i][match[i]];
            }
        }

        return totalWeight;
    }

    private int findMinMatch(int v, int[][] minCost, boolean[] used) {
        int minWeight = Integer.MAX_VALUE;
        int u = -1;
        for (int i = 0; i < V; i++) {
            if (!used[i] && minCost[v][i] < minWeight) {
                minWeight = minCost[v][i];
                u = i;
            }
        }
        return u;
    }

    public static void main(String[] args) {
        int V = 5; // 顶点数
        ChinesePostmanProblem graph = new ChinesePostmanProblem(V);
        graph.addEdge(0, 1, 1);
        graph.addEdge(0, 2, 4);
        graph.addEdge(1, 2, 2);
        graph.addEdge(1, 3, 6);
        graph.addEdge(2, 3, 3);
        graph.addEdge(3, 4, 2);
        graph.addEdge(4, 0, 7);

        int result = graph.chinesePostman();
        System.out.println("The weight of the shortest postman tour is " + result);
    }
}

22.1代码说明:

  1. 类和成员变量
    • V:顶点数。
    • adj:用邻接表表示的图。
    • Edge:表示图中的边,包括源顶点、目标顶点和权重。
  2. 构造函数:初始化图的顶点数和邻接表。
  3. addEdge方法:添加一条无向边到图中。
  4. chinesePostman方法:实现中国邮路问题的求解。首先检查每个顶点的度数,如果所有顶点的度数都是偶数,则图已经是欧拉图,返回0。否则,通过最小权匹配算法找到最小权完美匹配,并计算其总权重。
  5. findMinMatch方法:辅助方法,用于找到与当前顶点匹配的最小权重顶点。
  6. main方法:创建一个图并调用chinesePostman方法来求解中国邮路问题。
这个解决方案使用了最小权匹配算法来确保总长度最小,从而有效地解决了中国邮路问题。

23基于Java语言,解决最大二分图匹配问题。

最大二分图匹配问题(Maximum Bipartite Matching Problem)是一个经典的组合优化问题,目标是在给定的二分图中找到一个最大的匹配。这个问题可以通过多种算法来解决,其中最常用的是匈牙利算法(Hungarian Algorithm)。
下面是一个基于Java语言的解决方案:
import java.util.*;

public class BipartiteMatching {
    private int[] match; // 存储匹配结果
    private boolean[] visited; // 标记顶点是否被访问过
    private int[][] graph; // 邻接矩阵表示的二分图

    public BipartiteMatching(int[][] graph) {
        this.graph = graph;
        int n = graph.length;
        match = new int[n];
        visited = new boolean[n];
        Arrays.fill(match, -1); // 初始化匹配数组为-1,表示未匹配
    }

    public int maxMatching() {
        int result = 0;
        for (int i = 0; i < graph.length; i++) {
            Arrays.fill(visited, false); // 每次寻找新的增广路径前重置访问标记
            if (findMatch(i)) {
                result++;
            }
        }
        return result;
    }

    private boolean findMatch(int u) {
        for (int v = 0; v < graph[u].length; v++) {
            if (graph[u][v] == 1 && !visited[v]) {
                visited[v] = true;
                if (match[v] == -1 || findMatch(match[v])) {
                    match[v] = u;
                    return true;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        // 示例二分图,使用邻接矩阵表示
        int[][] graph = {
            {0, 1, 1, 0},
            {1, 0, 0, 1},
            {1, 0, 0, 1},
            {0, 1, 1, 0}
        };

        BipartiteMatching matching = new BipartiteMatching(graph);
        int maxMatch = matching.maxMatching();
        System.out.println("The maximum number of matches is: " + maxMatch);
    }
}

23.1代码说明:

  1. 类和成员变量
    • match:存储每个顶点的匹配情况,初始值为-1表示未匹配。
    • visited:标记顶点是否被访问过,用于避免重复访问。
    • graph:用邻接矩阵表示的二分图。
  2. 构造函数:初始化图、匹配数组和访问标记数组。
  3. maxMatching方法:遍历所有顶点,尝试找到增广路径并更新匹配结果。返回最大匹配数。
  4. findMatch方法:递归地寻找增广路径。如果找到增广路径,则更新匹配数组并返回true;否则返回false。
  5. main方法:创建一个示例二分图并调用maxMatching方法求解最大匹配数。
这个解决方案使用了深度优先搜索(DFS)来寻找增广路径,并通过递归实现匈牙利算法的核心逻辑。通过这种方式,可以有效地解决最大二分图匹配问题。

24基于Java语言,实现最大团问题。

最大团问题(Maximum Clique Problem)是图论中的经典问题,目标是在给定的无向图中找到一个最大的完全子图(即每个顶点都与其他所有顶点相连)。这个问题是NP难问题,因此没有已知的多项式时间算法可以解决所有实例。
下面是一个基于Java语言的实现,使用回溯法来寻找最大团。这种方法虽然不是最优的,但可以用于小规模图的求解。
import java.util.*;

public class MaximumClique {
    private int[][] graph; // 邻接矩阵表示的图
    private boolean[] visited; // 标记顶点是否被访问过
    private List<Integer> currentClique; // 当前找到的最大团
    private List<Integer> bestClique; // 最佳最大团

    public MaximumClique(int[][] graph) {
        this.graph = graph;
        int n = graph.length;
        visited = new boolean[n];
        currentClique = new ArrayList<>();
        bestClique = new ArrayList<>();
    }

    public List<Integer> findMaxClique() {
        for (int i = 0; i < graph.length; i++) {
            Arrays.fill(visited, false); // 每次寻找新的增广路径前重置访问标记
            currentClique.clear();
            findClique(i);
        }
        return bestClique;
    }

    private void findClique(int u) {
        currentClique.add(u);
        visited[u] = true;

        boolean isClique = true;
        for (int v : currentClique) {
            if (v != u && graph[u][v] == 0) {
                isClique = false;
                break;
            }
        }

        if (isClique && currentClique.size() > bestClique.size()) {
            bestClique = new ArrayList<>(currentClique);
        }

        for (int v = 0; v < graph.length; v++) {
            if (!visited[v] && graph[u][v] == 1) {
                findClique(v);
            }
        }

        currentClique.remove(currentClique.size() - 1);
        visited[u] = false;
    }

    public static void main(String[] args) {
        // 示例图,使用邻接矩阵表示
        int[][] graph = {
            {0, 1, 1, 0, 0},
            {1, 0, 1, 1, 0},
            {1, 1, 0, 1, 0},
            {0, 1, 1, 0, 1},
            {0, 0, 0, 1, 0}
        };

        MaximumClique cliqueFinder = new MaximumClique(graph);
        List<Integer> maxClique = cliqueFinder.findMaxClique();
        System.out.println("The maximum clique is: " + maxClique);
    }
}

24.1代码说明:

  1. 类和成员变量
    • graph:用邻接矩阵表示的图。
    • visited:标记顶点是否被访问过,用于避免重复访问。
    • currentClique:存储当前找到的团。
    • bestClique:存储最佳最大团。
  2. 构造函数:初始化图、访问标记数组、当前团和最佳团。
  3. findMaxClique方法:遍历所有顶点,尝试找到最大团并更新最佳团。返回最佳最大团。
  4. findClique方法:递归地寻找团。如果当前团是有效的并且比最佳团大,则更新最佳团。继续搜索未访问的相邻顶点。
  5. main方法:创建一个示例图并调用findMaxClique方法求解最大团。
这个解决方案使用了回溯法来寻找最大团,通过递归地扩展当前团并检查其有效性。尽管这种方法适用于小规模图,但对于大规模图来说效率较低。对于更大规模的图,可以考虑使用启发式算法或近似算法来求解最大团问题。