图的概念及深度优先搜索和广度优先搜索

本文深入探讨了图的基本概念,并详细介绍了两种重要的图遍历算法——深度优先搜索(DFS)和广度优先搜索(BFS),包括它们的工作原理、实现方式以及在实际问题中的应用。通过实例解析,帮助读者理解如何运用这两种算法解决图论问题。
摘要由CSDN通过智能技术生成
图是一种复杂的非线性结构。
G 是由两个集合 V(G) E(G) 组成 的,记为 G=(V,E )
 其中: V(G) 是顶点的非空有限集,   E(G) 是边的有限集合,边是顶点的无序对或有序对


一、有向图和无向图
无向图——无向图G是由两个集合V(G)E(G)组成,   V(G) 是顶点的非空有限集, E(G) 是边的有限集合,边是顶 点的无序对,记为( vi,vj 或( vj,vi ) 并且( vi,vj )=( vj,vi )
有向图 —— 有向图 G 是由两个集合 V(G) E(G) 组成,   V(G) 是顶点的非空有限集, E(G) 是有向边(也称弧)的有限集合,弧是顶点的有序对,记为 < vi,vj > vi,vj 是顶点, vi 为弧尾, vj 为弧头。

例如:有向图



二、完全图
概念:图中任意两点之间恰有一条遍相连。
因此: 1、 有向完全图  : n 个顶点的有向图最大边数是 n(n-1)   (边是有方向的)
           2、无向完全图 :n 个顶点的无向图最大边数是 n(n-1)/2

三、邻接点、度
邻接点 :  若(vi,vj)是一条无向边,则称vi和vj互为邻接点
              (没有找到有向图的邻接点,我认为弧尾是弧头的连接点)
关联    :   vi和vj互为邻接点,称边(vi,vj)关联于顶点vi和vj
顶点的度
1、对于无向图,某顶点的度为与该顶点相连的边的数量 记为 D(vi)
2、对于有向图,顶点的度分成 入度与出度,其中入度是以该顶点为头的弧的数目,记为ID(v)。出度是以该顶点为尾的弧的数目,记为OD(v)。顶点的度为入度和出度的和,即 D(v)=ID(v)+OD(v)。
不论是有向图还是无向图,边和度的关系: e = (d(v1) + d(v2) + ...d(vn)) / 2
子图:

四、路径和回路
路径        存在顶点的序列 V={Vp,Vi 1 , …… Vi n ,Vq } 满足( Vp ,Vi 1 ),  ( Vi 1 ,Vi 2 ),   ,  ( Vi n , Vq ) 均属于边集 E (G), 则称 vp vq 存在路径。
路径长度:沿路径边的数目 沿路径各边权值之和。
回路       : 第一个顶点和最后一个顶点相同的路径叫回路
简单路径 序列中顶点不重复出现的路径叫简单路径
简单回路 除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路叫简单回路


五、连通图
连通       : 从顶点 Vi 到顶点 vj 有一条路径,则说 Vi vj 是连通的
连通图    : 图中任意两个顶点都是连通的图叫 连通图
连通分量 : 无向图中的 最大 ( 含的边和顶点最多 ) 连通
强连通图 : 有向图中,如果对每一对属于顶点集合的Vi,Vj(ij不等), ,从Vi到Vj 和从Vj到 Vi都存在路径,则称G是强连通图

六:权和网、生成树
权    与图的边或弧相关的数叫
网   : 带权的图叫


一个连通图的 生成树 是一个 极小连通子图 ,它含有图中全部顶点,但只有足以构成一棵树的 n-1 条边。
生成树算法:
1、普里姆(Prim)算法:选择一个最小的边进行扩充(扩充时,选择两个顶点与其他权重最小的),直到生成一个最小连通子图
2、 Kruskal算法 : 依次选择权重最小的边,直到生成最小连通子图
一棵有 n 个顶点的生成树有且仅有 n-1 条边
有向树: 只有一个顶点的入度为 0 ,其余顶点的入度为 1 的有向图。



八:存储结构
8.1 邻接矩阵(二维数组)
     无向图:如果i和j有连接 Eij = Eji = 1 或者权重, 否则为0,(斜对称矩阵)。 行(列)非零元素个数,表示度      
     有向图:Eij  = 权重,如果有i到j直接连接的边。   行非零元素个数,表示出度;   列非零元素个数,表示入度

     int A[n][n]; // n 为顶点的个数
     特别是对于有向图,邻接矩阵极大浪费空间

8.2   邻接表(大部分用)空间复杂度 O(n+e)

邻接表是图的一种链式存储结构。这种存储结构类似于树的孩子链表。对于图G中每个顶点V i ,把所有邻接于V i 的顶点V j 链成一个单链表,这个单链表称为顶点V i 的邻接表。
对图中每个顶点建立一个邻接关系的 链表 ,并将其表头指针用向量 ( 一维数组 ) 存储 , 该结构称为邻接表

邻接表能很容易算出出度,如果用到入度,需要建立 逆邻接表
数据结构见下面代码

九、遍历
遍历有广度优先遍历( breadth-first search, BFS )和深度优先遍历(DFS),都用到数组来标记是否访问到。
(存储结构不确定,则遍历结果不唯一)
9.1 广度优先遍历
9.2 深度优先遍历

#include <iostream>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
// 每一个顶点的边链表
struct Edge {
    int  verAdj;  // 邻接点的序号
    int  cost;    // 边的权值(顶点到这个点的)
    Edge *next;   // 指向下一个边节点
    Edge(int verVal, int costVal = 1) {
        verAdj = verVal;
        cost   = costVal;
        next   = NULL;
    }
};
// 用数组存顶点节点
struct Vertex {
    int  verName;   // 顶点名称
    Edge *adjacent; // 边链表的头指针
    Vertex(){verName = 0; adjacent = NULL;}
    Vertex(int val) {verName = val; adjacent = NULL;}
    Vertex(int val, Edge *edge) {
        verName  = val;
        adjacent = edge;
    }
};
/**
 * 为了处理方便,verName与顶点数组的下表一样
 * 否则还需要加一个数组,用来指示verName指向数组索引
 */
class GraspList {
private:
    Vertex *head;
    int graspSize; // 图点的个数
public:
    GraspList(int graspSize) {
        this->graspSize = graspSize + 1; // 目前 0到n是 n+1 个
        this->head = new Vertex[this->graspSize];
        for (int i = 0; i < this->graspSize; ++i) {
            this->head[i].verName  = i;
            this->head[i].adjacent = NULL;
        }
    }
    void createGraspList() {
        int rows;
        cin >> rows; // 输入行数(边个数)
        for (int i = 0; i < rows; ++i) {
            int vBegin, vEnd;
            cin >> vBegin >> vEnd;
            Edge *edge = new Edge(vEnd);
            if (this->head[vBegin].adjacent == NULL) {
                this->head[vBegin].adjacent = edge;
            } else {
                Edge *tmpHead = this->head[vBegin].adjacent;
                while(tmpHead->next != NULL) {
                    tmpHead = tmpHead->next;
                }
                tmpHead->next = edge;
            }
        }
    }
    /**
     * 宽度优先遍历, 利用队列,把每个没有访问的顶点入队列
     * @param
     */
    void bfs() {
        queue<Vertex> bfsQueue;
        queue<int>    result;
        vector<int>   visited(this->graspSize, 0);
        for (int i = 0; i < this->graspSize; ++i) {
            if (visited[i] == 1) {
                continue;
            }
            bfsQueue.push(this->head[i]);
            while (!bfsQueue.empty()) {
                Vertex tmp = bfsQueue.front();
                bfsQueue.pop();
                result.push(tmp.verName);
                visited[tmp.verName] = 1;
                Edge *tmpEdge = tmp.adjacent;
                while (tmpEdge != NULL) {
                    int verName = tmpEdge->verAdj;
                    if (visited[verName] == 0) { // 未被访问
                        bfsQueue.push(this->head[verName]);
                    }
                    tmpEdge = tmpEdge->next;
                }
            }
        }// end for
        while (!result.empty()) {
            cout << result.front() << "->";
            result.pop();
        }
        cout << endl;
    }
    /**
     * 深度优先遍历,利用栈,先把一个如栈,然后在对栈顶的节点进行
     */
    void dfs() {
        stack<Vertex> dfsStack;
        queue<int>    result;
        vector<int>   visited(this->graspSize, 0);
        for (int i = 0; i < this->graspSize; ++i) {
            if (visited[i] == 1) {
                continue;
            }
            dfsStack.push(this->head[i]);
            while (!dfsStack.empty()) {
                Vertex tmp = dfsStack.top();
                dfsStack.pop();
                result.push(tmp.verName);
                visited[tmp.verName] = 1;
                Edge *tmpEdge = tmp.adjacent;
                while (tmpEdge != NULL) {
                    int verName = tmpEdge->verAdj;
                    if (visited[verName] == 0) {
                        dfsStack.push(this->head[verName]);
                    }
                    tmpEdge = tmpEdge->next;
                }
            }
        }// end for
        while (!result.empty()) {
            cout << result.front() << "->";
            result.pop();
        }
        cout << endl;
    }
};
int main()
{
    int verCount; // 顶点个数,从0开始
    cin >> verCount;
    GraspList *gl = new GraspList(verCount);
    gl->createGraspList();
    gl->bfs();
    gl->dfs();
    cout << "Hello World!" << endl;
    return 0;
}











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值