[Java 数据结构] 图(1)

一 . 图 (Graph) 的基本概念

1. 图

图 : 由顶点(Vertex)和边(Edge)组成的集合

G = (V,E)

顶点集合 V = {x| x 属于某个数据对象集}是有穷非空集合

边集合 E = {(x,y)|x,y 属于 V} 或者 E = {<x,y>|x,y∈V && Path(x,y)}

(x,y) 表示 x,y 之间的双向通道(即 xy 是无方向的)

**Path<x,y>**表示从 x 到 y 的一条单向通道 (Path<x,y> 有方向的)

比如:社交网络中,用户是顶点,用户之间的好友关系是边;地图中,城市是顶点,道路是边

分类 :

  • 无向图 : 边没有方向(如好友关系,A 是 B 的好友,B 也是 A 的好友)
  • 有向图 : 边有方向(如关注,A 关注 B,B 不一定关注 A)
  • 带权图 : 边有数值(如地图中道路的长度、网络中的延迟)

2. 完全图

  • 无向完全图 : 在有 n 个顶点无向图中 , 若有 n*(n-1)/2 条边 , 即任意两个顶点之间有且仅有一条边
  • 有向完全图 : 在有 n 个顶点的有向图中 , 若 有 n*(n-1)条边 , 即任意两个顶点之间有且仅有方向相反的边

3.邻接顶点

  • 在无向图中 , 若 (u,r)是 E(G) 的一条边 , 则称 u 和 v 互为邻接顶点
  • 在有向图中 , 若 <u,v>是 E(G)中的一条边 , 则称顶点 u 邻接到 v , 顶点 v 临界自顶点 u , 并称边<u,v>与顶点 u 和顶点 v 相关联

4.顶点的度

  • 无向图中 , 顶点 v 的度 deg(v) 是关联边的条数, 此时 出度 = 入度 , deg(v) = indeg(v) = outdeg(v)
  • 有向图中 , 顶点 v 的度是入度 (以 v 为终点的边数 , indev(v)) 与出度(以 v 为起点的边数 , outdev(v)) , deg(v) = indev(v) + outdev(v)

5.路径与路径长度

  • 路径 : 从顶点 vi 到 vj 的顶点序列
  • 路径长度 : 不带权重图 计算路径上的边数 ; 带权图 : 路径上各边权值总和

6.简单路径与回路

简单路径 : 路径上的顶带你无重复

回路(环) : 路径上的起点和终点是同一个顶点

7.子图

子图可包含原图中的部分顶点和边

8.连通图和强连通图

连通图(无向图) : 图中任意一对顶点之间都存在路径

强连通图(有向图) : 图中任意一对顶点 vi , vj 既存在 vi->vj 的路径 , 又存在 vj->vi 的路径

9.生成树

定义 : 连通图的极小连通子图 , 包含原图 所有 n 个顶点 , 有且仅有 n-1 条边(无回路)

特征 : 是连通图的"最简连通形式" , 删去任意一条边都会导致不连通

二 . 图的存储结构

1.邻接矩阵

存储方式 : 用二位数组 a[n][n](n 为顶点数)表示 ; 若顶点 i 和 j 之间有边 , 则 a[i][j] = 边的权值(如果为无向图 , 则同时赋 a[j][i] = 权值) ; 无边则为 ∞

根据上图构建邻接矩阵

java 复制代码
package graph;

import java.util.Arrays;

public class GraphByMatrix {
    private char[] arrayV;//存储顶点
    private int[][] martrix;//存储权值或者边
    private boolean isDirect;//是否为有向图

    public GraphByMatrix(int size , boolean isDirect) {
        arrayV = new char[size];
        martrix = new int[size][size];
        this.isDirect = isDirect;
        for (int i = 0; i < size; i++) {
            Arrays.fill(martrix[i],Integer.MAX_VALUE);//将所有元素都初始化为Integer.MAX_VALUE
        }
    }
    /**
     * 初始化顶点数组
     */
    public void initArrayV(char[] arrays){
        for (int i = 0; i < arrays.length; i++) {
            arrayV[i] = arrays[i];//初始化
        }
    }
    /**
     * 给两个顶点的边加上权重
     * v1起点
     * V2终点
     */
    public void addEdge(char v1,char v2 , int weight){
        int indexv1 = getIndex(v1);
        int indexv2 = getIndex(v2);
        martrix[indexv1][indexv2] = weight;
        if (!isDirect) {
            martrix[indexv2][indexv1] = weight;//无向图的话,两条边都要设置
        }
    }

