数据结构与算法-图

引言

        在计算机科学领域,数据结构和算法是程序员工具箱中的两大瑰宝。其中,图(Graph) 是一种极其重要的非线性数据结构,它以节点和边的概念描述实体间复杂的关系网络。本文将对图的数据结构进行详尽解析,探讨其基本概念、操作以及实际应用场景。

一、什么是图?

        图 是由一组顶点(或称为节点)以及连接这些顶点的边构成的抽象数据类型。根据边的特性,图可以进一步分为无向图和有向图:

  • 无向图 中每条边没有方向,表示两个顶点之间的关系是对称的。
  • 有向图 中的边具有方向性,从一个顶点指向另一个顶点,表示单向依赖或流向关系。

此外,根据边是否有权重,又可将图划分为带权图和无权图。

二、图的基本操作

  1. 创建图:初始化图结构,包括创建空图、添加顶点和边等操作。
  2. 遍历图:主要有深度优先搜索(DFS)、广度优先搜索(BFS)两种方式,用于访问图中所有顶点或寻找特定路径。
  3. 查找路径:如Dijkstra算法求最短路径,Floyd-Warshall算法求任意两点间的最短路径,A*搜索算法在启发式指导下寻找最优路径等。
  4. 判断连通性:检查图中是否存在从一个顶点到另一个顶点的路径。
  5. 拓扑排序:对于有向无环图(DAG),按依赖关系进行排序。

三、图的应用场景

  1. 社交网络:用户之间的好友关系、关注关系可以用图来建模,便于进行好友推荐、社区发现等任务。
  2. 交通网络:地图上的道路系统、城市间航班或铁路线路可用图表示,方便计算最佳出行路线。
  3. 网页链接结构:互联网上的网页通过超链接形成巨大的有向图,搜索引擎正是利用此结构进行网页抓取和排名计算(PageRank算法)。
  4. 电路设计:电路中的各个元件及其连接关系可以通过图来描绘,辅助分析电路功能及故障排查。
  5. 生物信息学:基因调控网络、蛋白质相互作用网络等复杂生物学系统也可用图来刻画。

四、图算法的实际应用案例

  • Google PageRank:使用有向有权图模型量化网页的重要性,并据此优化搜索结果排序。
  • Facebook的朋友推荐系统:基于社交网络图构建算法,实现朋友推荐功能。
  • GPS导航系统:采用图数据结构处理地理信息,实时规划出最优行驶路径。

五、图的代码实践   

1.创建图(邻接矩阵)

 

public class Graph {

    private ArrayList<String> vertexList;   //存储定点的集合
    private int[][] edges;     //存储图对应的领接矩阵
    private int numOfEdges;    //边的数目

    public static void main(String[] args) {
        int n = 5;
        String[] vertexs = {"A", "B", "C", "D", "E"};
        Graph graph = new Graph(n);
        for (String vertex : vertexs) {
            graph.addVertex(vertex);
        }
        graph.addEdge(0, 1, 1);
        graph.addEdge(0, 2, 1);
        graph.addEdge(1, 2, 1);
        graph.addEdge(1, 3, 1);
        graph.addEdge(1, 4, 1);

        graph.show();
    }

    public Graph(int n) {
        edges = new int[n][n];
        vertexList = new ArrayList<String>(n);
        numOfEdges = 0;
    }

    //    添加定点
    public void addVertex(String vertex) {
        vertexList.add(vertex);
    }

    //    返回下标对应数据
    public String getValueByIndex(int i) {
        return vertexList.get(i);
    }

    //    返回下标对应数据
    public String getValueByIndex(int i) {
        return vertexList.get(i);
    }

//    添加边

