大师兄的数据结构学习笔记(九): 图

大师兄的数据结构学习笔记(八): 哈夫曼树与哈夫曼编码
大师兄的数据结构学习笔记(十): 伸展树

一、什么是图(Graph)

  • 图表示多对多的关系。
  • 线性结构和树都可以看成是图的特殊情况。
1. 关于顶点
  • 图包含一组顶点。
  • 通常用V(Vertex)表示顶点集合。
2. 关于边
  • 图包含一组边。
  • 通常用E(Edge)表示边的集合。
  • 边表示的是顶点和顶点之间的某种关系:

1) 无向边

  • (v,w) \in E,其中v,w \in V
  • 如果可以从v到w, 也可以从w到v。


2) 有向边

  • <v,w> \in E,其中v,w \in V
  • 表示从v指向w的边(单行线)。


  • 在图中,不考虑重边和自回路。
3. 抽象数据类型
  • 类型名称:图(Graph)
  • 数据额对象集:G(V,E)由一个非空的有限顶点集合V和一个有限边集合E组成。
  • 常见操作集:对于任意图G\in Graph, 以及v \in V, e\in E
操作集 说明
Graph Create() 建立并返回空图。
Graph InsertVertex(Graph G,Vertex v) 将v插入G。
Graph InsertEdge(Graph G,Edge e) 将e插入G。
void DFS(Graph G,Vertex v) 从顶点v出发深度优先遍历图G。
void BFS(Graph G,Vertex v) 从顶点v出发宽度优先遍历图G。
void ShortestPath(Graph G,Vertex v,int Dist[]) 计算图G中顶点v到任意其他顶点的最短距离。
void MST(Graph G) 计算图G的最小生成树。
4. 常见术语
  • 无向图(undirected edge): 图中所有的边都是无方向的。
  • 有向图(directed edge): 图中的边可能是有向边或无向边,但方向是有意义的。
  • 带权网络(weighted network): 图中的边是带权重的,用来表示顶点之间关系的细节。
  • 邻接点(adjacent node): 有边直接相连的顶点。
  • 度(degree): 在无向图中,与顶点v关联的边数,成为v的度数,记作deg(v)。
  • 出度: 从该点出发的边数。
  • 入度: 指向该点的边数。
  • 简单图(simple graph): 不含任何自环(self-loop)的图。
  • 通路(path): 由m+1个顶点与m条边交替而成的序列。
  • 环路(cycle): 对于长度m\geq1的通路\pi,起止顶点相同(v_0=v_m)。
  • 稀疏图(sparse graph): 点很多而边很少。
  • 完全图(complete graph): 任意两个顶点间,都有一条边。

二、在程序中表示图

1. 邻接矩阵(adjacency matrix)
  • 是图最基本的实现方式。
  • 使用方阵G[N][N]表示由N个顶点构成的图,其中每个单元各自负责描述一对顶点之间可能存在的邻接关系。
  • 若<V_i,V_j>是G中的边, G[i][j] = 1。
  • 否则G[i][j] = 0。
优点 缺点
1. 直观、简单、好理解。
2. 方便检查任意一对顶点间是否存在边。
3. 方便找任一顶点的所有邻接点。
4. 方便计算任一顶点的度。
1. 稀疏图浪费空间 - 存有大量无效元素(0)。
2. 稀疏图浪费时间 - 统计总边数需要遍历所有结点。
#ifndef NODE_H_
#define NODE_H_

class Node
{
public:
    Node(char data = 0); // 构造结点
    char m_cData; // 数据
    bool m_bIsVisted; // 是否被访问过
};
#endif // !NODE_H_
#include "node.h"

Node::Node(char data)
{
    m_cData = data;
    m_bIsVisted = false; //默认未访问
}
#ifndef GRAPH_H_
#define GRAPH_H_
#include <vector>
#include "node.h"
using namespace std;