    private int getIndex(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v){
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取顶点的度
     */
    public int getDevOfV(char v){
        int index = getIndex(v);
        int count = 0;
        for (int i = 0; i < arrayV.length; i++) {
            if(i != index&&martrix[index][i] != Integer.MAX_VALUE){//避免误统计自环
                count++;
            }

        }
        if(isDirect){
            for (int i = 0; i < arrayV.length; i++) {
                if(i != index &&martrix[i][index] != Integer.MAX_VALUE){//同样是避免自环
                    count++;
                }
            }
        }
        return count;
    }
    public void print(){
        for (int i = 0; i < arrayV.length; i++) {
            for (int j = 0; j < arrayV.length; j++) {
                System.out.print(martrix[i][j]+" ");
            }
            System.out.println();
        }
    }

public static void main(String[] args) {
    // 1. 创建4个顶点的图(注意:右侧矩阵是无向图的邻接矩阵,故isDirect=false)
    GraphByMatrix graph = new GraphByMatrix(4, false);
    // 2. 初始化顶点数组
    char[] array = {'0', '1', '2', '3'};
    graph.initArrayV(array);
    // 3. 按右侧矩阵添加边(无向图只需加单向,代码自动补全反向)
    graph.addEdge('0', '1', 1);   // 0→1,权重1
    graph.addEdge('0', '3', 4);   // 0→3,权重4
    graph.addEdge('1', '2', 9);   // 1→2,权重9
    graph.addEdge('1', '3', 2);   // 1→3,权重2
    graph.addEdge('2', '0', 3);   // 2→0,权重3(对应矩阵2行0列)
    graph.addEdge('2', '1', 5);   // 2→1,权重5(对应矩阵2行1列)
    graph.addEdge('2', '3', 8);   // 2→3,权重8(对应矩阵2行3列)
    graph.addEdge('3', '2', 6);   // 3→2,权重6(对应矩阵3行2列)
    
    // 4. 打印邻接矩阵,验证是否匹配右侧矩阵
    System.out.println("=== 匹配右侧图的邻接矩阵 ===");
    graph.print();
    // 5. 测试顶点0的度数(无向图,邻接边数:0→1、0→3、0←2 → 度数3)
    System.out.println("顶点0的度数:" + graph.getDevOfV('0'));
}
}

总结 :

  • 无向图的邻接矩阵是对称的 ; 有向图的邻接矩阵不一定是对称的

注意 : 下图中的 0 表示不通 , 1 表示连通

  • 如果边带有权值 , 并且两个结点之间是连通的 , 就用权值来代替边的关系 , 如果两个点不连通 , 则使用无穷大来代替

2.邻接表

存储方式 : 用数组+链表结构 ; 数组存储每个顶点 , 每个顶点对应一个链表 , 链表存储该顶点的邻接顶点(及边的信息)

java 复制代码
package graph;

import java.util.ArrayList;

public class GraphByNode {

    class Node{
        public int src;
        public int dest;
        public int weight;
        public Node next;

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

    private ArrayList<Node> edgeList;//存储边
    private char[] arrayV;//存储顶点
    private boolean isDirect;//是否是优先图

    public GraphByNode(int size,boolean isDirect){
        arrayV = new char[size];
        edgeList = new ArrayList<>(size);
        this.isDirect = isDirect;
        for(int i = 0;i < size;i++){
            edgeList.add(null);
        }
    }

    /**
     * 初始化顶点数组
     * @param array
     */
    public void initArrayV(char[] array){
        for (int i = 0; i < arrayV.length; i++) {
            arrayV[i] = array[i];
        }
    }


    public int getIndex(char v){
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v){
                return i;
            }
        }
        return -1;
    }
    /**
     * 添加权重
     */
    public void addEdge(char v1,char v2,int weight){
        int src = getIndex(v1);
        int dest = getIndex(v2);

        addEdgechild(src,dest,weight);
    }

    private void addEdgechild(int src, int dest, int weight) {
        Node cur = edgeList.get(src);
        //看这个结点是否在链表中
        while(cur!=null){
            if(cur.dest==dest){
                return;
            }
            cur = cur.next;
        }
        //不在的话 , 重新生成结点 , 头插法插入
        Node node = new Node(src,dest,weight);
        node.next = edgeList.get(src);
        edgeList.set(src,node);
    }