    /**
     * @param v1     顶点的下标
     * @param v2     顶点的下标
     * @param weight 权值
     */
    public void addEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numOfEdges++;
    }

    //    得到边的数目
    public int getNumOfEdges() {
        return numOfEdges;
    }

    //    得到边的权值
    public int getWeight(int v1, int v2) {
        return edges[v1][v2];
    }

    //    展示图的邻接矩阵
    public void show() {
        for (int[] edge : edges) {
            System.out.println(Arrays.toString(edge));
        }
    }
}

2.图的邻接矩阵结果 

3.图的深度优先遍历 

思路:

1.访问初始节点v,并标记节点v已访问

2.查找节点v的第一个邻接节点w

3.若w存在,则继续执行4,若其不存在,则回到1,将从v的下一个节点继续

4.若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行123)

5.查找节点v的w邻接节点的下一个邻接节点,在进行步骤3

代码 

1,辅助方法
    //    得到第一个邻接节点的下标w

    /**
     * @param index
     * @return 如果存在返回对应下标,否则-1
     */
    public int getFirstNeighbor(int index) {
        for (int j = 0; j < vertexList.size(); j++) {
            if (edges[index][j] > 0) {
                return j;
            }
        }
        return -1;
    }

    //    根据前一个邻接接节点的下标获取下一个邻接节点
    public int getNextNeighbor(int v1, int v2) {
        for (int j = v2 + 1; j < vertexList.size(); j++) {
            if (edges[v1][j] > 0) {
                return j;
            }
        }
        return -1;
    }
2.dfs算法
    //    深度优先遍历算法
    private void dfs(boolean[] isVisited, int i) {
//        首先访问该节点
        System.out.print(getValueByIndex(i) + "->");
//        访问了给他设置访问
        isVisited[i] = true;
//        查找i节点第一个邻接节点w
        int w = getFirstNeighbor(i);
        while (w != -1) {
            if (!isVisited[w]) {
                dfs(isVisited, w);
            }
//            如果w已经被访问
            w = getNextNeighbor(i, w);
        }
    }

    //    对dfs进行一个重载遍历所有的节点
    public void dfs() {
        isVisited = new boolean[5];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                dfs(isVisited, i);
            }
        }
    }

4.图的广度优先

思路:

1.访问初始节点v,并标记节点v已访问

2.节点v入队列

3.当队列非空时,继续执行,否则算法结束

4.出队列,取得队列头结点u

5.查找节点u的第一个邻接节点w

6.若w不存在,则进行步骤3,否则循环执行下三个步骤

        6.1若w未被访问,则访问节点w并标记已访问

        6.2节点w入队列

        6.3查找u的继w邻接节点后的下一个邻接节点w,再进行步骤6

代码 

1.bfs算法
//    对一个节点进行广度优先遍历
    public void bfs(boolean[] isVisited, int i) {
        int u; //表示队列的头结点对应的下标
        int w; //邻接节点
//        队列,记录节点访问的顺序
        LinkedList queue = new LinkedList();
        System.out.print(getValueByIndex(i) + "->");
        isVisited[i] = true;
        queue.addLast(i);
        while (!queue.isEmpty()) {
            u = (Integer)queue.removeFirst();
            w = getFirstNeighbor(u);
            while (w != -1) {
                if (!isVisited[w]) {
                    System.out.print(getValueByIndex(w) + "->");
                    isVisited[w] = true;
                    queue.addLast(w);
                }
                w = getNextNeighbor(u, w);
            }
        }
    }

//    遍历所有的节点广度优先
    public void bfs() {
        isVisited = new boolean[5];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                bfs(isVisited, i);
            }
        }
    }

5.dfs和bfs代码运行结果

六、总结 

        图作为复杂网络关系的有效模型,在现代计算机科学中扮演着至关重要的角色。理解并熟练掌握图相关的数据结构与算法,不仅能帮助我们解决众多实际问题,更能提升我们在复杂系统分析和设计方面的综合能力。无论是理论研究还是软件开发实践,图都是每一位计算机科学家和技术开发者必须掌握的核心知识点。

 

  • 44
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值