class Graph
{
public:
    Graph(int capacity); //构造函数
    ~Graph();           //析构函数
    void resetNode(); //重置节点
    bool InsertVertex(Node* pNode); // 插入节点
    void DFS(int nodeIndex); //深度优先遍历
    void BFS(int nodeIndex); //宽度优先遍历
    void setValueToMatrixForDirectedGraph(int row, int col, int val = 1); //给有向图的邻接矩阵元素设置值
    void setValueToMatrixForUndirectedGraph(int row, int col, int val = 1);//给无向图的邻接矩阵的元素设置值
    void printMatrix(); //打印邻接矩阵
private:
    int m_iCapacity; // 节点容量
    int m_iNodeCount; //当前节点数量
    Node* m_pNodeArray; //指向第一个节点的指针
    int* m_pMatrix; // 指向邻接矩阵首元素的指针
    bool getValueFromMatrix(int row, int col, int& val); // 获取临街矩阵中元素的值
    void breadthFirstTraverseImpl(vector<int> preVec);
};

#endif // !GRAPH_H_
#include <iostream>
#include "graph.h"
using namespace std;

//构造函数
Graph::Graph(int capacity)
{
    m_iCapacity = capacity;
    m_iNodeCount = 0;
    m_pNodeArray = new Node[m_iCapacity];
    m_pMatrix = new int[m_iCapacity * m_iCapacity];
    memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int)); // 初始化matrix
}

//析构函数
Graph::~Graph()
{
    delete[]m_pNodeArray; // 释放节点
    delete[]m_pMatrix;    //释放矩阵
}

//重置节点
void Graph::resetNode()
{
    for (int i = 0; i < m_iNodeCount; i++)
    {
        m_pNodeArray[i].m_bIsVisted = false;
    }
}

// 插入节点
bool Graph::InsertVertex(Node* pNode)
{
    if (pNode == nullptr) return false;
    m_pNodeArray[m_iNodeCount] = pNode->m_cData;
    m_iNodeCount++;
    return true;
}

// 获取临街矩阵中元素的值
bool Graph::getValueFromMatrix(int row, int col, int& val)
{
    if (row < 0 || row >= m_iCapacity)
    {
        return false;
    }

    if (col < 0 || col >= m_iCapacity)
    {
        return false;
    }
    val = m_pMatrix[row * m_iCapacity + col];
    return true;
}

void Graph::breadthFirstTraverseImpl(vector<int> preVec)
{
    int value = 0;
    vector<int> curVec;
    for (int j = 0; j < (int)preVec.size(); j++)
    {   
        for (int i = 0; i < m_iCapacity; i++) {
            getValueFromMatrix(preVec[j], i, value);
            if (value == 0) continue;
            if (m_pNodeArray[i].m_bIsVisted) continue;
            cout << m_pNodeArray[i].m_cData << " ";
            m_pNodeArray[i].m_bIsVisted = true;
            curVec.push_back(i);
        }
    }
}

//深度优先遍历
void Graph::DFS(int nodeIndex)
{
    int value = 0;
    cout << m_pNodeArray[nodeIndex].m_cData << " ";  // 打印值
    m_pNodeArray[nodeIndex].m_bIsVisted = true; // 修改状态

    for (int i = 0; i < m_iCapacity; i++)
    {
        getValueFromMatrix(nodeIndex, i, value);
        if (value == 0) continue;
        if (m_pNodeArray[i].m_bIsVisted) continue;
        DFS(i);
    }
}

//宽度优先遍历
void Graph::BFS(int nodeIndex)
{
    cout << m_pNodeArray[nodeIndex].m_cData << " ";
    m_pNodeArray[nodeIndex].m_bIsVisted = true;
    vector<int> curVec;
    curVec.push_back(nodeIndex);
    breadthFirstTraverseImpl(curVec);
}

//给有向图的邻接矩阵元素设置值
bool Graph::setValueToMatrixForDirectedGraph(int row, int col, int val)
{
    if (row < 0 || row >= m_iCapacity)
    {
        return false;
    }
    if (col < 0 || col >= m_iCapacity)
    {
        return false;
    }
    m_pMatrix[row * m_iCapacity + col] = val;
    return true;
}

