【算法和数据结构】图(一)图的定义和封装(C++实现)

       图,一种复杂的数据结构,在实际生活中起着举足轻重的作用。如路网(线路规划),社交网络描述等等。现在我们就以下面这个简单无向图(图1)为例,来说明使用邻接矩阵 实现图在计算机中的存储和操作的方法。
这里写图片描述
              图1

       不同于树,我们在习惯上称呼如图中数字编号的店为顶点而非节点。在树的概念中,存在一个特殊的节点称之为根,其起着好似渔网的主绳的作用,缺之不可。而在图这种数据结构中,更加倾向于平等地看待每一个顶点。

        在下面给出的实现中,我们用一维数组来表示邻接矩阵。若图有n个顶点,则数组大小即为n*n。第row行,col列的元素实际上存储在数组下标为row*n+col的位置。

        关于邻接矩阵的定义这里不再赘述,下图2给出图1所示简单无向图的邻接矩阵,希望能够帮助读者理解。
这里写图片描述
                图2

        下面给出以邻接矩阵表示图的C++实现,其中封装了对图的常用操作(具体见代码注释):

/*节点类*/
class Node
{
public:
    Node(char identifier = 0);

    char m_identifier;       //顶点编号
    bool m_isVisited;        //顶点访问标志位:true表示已经被访问
};
Node::Node(char identifier)
{
    m_identifier = identifier;
    m_isVisited = false;
}


/*图类*/
class Graph
{
public:
    Graph(int capacity);
    ~Graph();

    void resetNode();              //重置所有顶点的访问标志位为false,未访问
    bool addNode(Node *pNode);     //添加新顶点
    bool addEdgeForUndirectedGraph(int row, int col, int val = 1); //添加边以构造无向图,val表示权值,默认连通
    bool addEdgeForDirectedGraph(int row, int col, int val = 1);   //添加边以构造有向图,val表示权值,默认连通
    void printMatrix();   //打印邻接矩阵

    void depthFirstTraverse(int nodeIndex);   //深度优先遍历,指定第一个点
    void widthFirstTraverse(int nodeIndex);   //广度优先遍历,指定第一个点

private:
    bool getValueOfEdge(int row, int col, int &val);  //获取边权值
    void widthFirstTraverseImplement(vector<int> preVec);  //利用vector实现广度优先遍历