    /**
     * 获取顶点的度
     * @param v
     * @return
     */
    public int getDevOfV(char v){
        int count = 0;
        int srcIndex = getIndex(v);
        Node cur = edgeList.get(srcIndex);
        while (cur!=null){
            count++;
            cur = cur.next;
        }
        //判断是否是有向图
        if(isDirect){
            //计算入度
            int destIndex = srcIndex;
            for(int i = 0;i<arrayV.length;i++){
                if(i == destIndex){
                    continue;
                }else {
                    Node curNode = edgeList.get(i);
                    while(curNode!=null){
                        if(curNode.dest==destIndex){
                            count++;
                        }
                        curNode = curNode.next;
                    }
                }
            }
        }
        return count;
    }
    public void printGraph(){
        for(int i = 0;i<arrayV.length;i++){
            System.out.print(arrayV[i]+":");
            Node cur = edgeList.get(i);
            while(cur!=null){
                System.out.print(arrayV[cur.dest]+"->");
                cur = cur.next;
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
    // 1. 创建5个顶点的有向图(isDirect=true)
    GraphByNode graph = new GraphByNode(5, true);
    // 2. 初始化顶点集合(A、B、C、D、E)
    char[] array = {'A', 'B', 'C', 'D', 'E'};
    graph.initArrayV(array);

    // 3. 按出边表添加所有有向边
    graph.addEdge('A', 'B', 1);  // A→B(出边表:A的链表存1)
    graph.addEdge('A', 'D', 1);  // A→D(出边表:A的链表存3)
    graph.addEdge('B', 'C', 1);  // B→C(出边表:B的链表存2)
    graph.addEdge('C', 'D', 1);  // C→D(出边表:C的链表存3)
    graph.addEdge('D', 'E', 1);  // D→E(出边表:D的链表存4)
    graph.addEdge('E', 'A', 1);  // E→A(出边表:E的链表存0)

    // 4. 打印出边表(匹配图的"出边表"结构)
    System.out.println("=== 出边表 ===");
    graph.printGraph();

    // 5. 打印顶点A的度数(出边2 + 入边3 → 总度数5)
    System.out.println("顶点A的度数:" + graph.getDevOfV('A'));
}

}

总结:

  • 无向图中同一条边会在邻接表中出现两次 , 所以如果向知道顶点 vi 的度 , 只需要知道顶点 vi 边链表集合中的结点数目即可
  • 有向图中每条边在邻接表中只出现一次 , 在计算 vi 度的时候 , 不光需要计算 vi 所连出去的顶点数 , 还需要遍历其余顶点看是否有顶点 vi 作为终点的边(注意去除已经计算的边)

三.图的遍历(使用邻接矩阵的代码)

1.广度优先遍历(BFS)

逐层扩散

和二叉树的层序遍历有点类似

实现逻辑 : 用队列存储待访问结点 , 向访问当前结点 , 再把它的所有邻接结点(未访问过的)依次加入队列中 , 按"先进先出"的顺序处理

java 复制代码
public void bfs(char v){
int startIndex = getIndex(v);
if(startIndex == -1){
    System.out.println("顶点不存在");
    return;
}
//定义一个visited数组 标记当前这个结点是否已经被访问
boolean[] visited = new boolean[arrayV.length];
//定义一个队列来完成广度优先遍历
Queue<Integer> queue = new LinkedList<>();
queue.offer(startIndex);//将下标存入
visited[startIndex] = true;
while(!queue.isEmpty()){
    int top = queue.poll();//取出并打印
    System.out.print(arrayV[top]+"->");
    for(int i = 0 ; i < arrayV.length ; i++){
        if(martrix[top][i]!=Integer.MAX_VALUE&&!visited[i]){
            queue.offer(i);
            visited[i] = true;
        }
    }
}
}
    private int getIndex(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v){
                return i;
            }
        }
        return -1;
    }

2.广度优先遍历(DFS)

一条路走到黑 , 走不通再回头

用递归实现 , 访问当前结点后 , 直接选一个未访问的邻接结点深入 , 走到没路可走 , 再回溯到上一个结点继续选择其他邻接结点

java 复制代码
public void dfs(char v){
boolean[] visited = new boolean[arrayV.length];
int startIndex = getIndex(v);
if (startIndex == -1) {
    System.out.println("下标不合法");
    return;
}
dfsChild(startIndex,visited);

}

private void dfsChild(int startIndex, boolean[] visited) {
    System.out.print(arrayV[startIndex]+"->");
    visited[startIndex] = true;
    for (int i = 0; i < arrayV.length; i++) {
        if(martrix[startIndex][i] != Integer.MAX_VALUE && !visited[i]){
            dfsChild(i,visited);//递归访问i的顶点
        }
    }
}
    private int getIndex(char v) {
        for (int i = 0; i < arrayV.length; i++) {
            if(arrayV[i] == v){
                return i;
            }
        }
        return -1;
    }
相关推荐
无尽的罚坐人生2 小时前
hot 100 128. 最长连续序列
数据结构·算法·贪心算法
Savior`L2 小时前
基础算法:模拟、枚举
数据结构·c++·算法
软件算法开发2 小时前
基于蘑菇繁殖优化的LSTM深度学习网络模型(MRO-LSTM)的一维时间序列预测算法matlab仿真
深度学习·算法·matlab·lstm·时间序列预测·蘑菇繁殖优化·mro-lstm
雪花desu2 小时前
【Hot100-Java中等】LeetCode 11. 盛最多水的容器:双指针法的直观理解与数学证明
算法·leetcode
POLITE32 小时前
Leetcode 438. 找到字符串中所有字母异位词 JavaScript (Day 4)
javascript·算法·leetcode
海绵宝龙2 小时前
Vue 中的 Diff 算法
前端·vue.js·算法
wadesir2 小时前
高效计算欧拉函数(Rust语言实现详解)
开发语言·算法·rust
aini_lovee2 小时前
基于扩展的增量流形学习算法IMM-ISOMAP的方案
算法
white-persist2 小时前
【内网运维】Netsh 全体系 + Windows 系统专属命令行指令大全
运维·数据结构·windows·python·算法·安全·正则表达式