//给无向图的邻接矩阵的元素设置值
bool Graph::setValueToMatrixForUndirectedGraph(int row, int col, int val)
{
    if (row < 0 || row >= m_iCapacity)
    {
        return false;
    }
    if (col < 0 || col >= m_iCapacity)
    {
        return false;
    }
    m_pMatrix[row * m_iCapacity + col] = val;
    m_pMatrix[col * m_iCapacity + row] = val;
    return true;
}

//打印邻接矩阵
void Graph::printMatrix()
{
    for (int i = 0; i < m_iCapacity; i++)
    {
        for (int k = 0; k < m_iCapacity; k++)
        {
            cout << m_pNodeArray[i*m_iCapacity+k].m_cData << " ";
        }
        cout << endl;
    }
}
2. 邻接表(adjacency list)
  • G[N]为指针数组,对应矩阵每行一个链表,只存非0元素。
优点 缺点
1. 方便找任一丁点的所有邻接点
2. 节约稀疏图空间。
3. 方便计算无向图任一顶点的
1. 只能计算有向图的出度,计算入度需要构造逆邻接表
2. 不方便检查任意一对顶点间是否存在边。
#ifndef QUEUE_H
#define QUEUE_H

const int queueSize = 100;

template<class T>
//表结构
class queue
{
public:
    T data[queueSize];
    int front, rear;
};

#endif
#ifndef GRAPH_H
#define GRAPH_H
#include "queue.h"

// 边表结点
struct ArcNode
{
    int adjvex; // 邻接点
    ArcNode* next; 
};


// 顶点表结点
struct VertexNode
{
    int vertex;
    ArcNode* firstedge;
};


const int MaxSize = 10;
int visited[MaxSize] = {0}; // 是否被访问

template<class T>
class Graph
{
public:
    Graph(T a[], int n, int e); //建立n个顶点,e条边的图
    ~Graph() {};                // 析构函数
    void DFS(int v);            // 深度优先搜索
    void BFS(int v);            // 广度优先搜索
private:
    VertexNode adjlist[MaxSize];// 图中的顶点
    int vertexNum;              // 图中的顶点数
    int arcNum;                 // 图中的边数
};

#endif
#include <iostream>
#include "graph.h"
using namespace std;

template<class T>
// 构造函数
Graph<T>::Graph(T a[], int n, int e)
{
    vertexNum = n;
    arcNum = e;
    // 初始化顶点
    for (int i = 0; i < vertexNum; i++)
    {
        adjlist[i].vertex = a[i];
        adjlist[i].firstedge = nullptr;
    }
    // 初始化边
    for (int i = 0; i < arcNum; i++)
    {
        int k, j;
        cin >> k >> j;
        ArcNode* s = new ArcNode;
        s->adjvex = j;
        s->next = adjlist[k].firstedge;
        adjlist[k].firstedge = s;
    }
}

template<class T>
// 深度优先
void Graph<T>::DFS(int v)
{
    cout << adjlist[v].vertex;
    visited[v] = 1;
    ArcNode* p = adjlist[v].firstedge;
    while (p != nullptr)
    {
        int j = p->adjvex;
        if (visited[j]==0)
        {
            DFS(j);
        }
        p = p ->next;
    }
}

template<class T>
// 广度优先
void Graph<T>::BFS(int v)
{
    int visited[MaxSize] = { 0 };
    queue<T> Q;
    Q.front = Q.rear = -1; // 初始化队列
    cout << adjlist[v].vertex;
    visited[v] = 1;
    Q.data[++Q.rear] = v;
    while (Q.front != Q.rear)
    {
        v = Q.data[++Q.front];
        ArcNode* p = adjlist[v].firstedge;
        while (p != nullptr)
        {
            int j = p->adjvex;
            if (visited[j] == 0)
            {
                cout << adjlist[j].vertex;
                visited[j] = 1;
                Q.data[++Q.rear] = j;
            }
            p = p->next;
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355