    int m_iCapacity;     //图容量,即申请的数组空间最多可容纳的顶点个数
    int m_iNodeCount;    //图的现有顶点个数
    Node *m_pNodeArray;  //存放顶点的数组
    int *m_pMatrix;      //为了方便,用一维数组存放邻接矩阵
};
Graph::Graph(int capacity)
{
    m_iCapacity = capacity;
    m_iNodeCount = 0;

    m_pNodeArray = new Node[m_iCapacity];
    m_pMatrix = new int[m_iCapacity*m_iCapacity];

    for (int i = 0;i < m_iCapacity*m_iCapacity;i++)  //初始化邻接矩阵
    {
        m_pMatrix[i] = 0;
    }
}
Graph::~Graph()
{
    delete []m_pNodeArray;
    delete []m_pMatrix;
}
void Graph::resetNode()
{
    for (int i = 0;i < m_iNodeCount;i++)
    {
        m_pNodeArray[i].m_isVisited = false;
    }
}
bool Graph::addNode(Node *pNode)
{
    if (pNode == NULL)
        return false;
    m_pNodeArray[m_iNodeCount].m_identifier = pNode->m_identifier;
    m_iNodeCount++;
    return true;
}
bool Graph::addEdgeForUndirectedGraph(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;
}
bool Graph::addEdgeForDirectedGraph(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;
}
void Graph::printMatrix()
{
    for (int i = 0;i < m_iCapacity;i++)
    {
        for (int k = 0;k < m_iCapacity;k++)
            cout << m_pMatrix[i*m_iCapacity + k] << " ";
        cout << endl;
    }
}
void Graph::depthFirstTraverse(int nodeIndex)
{
    int value = 0;

    //访问第一个顶点
    cout << m_pNodeArray[nodeIndex].m_identifier << " ";
    m_pNodeArray[nodeIndex].m_isVisited = true;

    //访问其他顶点
    for (int i = 0;i < m_iCapacity;i++)
    {
        getValueOfEdge(nodeIndex, i, value);
        if (value != 0)   //当前顶点与指定顶点连通
        {
            if (m_pNodeArray[i].m_isVisited == true)  //当前顶点已被访问
                continue;
            else           //当前顶点没有被访问,则递归
            {
                depthFirstTraverse(i);
            }
        }
        else       //没有与指定顶点连通
        {
            continue;
        }
    }
}
void Graph::widthFirstTraverse(int nodeIndex)
{
    //访问第一个顶点
    cout << m_pNodeArray[nodeIndex].m_identifier << " ";
    m_pNodeArray[nodeIndex].m_isVisited = true;

    vector<int> curVec;
    curVec.push_back(nodeIndex);      //将第一个顶点存入一个数组
    widthFirstTraverseImplement(curVec);
}
void Graph::widthFirstTraverseImplement(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++)  //传入的数组中的顶点是否与其他顶点连接
        {
            getValueOfEdge(preVec[j], i, value);
            if (value != 0)   //连通
            {
                if (m_pNodeArray[i].m_isVisited==true)  //已经被访问
                {
                    continue;
                }
                else   //没有被访问则访问
                {
                    cout << m_pNodeArray[i].m_identifier << " ";
                    m_pNodeArray[i].m_isVisited = true;

                    //保存当前点到数组
                    curVec.push_back(i);
                }
            }
        }
    }

    if (curVec.size()==0)   //本层次无被访问的点,则终止
    {
        return;
    }
    else
    {
        widthFirstTraverseImplement(curVec);
    }
}
bool Graph::getValueOfEdge(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;
}

        特别需要提醒的是:

  1. 在实现图遍历时应用了C++标准数组模板vector,读者在使用时应该包含相关头文件。
  2. 构造边的时候封装了两个函数,一个是对于有向图,一个是对于无向图,读者对比两种图形的邻接矩阵和实现可以很容易理解。

        另外,在前面对于二叉树的介绍中,我们使用了数组和链表两种方式来表示树。其实对于图来说,也拥有着两种表示方式。上述邻接矩阵的表示方法即使用数组,另外还有使用链表的如邻接表、十字链表、邻接多重表等等。他们在表示有向图、无向图和运算代价方面各有优劣,这里不再做详细介绍。有兴趣的读者朋友可以查阅相关资料。

        点击这里下载完整源码,包括最小生成树等方法的封装。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是C语言代码实现: ```c #include <stdio.h> #include <stdlib.h> #define MAXN 100 #define QUEUE_SIZE 100 int A[MAXN][MAXN]; // 邻接矩阵 int visited[MAXN]; // 记录结点是否被访问过 // 定义队列结构体 struct Queue { int data[QUEUE_SIZE]; int front; int rear; }; // 初始化队列 void initQueue(struct Queue *q) { q->front = 0; q->rear = 0; } // 判断队列是否为空 int isQueueEmpty(struct Queue *q) { return q->front == q->rear; } // 入队 void enqueue(struct Queue *q, int x) { if ((q->rear + 1) % QUEUE_SIZE == q->front) { printf("Queue is full!\n"); exit(1); } q->data[q->rear] = x; q->rear = (q->rear + 1) % QUEUE_SIZE; } // 出队 int dequeue(struct Queue *q) { if (isQueueEmpty(q)) { printf("Queue is empty!\n"); exit(1); } int x = q->data[q->front]; q->front = (q->front + 1) % QUEUE_SIZE; return x; } // BFS遍历连通分量 void BFS(int v, int n) { struct Queue q; initQueue(&q); visited[v] = 1; // 标记结点v已被访问 enqueue(&q, v); // 将结点v入队 while (!isQueueEmpty(&q)) { int u = dequeue(&q); // 取出队头结点 for (int i = 0; i < n; i++) { if (A[u][i] && !visited[i]) { // 如果结点i与结点u相邻且未被访问 visited[i] = 1; // 标记结点i已被访问 enqueue(&q, i); // 将结点i入队 } } } } // 统计连通分量的个数 int countConnectedComponents(int n) { int count = 0; for (int i = 0; i < n; i++) { if (!visited[i]) { // 如果结点i未被访问 count++; // 连通分量个数加1 BFS(i, n); // 遍历结点i所在的连通分量 } } return count; } int main() { int n; scanf("%d", &n); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { scanf("%d", &A[i][j]); } } printf("The number of connected components is %d.\n", countConnectedComponents(n)); return 0; } ``` 代码中使用了邻接矩阵存储,用visited数组记录每个结点是否被访问过。BFS遍历连通分量时,使用了队列数据结构。最后统计连通分量的个数,即为所求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值