数据结构(C语言)图的基本操作及应用

图的基本操作及应用

  • 用邻接表法创建无向图
  • 以邻接表形式打印无向图
  • 深度遍历无向图并打印
  • 利用队列知识实现广度遍历无向图并打印
  • 取各顶点在表中序号

头文件及数据

  • 头文件定义及所需数据
#include <iostream>
using namespace std;
#define MAX 100     //最大顶点数

图存储结构

  • 边结点的存储结构:这个边所指向的顶点的位置、指向连接这个顶点的下一条边的指针(比如这个顶点为1,则nextB第一个指向边12,nextB下一个则会指向边13,再下一个则指边14)、这条边的相关信息(如权值)
typedef struct BNode
{
    int pointPosite;             //该边所指向顶点的位置
    struct BNode *nextB;         //指向下一条边的指针
    int info;            		 //和边相关的信息
}BNode;
  • 顶点的存储结构:存放顶点的数据域、指向连接这个顶点的第一条边(这个有些不好理解,如下图:比如顶点为1,则顶点1的firstB为边12,若在循环里下一个firstB则指向边25)
typedef struct DNode
{
    string data;                   //存放顶点信息
    BNode *firstB;              //指针指向第一条依附该顶点的边
}DNode,AdjList[MAX];//AdjList表领接表类型``
 - [ ] ***邻接表的存储结构***
 

