王道P221,综合应用题第二题

判断图是否是一棵树,需要两个条件

  • G是连通图
  • G的边数比顶点数少1,edge = vertex - 1

算法思路,对第一个顶点调用一次DFS,如果能一次访问完所有的结点,那么说明此图连通,在DFS的过程中,访问当前节点的下一个节点时候,说明有一条边,维护边数的Edgeum变量自增。最后 如果访问过的顶点数和实际的顶点数相同说明联通,同时如果边数等于节点数-1说明该就是树了。

这里有一个小细节要注意,无向图的邻接表中,结点a和b之间的边其实存放了两次,一次是a -> b,另一次是b -> a。比如可以看看综合应用题的第一题1到2有一条边,2到1也有一条边,因此最后总的边数需要除以2才是真正的边的数目。

bool visited[MaxVertexNum];
int EdgeNum = 0, VexNum = 0;

//DFS,可以返回一次遍历返回的边的数目和结点的数目
void DFS(ALGraph G, int i) {
    visited[i] = true;
    VexNum ++;

    ArcNode * p = G.vertices[i].first;
    while(p) {    
        EdgeNum ++;    //访问当前节点的下一个结点时,边数+1
        if(!visited[p -> adjvex]) {                      
            DFS(G, p -> adjvex);
        }
        p = p -> next;
    }
}

bool IsTree(ALGraph G) {    
    for(int i = 0; i < G.vertexnum; ++i) {
        visited[i] = false;
    }
    DFS(G, 0);
    if(VexNum == G.vertexnum && EdgeNum / 2 ==  (G.vertexnum - 1)) //如果连通并且边数 = 节点数 - 1,则说明是树
        return true;
    return false;
}

完整代码:

#include<iostream>
using namespace std;

#define MaxVertexNum 100
#define VertexType char

//边表结点
typedef struct ArcNode { 
    int adjvex;          //存储该结点在顶点表中的下标
    ArcNode * next;      //指向下一个边表
}ArcNode;

//顶点表结点
typedef struct VNode {  
    VertexType data;            //顶点信息
    ArcNode * first;             //指向边表
} VNode, AdjList[MaxVertexNum]; //顶点和顶点表

//图的存储结构
typedef struct {       
    AdjList vertices;  //邻接表
    int vertexnum, arcnum; //维护图的顶点数和边的个数
} ALGraph;


//找到结点x在顶点表中的位置
int LocateVex(ALGraph & G, VertexType x) {
    for(int i = 0; i < G.vertexnum; i ++) {
        if(x == G.vertices[i].data) 
            return i;
    }
    return -1;
}

void CreateALGrapha(ALGraph & G) {
    //cout << "输入顶点数和边数:";
    cin >> G.vertexnum >> G.arcnum;
    for(int i = 0; i < G.vertexnum; ++i) {
        //cout << "输入第" << i + 1 << "个顶点的信息:";
        cin >> G.vertices[i].data;
        G.vertices[i].first = NULL;
    }

    for(int i = 0; i < G.arcnum; ++i) {
        VertexType e1, e2;
        //cout << "输入第" << i + 1 << "条边的顶点:";
        cin >> e1 >> e2;
        ArcNode * e = new ArcNode;
        if(!e) {
            cout << "内存分配失败:" << endl;
            exit(0);
        }
        
        int vi = LocateVex(G, e1);
        int vj = LocateVex(G, e2);

        //e1和e2之间有一条边,先用头插法将e2的边表插到e1的顶点表后
        e -> adjvex = vj;
        e -> next = G.vertices[vi].first;
        G.vertices[vi].first = e;
		
        e = new ArcNode;
        if(!e) {
            cout << "内存分配失败:" << endl;
            exit(0);
        }        
        //再将e1的边表插到e2的顶点表
        e -> adjvex = vi;
        e -> next = G.vertices[vj].first;
        G.vertices[vj].first = e;
    }
}

bool visited[MaxVertexNum];
int EdgeNum = 0, VexNum = 0;

//DFS,可以返回一次遍历返回的边的数目和结点的数目
void DFS(ALGraph G, int i) {
    visited[i] = true;
    VexNum ++;

    ArcNode * p = G.vertices[i].first;
    while(p) {    
        EdgeNum ++;    
        if(!visited[p -> adjvex]) {                      
            DFS(G, p -> adjvex);
        }
        p = p -> next;
    }
}

bool IsTree(ALGraph G) {    
    for(int i = 0; i < G.vertexnum; ++i) {
        visited[i] = false;
    }
    DFS(G, 0);
    if(VexNum == G.vertexnum && EdgeNum / 2 ==  (G.vertexnum - 1)) //一条边会被两次访问,除2
        return true;
    return false;
}

int main() {
    ALGraph G;
    CreateALGrapha(G);
    if(IsTree(G)) {
        cout << "树" << endl;
    } else {
        cout << "图" << endl;
    }
    return 0;
}

/*
图的输入样例如下
5 7
a b c d e
a b
a e
b c
b d
b e
c d
d e
树的输入样例如下
5 4
a b c d e
a b
a c
a d
b e
*/

立志成为一名攻城狮