欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

数据结构第13节 无向图

最编程 2024-07-12 07:15:11
...

无向图是图论中的一个基本概念,它是数学和计算机科学中用来描述一组对象(顶点)以及它们之间的成对关系(边)的结构。在无向图中,边是没有方向的,这意味着边所连接的两个顶点可以互相访问。

定义

无向图 ( G ) 可以定义为一个有序对 ( G=(V,E) ),其中:

  • ( V ) 是顶点的集合,也称作节点的集合。
  • ( E ) 是边的集合,边是 ( V ) 中顶点的无序对,表示两个顶点之间的连接。

术语

  • 顶点:图中的基本单位,通常用字母 ( v ) 或 ( u ) 表示。
  • :连接顶点的线段,表示两个顶点之间的关系,通常用 ( e ) 表示。
  • 度数:一个顶点的度数是与该顶点相邻的所有边的数量。
  • 路径:一系列顶点和边,使得每个边都连接路径中的连续两个顶点。
  • 简单路径:路径中不重复经过任何顶点。
  • :闭合路径,起点和终点相同。
  • 连通性:如果图中任意两个顶点之间都存在路径,则称该图为连通图。
  • 连通分量:无向图中的最大连通子图,彼此之间没有边相连。

图的表示

无向图可以用以下几种方式表示:

  • 邻接矩阵:一个 ( |V| \times |V| ) 的矩阵,其中第 ( i ) 行第 ( j ) 列的元素为 1 如果顶点 ( i ) 和顶点 ( j ) 之间有一条边,否则为 0。
  • 邻接列表:对于每个顶点,维护一个链表,包含与之直接相连的所有顶点。

应用

无向图在许多领域有应用,如:

  • 社交网络:顶点可以代表人,边可以代表朋友关系。
  • 互联网:网页作为顶点,超链接作为边。
  • 电路设计:顶点可以是电路元件,边可以是连接导线。
  • 地图和道路网络:顶点可以是城市或交叉口,边可以是道路。
  • 化学分子结构:顶点可以是原子,边可以是化学键。

算法

处理无向图的一些常见算法包括:

  • 深度优先搜索 (DFS):从某个顶点开始,尽可能深入地探索每条路径。
  • 广度优先搜索 (BFS):从某个顶点开始,探索所有离起点等距离的顶点。
  • 最小生成树算法:如Prim算法或Kruskal算法,用于在加权图中找到一棵包含所有顶点的最小权重的树。
  • 连通性检测:确定图是否连通,或找出所有连通分量。

无向图是理解和解决许多复杂问题的基础工具。在实际应用中,无向图可以转化为有向图或加权图以适应更复杂的关系和需求。

在Java中,我们可以使用邻接矩阵或邻接列表来表示无向图。下面是一个使用邻接列表的例子:

import java.util.*;

public class Graph {
    private final int V;   // Number of vertices
    private LinkedList<Integer> adj[]; //Adjacency List

    // Constructor
    Graph(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i=0; i<v; ++i)
            adj[i] = new LinkedList();
    }

    //Function to add an edge into the graph
    void addEdge(int v,int w) {
        adj[v].add(w); // Add w to v’s list.
        adj[w].add(v); // Since graph is undirected, add v to w's list too
    }

    // Function to print the graph
    void printGraph() {
        for (int v = 0; v < V; ++v) {
            System.out.println("Adjacency list of vertex "+ v);
            System.out.print("head ");
            Iterator<Integer> it = adj[v].listIterator();
            while (it.hasNext())
                System.out.print(" -> " + it.next());
            System.out.println("\n");
        }
    }

    public static void main(String args[]) {
        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();
    }
}

在这个例子中,我们首先定义了一个Graph类,它有两个私有变量:V(顶点的数量)和adj(邻接列表)。然后我们在构造函数中初始化这些变量。

我们定义了一个addEdge方法来添加边。由于这是一个无向图,所以我们需要在两个顶点的邻接列表中都添加对方。

最后,我们定义了一个printGraph方法来打印图的邻接列表表示。

在main方法中,我们创建了一个Graph对象,并添加了一些边,然后打印出了这个图。

当然,让我们再看一个使用邻接矩阵表示无向图的例子。邻接矩阵是一种二维数组,用于表示图中顶点之间的连接关系。如果图中的两个顶点u和v之间存在一条边,那么邻接矩阵中的元素A[u][v]和A[v][u]的值为1,否则为0。

下面是一个使用邻接矩阵表示无向图的Java代码示例:

public class Graph {
    private final int V;   // No. of vertices
    private int E;         // No. of edges
    private int[][] adj;   // Adjacency Matrix

    // Constructor
    Graph(int v) {
        V = v;
        E = 0;
        adj = new int[V][V];
    }

    // Method to add an edge in a graph
    public void addEdge(int v, int w) {
        if (v >= 0 && v < V && w >= 0 && w < V) {
            adj[v][w] = 1;
            adj[w][v] = 1;
            E++;
        } else {
            throw new IllegalArgumentException("Invalid vertices");
        }
    }

    // Method to print adjacency matrix
    public void printGraph() {
        for (int v = 0; v < V; v++) {
            for (int w = 0; w < V; w++) {
                System.out.print(adj[v][w] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        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();
    }
}

在这个例子中,我们首先定义了一个Graph类,它有三个私有变量:V(顶点的数量),E(边的数量)和adj(邻接矩阵)。然后我们在构造函数中初始化这些变量。

我们定义了一个addEdge方法来添加边。由于这是一个无向图,所以我们需要在邻接矩阵的两个位置上都设置1。

最后,我们定义了一个printGraph方法来打印图的邻接矩阵表示。

在main方法中,我们创建了一个Graph对象,并添加了一些边,然后打印出了这个图的邻接矩阵表示。

让我们通过一个迷宫游戏的例子来理解无向图的应用。在这个游戏中,迷宫可以被看作是一个无向图,其中每个房间都是一个顶点,每个门(连接两个房间的通道)都是一条边。

假设我们有一个迷宫,它由多个房间组成,每个房间都有可能与其他房间相连。我们的目标是找到从起点到终点的路径。这个问题可以通过深度优先搜索(DFS)或广度优先搜索(BFS)在无向图中寻找路径来解决。

以下是一个使用DFS在迷宫(无向图)中寻找路径的Java代码示例:

import java.util.*;

class Maze {
    private final int V;   // Number of rooms
    private LinkedList<Integer> adj[]; //Adjacency List

    // Constructor
    Maze(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i=0; i<v; ++i)
            adj[i] = new LinkedList();
    }

    //Function to add a door between two rooms
    void addDoor(int v, int w) {
        adj[v].add(w); // Add w to v’s list.
        adj[w].add(v); // Since maze is undirected, add v to w's list too
    }

    // A recursive function to find path from source 's' to destination 'd'
    boolean DFSUtil(int s, int d, boolean visited[]) {
        visited[s] = true;
        System.out.print(s + " ");

        if (s == d)
            return true;

        Iterator<Integer> i = adj[s].listIterator();
        while (i.hasNext()) {
            int n = i.next();
            if (!visited[n]) {
                if (DFSUtil(n, d, visited))
                    return true;
            }
        }
        return false;
    }

    // Prints shortest path from source 's' to destination 'd'
    void findPath(int s, int d) {
        boolean visited[] = new boolean[V];

        if (DFSUtil(s, d, visited) == false)
            System.out.println("No path exists");

        System.out.println();
    }

    public static void main(String args[]) {
        Maze maze = new Maze(9); // Assume we have 9 rooms

        maze.addDoor(0, 1);
        maze.addDoor(0, 3);
        maze.addDoor(1, 2);
        maze.addDoor(1, 3);
        maze.addDoor(2, 4);
        maze.addDoor(3, 4);
        maze.addDoor(4, 5);
        maze.addDoor(5, 6);
        maze.addDoor(6, 7);
        maze.addDoor(7, 8);

        System.out.println("Path from room 0 to room 8:");
        maze.findPath(0, 8);
    }
}

在这个例子中,我们首先定义了一个Maze类,它有两个私有变量:V(房间的数量)和adj(邻接列表)。然后我们在构造函数中初始化这些变量。

我们定义了一个addDoor方法来添加门。由于这是一个无向图,所以我们需要在两个房间的邻接列表中都添加对方。

我们还定义了一个findPath方法来找到从源房间到目标房间的路径。这个方法使用了深度优先搜索(DFS)算法,并且使用了一个辅助方法DFSUtil来进行递归搜索。

在main方法中,我们创建了一个Maze对象,并添加了一些门,然后找到了从房间0到房间8的路径。