```cpp
typedef struct
{
    AdjList vertices;           //顶点向量
    int DNum, BNum;             //图的当前顶点数和边数
}TGraph;

队列的存储表示

  • 顺序队列的存储表示(先进先出)
typedef struct
{
    string *base;  	//存储空间的基地址
    int front;		//头指针
    int rear;		//尾指针
}SqQueue;

队列的初始化、入队、出队、判空

  • 队列的初始化
void InitQueue(SqQueue &Q)
{//构造一个空队列
    Q.base = new string[MAX];	
    if(!Q.base)
    {
        cout<<"内存分配失败"<<endl;
    }
    Q.front = Q.rear = 0;
}
  • 入队
void EnQueue(SqQueue &Q, string e)
{
    if((Q.rear+1)%MAX == Q.front)
    {
        cout<<"队列已满"<<endl;
    }
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear+1)%MAX;
}
  • 出队
string DeQueue(SqQueue &Q, string &e)
{
    if(Q.front == Q.rear)
    {
        cout<<"队列为空"<<endl;
    }
    e = Q.base[Q.front];
    Q.front = (Q.front+1)%MAX;
    return e;
}
  • 判断是否为空队列
bool Empty(SqQueue Q)
{
    if(Q.front == Q.rear)
    {
        return true;
    }
    return false;
}

邻接表法创建无向图

  • 首先要求输入所要创建无向图的总顶点数和总边数,第二步在循环里输入顶点建立表头结点表,最后利用循环,要求输入各条边依附的两个顶点的信息,取两顶点在表头结点表中的序号,利用两个指针p1,p2分别使用头插法构建邻接表
void CreateUDG(TGraph &G)       //无向图UDG
{
    string v1,v2;
    cout<<"请输入所要创建图的总顶点数和总边数:";
    cin>>G.DNum>>G.BNum;         //输入总顶点数和总边数
    for(int i = 0; i < G.DNum; ++i) //输入各点,构造表头结点表
    {
        cout<<"请输入第"<<i+1<<"个顶点的值:";
        cin>>G.vertices[i].data;    //输入顶点值
        G.vertices[i].firstB = NULL;//初始化表头结点指针域为空
    }
    for(int k = 0; k < G.BNum; ++k) //输入各边,构造领接表
    {
        BNode *p1,*p2;
        cout<<"请输入第"<<k+1<<"条边的信息:";
        cin>>v1>>v2;                //输入一条边依附的两个顶点
        int m = LocateVex(G, v1); int n = LocateVex(G, v2); //得到v1 v2在G.vertices中的序号
        p1 = new BNode;         //生成一个新的边结点*p1
        p1->pointPosite = n;    //邻接点序号为n
        p1->nextB = G.vertices[m].firstB;   //头插法
        G.vertices[m].firstB = p1;
        p2 = new BNode;
        p2->pointPosite = m;
        p2->nextB = G.vertices[n].firstB;
        G.vertices[n].firstB = p2;
    }
}

打印该图邻接表

  • 定义指针p,外层for循环遍历输出每个结点信息,for循环中p指针依次指向每个顶点的邻接点,当指针p不为空时,进入内层while循环,首先输出指针p的data信息,再移动指针p指向下一个邻接点信息继续while循环
void UDGprint(TGraph G)
{
    BNode *p;
    for(int i = 0; i < G.DNum; ++i)
    {
        cout<<G.vertices[i].data;
        p = G.vertices[i].firstB;
        while(p)
        {
            cout<<"->";
            cout<<p->pointPosite;
            p = p->nextB;
        }
        cout<<endl;
    }
}

取所给顶点在表中的序号

  • 对表中所有顶点进行遍历,若表中的第i个data的值与所给顶点相等,则返回i,否则返回-1
int LocateVex(TGraph G, string v)
{
    for(int i = 0; i < G.DNum; ++i)
    {
        if(v == G.vertices[i].data)
        {
            return i;
        }
    }
    return -1;
}

定义标志数组

  • 定义标志数组,其初值全为false,以便标记顶点是否遍历,其初值为false;
bool visited[MAX];

深度优先遍历图

  • 首先输出第一个开始顶点v,且设置为已访问。定义指针p,访问指定开始顶点的第一个邻接点,p不为空时进入while循环,将邻接点的序号赋给w,判断是否被访问过,若没有则以w为参数对函数递归;若被访问过,p指向下一个边结点进入循环
void deepTravel(TGraph G, string v)
{
    int b,w;
    BNode *p;
    cout<<v<<" ";
    b = LocateVex(G, v);
    visited[b] = true;  //访问第b个顶点,标志为true
    p = G.vertices[b].firstB;    //p指向v的边链表的第一个边结点
    while(p != NULL)            //如果p不为空
    {
        w = p->pointPosite;     //w为v的第一个边结点
        if(!visited[w])         //若w未被访问 则继续递归
        {
            string a;
            for(int i = 0; i < G.DNum; ++i)
            {
                if(w == i)
                {
                    a = G.vertices[i].data;
                }
            }
            deepTravel(G, a);
        }
        p = p->nextB;       //否则p指向下一个v的边结点
    }
}

广度优先遍历图

  • 首先将所有顶点的标记数组设置为未访问false;第二步输出第一个开始顶点v,且设置为已访问,并将开始顶点v入队;第三步若队列不为空则进入外层while循环,将队列出队,并将指针p指向出队顶点v的第一个邻接点;第四步若指针p不为空则进入内层while循环,if判断指针p所指向的邻接点是否被访问过,若没有被访问过则输入所指向的邻接点data,同时将此邻接点设置为已被访问且将此邻接点入队;若指针p所指向的邻接点被访问过,则移动指针p指向v的下一个邻接点继续进入循环
void breadthTravel(TGraph G, string v)
{
    BNode *p;
    int l;
    string e;
    int b = LocateVex(G, v);
    SqQueue Q;
    InitQueue(Q);
    for(int i = 0; i < G.DNum; i++)
        visited[i] = false;
    if(!visited[b])
    {
        visited[b] = true;
        cout<<v<<" ";
        EnQueue(Q, v);
        while(!Empty(Q))
        {
            string s = DeQueue(Q, e);
            int u = LocateVex(G, s);
            p = G.vertices[u].firstB;
            while(p)
            {
                l = p->pointPosite;
                if(!visited[l])
                {
                    visited[l] = true;
                    cout<<G.vertices[l].data<<" ";
                    EnQueue(Q, G.vertices[l].data);
                }
                p = p -> nextB;
            }
        }
    }
}

main函数的设计

int main()
{
    string ch;
    TGraph G;
    CreateUDG(G);
    cout<<"建立的邻接表如下: "<<endl;
    UDGprint(G);
    cout<<"请输入遍历开始的结点:";
    cin >> ch;
    cout<<"深度遍历序列为: ";
    deepTravel(G, ch);
    cout<<"\n广度遍历序列为: ";
    breadthTravel(G, ch);
    return 0;
}

程序调试及运行结果分析

  • 运行结果(1)
  • 输入的图如下所示:
  • 运行结果(2)
  • 输入的图如下所示: