来交作业了。。。
这几天都没有更东西,一边是有作业难,一边是编译器经常崩溃(就不能是懒吗?) 有一说一,之前真没有怎么研究过类问题(就是数据结构没学好)

图的环的问题需要分情况,分有向图与无向图。

### 有向图

1.将所有节点涂白,表示节点未被访问;
2.从其中某一节点开始深度优先遍历,将访问过的节点涂灰;
3.如果其后裔节点均被访问过,则将节点涂黑,表示不再访问;
4.若某个已被涂灰的节点又被访问到,则有环的产生,并计数;
5.进行特判,自己指向自己不能算作环。

核心算法:

dfs_circle(graph g, int i)
{
    count=0;
    color[i]==Gray;
    for (j=1; j<=g->vNum; j++)
    {
        if(exist(Edge))             //边存在
        {
        if (color[i]==Gray)         //该节点被已涂灰
            if(vi!=vj)              //非自己指向自己的边
                count++;            //环数加一
        else if (color[i]==White)   
            dfs_circle(g,j);        //未被访问,继续dfs
        }
    }
    color[i]=Black;                 //后裔节点被访问,涂黑
}

实现代码:

//有向图 —— 环

#include <iostream>

#define MAXNUM 100
#define White 0
#define Gray -1
#define Black 1

using namespace std;

int color[MAXNUM];
int pre[MAXNUM];
int sum_circle = 0;
typedef struct graph
{
    int e[MAXNUM][MAXNUM];
    int vNum;
    int eNum;
}graph;                                      //用邻接矩阵存储图

void Create_Graph(graph* g)                  //创建图
{
    cout << "输入顶点个数:" << endl;
    cin >> g->vNum;
    cout << "输入边的个数:" << endl;
    cin >> g->eNum;
    int i, j, k;
    for (i = 1; i <= g->vNum; i++)
        for (j = 1; j <= g->vNum; j++)
            g->e[i][j] == 0;
    cout << "输入边的起点与终点:" << endl;
    for (k = 1; k <= g->eNum; k++)
    {
        cin >> i >> j;
        g->e[i][j] = 1;
    }
}

void dfs_circle(graph* g, int i)
{
    color[i] = Gray;      //该节点被访问
    for (int j = 1; j <= g->vNum; j++)
    {
        if (g->e[i][j] != 0)
        {
            if (color[j] == Gray)               //有环
            {
                if (i != j) sum_circle++;   //以防独自成环即a->a,环数加一 
            }
            else if (color[j] == White)
                dfs_circle(g, j);
        }
    }
    color[i] = Black;                      //子节点已访问完,该节点涂黑,不再访问
}

void DFS(graph* g)
{
    int i;
    for (i = 1; i <= g->vNum; i++)
    {
        color[i] = White;               //将所有节点设置为未被访问
    }
    for (i = 1; i <= g->vNum; i++)
    {
        if (color[i] == White)          //若节点未被访问,从该节点开始dfs_circle
            dfs_circle(g, i);
    }
}

int main()
{
    graph* g;
    g = new graph();
    Create_Graph(g);
    DFS(g);

    if (sum_circle==0) cout << "无环,是有向无关图" << endl;
    else 
    {
        cout << "有环" << endl;
        cout << "环的个数为:" << sum_circle << endl;
    }
    return 0;
}

### 无向图

无向图这里要介绍一种比较有意思的图的存储结构 —— 链式前向星

typedef struct Node
{
    int to, next;     //to表示该边指向的终点, next表示与这条边起点相同的上一条边的编号
}Edge;               //边集

Edge e[MAXNUM];
int head[MAXNUM];
void add_edge(int start, int end)
{
    e[cnt].to = end;               //该条边指向的终点
    e[cnt].next = head[start];     //以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[start] = cnt++;           //更新以u为起点的边的编号
}

先来说以下什么是前向星:

前向星是一种特殊的边集数组,我们把边集数组中的每一条边按照起点从小到大排序,如果起点相同就按照终点从小到大排序,并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,那么前向星就构造好了.

链式前向星则在建立的时候就将顺序排好,将最大的边(起点相同,终点最大)的边放在第一个,依次降序排列。遍历时就进行逆向遍历。


1.从某一节点开始dfs,将访问过的节点涂灰

2.逆向遍历,并记录边的终点
3.若节点是白色,则继续dfs
4.若节点是灰色的,则开始找环

核心算法:

dfs_num_of_circle(int start, int find)
{
    int sum=0;
    visit[start] = Gray;                   //从start节点开始遍历,将start节点涂灰
    for (int i = head[start]; i; i = e[i].next)    //逆向遍历
    {
        int flag = e[i].to;               //flag记录边i所指向的终点
        if (flag == find) continue;      //无向图,a->b与b->a相同
        if (visit[flag] == White )        //若没有访问过就标记前一个元素
        {
            dfs_num_of_circle(flag, start);
        }
        else if (visit[flag] == Gray)       //若已经被访问过,开始找环及路径
            sum++;                          //环的个数++
    }
    visit[start] = Black;     //后裔节点遍历完,涂黑,不再访问
}

实现代码:

//无向图 —— 环

#include <iostream>

#define MAXNUM 100
#define White 0
#define Gray 1
#define Black 2

using namespace std;
int head[MAXNUM];
int cnt = 0, sum = 0;
int visit[MAXNUM];
int pre[MAXNUM];
typedef struct Node
{
    int to, next;     //to表示该边指向的终点, next表示与这条边起点相同的上一条边的编号
}Edge;               //边集

Edge e[MAXNUM];

void add_edge(int start, int end)
{
    e[cnt].to = end;               //该条边指向的终点
    e[cnt].next = head[start];     //以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[start] = cnt++;           //更新以u为起点的边的编号
}

void dfs_num_of_circle(int start, int find)
{
    visit[start] = Gray;                   //从start节点开始遍历,将start节点涂灰
    for (int i = head[start]; i; i = e[i].next)
    {
        int flag = e[i].to;               //flag记录边i所指向的终点
        if (flag == find) continue;      //无向图,则a->b与b->a相同
        if (visit[flag] == White )        //若没有访问过就标记前一个元素
        {
            pre[flag] = start;
            dfs_num_of_circle(flag, start);
        }
        else if (visit[flag] == Gray)       //若已经被访问过,开始找环及路径
        {
            cout << "输出环的路径:" << endl;
            int temp = start;

            while (temp != flag)
            {
                cout << temp << " ";
                temp = pre[temp];
            }
            cout << flag << endl;
            sum++;                    //环的个数++
        }
    }
    visit[start] = Black;     //涂黑,不再访问
}



int main()
{
    int n, m, start, end;
    cout << "输入点的个数:" << endl;
    cin >> n;
    cout << "输入边的个数:" << endl;
    cin >> m;

    for (int i = 1; i <= m; i++)            //初始化边
    {
        cout << "输入第" << i << "条边的起点: ";
        cin >> start;
        cout << "输入第" << i << "条边的终点: ";
        cin >> end;
        add_edge(start, end);
        add_edge(end, start);
    }

    for (int i = 1; i <= n; i++)
    {
        if (visit[i] == White)
            dfs_num_of_circle(i, -1);
    }
    if (sum == 0) cout << "无环!" << endl;
    else if (sum > 0)
        cout << "有环" << endl << "环的个数为:" << sum << endl;

    return 0;
}

分类: 算法集

0 条评论

发表评论

Avatar placeholder

邮箱地址不会被公开。 必填项已用*标注