第六章学习小结

 
第六章学习小结恢复

图和树有点像,或者说是变化更多更复杂的森林也不为过。

先清晰一下概念:

图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。在图中的数据元素,我们称之为顶点(Vertex),顶点集合有穷非空。在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示,边集可以是空的。

图按照边的有无方向分为无向图和有向图。无向图由顶点和边组成,有向图由顶点和弧构成。弧有弧尾和弧头之分,带箭头一端为弧头。

图按照边或弧的多少分稀疏图和稠密图。如果图中的任意两个顶点之间都存在边叫做完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图。

图中顶点之间有邻接点、依附的概念。无向图顶点的边数叫做度。有向图顶点分为入度和出度。

图上的边或弧带有权则称为网。

图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为环,当中不重复的叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称为强连通图。图中有子图,若子图极大连通则就是连通分量,有向的则称为强连通分量。

无向图中连通且n个顶点n-1条边称为生成树。有向图中一顶点入度为0其余顶点入度为1的叫有向树。一个有向图由若干棵有向树构成生成森林。
--------------------------------------------

基于此大概还是对于复杂,变化多的非线性数据进行处理,以下是几种存储方法。

邻接矩阵的表示,其对于稀疏图的处理很浪费空间,所以一般用于稠密图:

#include<iostream>
using namespace std;


enum Graphkind{ DG, DN, UDG, UDN }; //{有向图,无向图,有向网,无向网}
typedef struct  Node
{
    int * vex;  //顶点数组
    int vexnum; //顶点个数
    int edge;   //图的边数
    int ** adjMatrix; //图的邻接矩阵
    enum Graphkind kind;
}MGraph;
void createGraph(MGraph & G,enum Graphkind kind)
{
    cout << "输入顶点的个数" << endl;
    cin >> G.vexnum;
    cout << "输入边的个数" << endl;
    cin >> G.edge;
    //输入种类

    //cout << "输入图的种类:DG:有向图 DN:无向图,UDG:有向网,UDN:无向网" << endl;
    G.kind = kind;
    //为两个数组开辟空间
    G.vex = new int[G.vexnum];
    G.adjMatrix = new int*[G.vexnum];
    cout << G.vexnum << endl;
    int i;
    for (i = 0; i < G.vexnum; i++)
    {
        G.adjMatrix[i] = new int[G.vexnum];
    }
    for (i = 0; i < G.vexnum; i++)
    {
        for (int k = 0; k < G.vexnum; k++)
        {
            if (G.kind == DG || G.kind == DN)
            {
                G.adjMatrix[i][k] = 0;
            }
            else {
                G.adjMatrix[i][k] = INT_MAX;
            }
        }

    }



    /*//输入每个元素的信息,这个信息,现在还不需要使用
    for (i = 0; i < G.vexnum; i++)
    {
        cin >> G.vex[i];
    }*/
    cout << "请输入两个有关系的顶点的序号:例如:1 2 代表1号顶点指向2号顶点" << endl;
    for (i = 0; i < G.edge; i++)
    {
        int a, b;
        cin >> a;
        cin >> b;
        if (G.kind == DN) {
            G.adjMatrix[b - 1][a - 1] = 1;
            G.adjMatrix[a - 1][b - 1] = 1;
        }
        else if (G.kind == DG)
        {
            G.adjMatrix[a - 1][b - 1] = 1;
        }
        else if (G.kind == UDG)
        {
            int weight;
            cout << "输入该边的权重:" << endl;
            cin >> weight;
            G.adjMatrix[a - 1][b - 1] = weight;
        }
        else {
            int weight;
            cout << "输入该边的权重:" << endl;
            cin >> weight;
            G.adjMatrix[b - 1][a - 1] = weight;
            G.adjMatrix[a - 1][b - 1] = weight;
        }   
    }
}


void print(MGraph g)
{
    int i, j;
    for (i = 0; i < g.vexnum; i++)
    {
        for (j = 0; j < g.vexnum; j++)
        {
            if (g.adjMatrix[i][j] == INT_MAX)
                cout << "∞" << " ";
            else
            cout << g.adjMatrix[i][j] << " ";
        }
        cout << endl;
    }
}

void clear(MGraph G)
{
    delete G.vex;
    G.vex = NULL;
    for (int i = 0; i < G.vexnum; i++)
    {
        delete G.adjMatrix[i];
        G.adjMatrix[i] = NULL;
    }
    delete G.adjMatrix;
}
int main()
{

        MGraph G;
        cout << "有向图例子:" << endl;
        createGraph(G, DG);
        print(G);
        clear(G);
        cout << endl;
        cout << "无向图例子:" << endl;
        createGraph(G, DN);
        print(G);
        clear(G);

        cout << endl;
        cout << "有向图网例子:" << endl;
        createGraph(G, UDG);
        print(G);
        clear(G);

        cout << endl;
        cout << "无向图网例子:" << endl;
        createGraph(G, UDN);
        print(G);
        clear(G);

        cout << endl;
    return 0;
}
View Code

邻接表代码的实现,主要用于稀疏图

#include<iostream>
#include<string>
using namespace std;

typedef string Vertextype;
//表结点结构
struct ArcNode {
    int adjvex;   //某条边指向的那个顶点的位置(一般是数组的下标)。
    ArcNode * nextarc; //指向下一个表结点
    int weight;   //这个只有网图才需要使用。
};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode * firstarc; //指向第一条依附在该顶点边的信息(表结点)
};

//
struct Graph
{
    int kind;  //图的种类(有向图:0,无向图:1,有向网:2,无向网:3)
    int vexnum; //图的顶点数
    int edge;  //图的边数
    Vnode * node; //图的(顶点)头结点数组
};

void createGraph(Graph & g,int kind)
{
    cout << "请输入顶点的个数:" << endl;
    cin >> g.vexnum;
    cout << "请输入边的个数(无向图/网要乘2):" << endl;
    cin >> g.edge;
    g.kind = kind; //决定图的种类
    g.node = new Vnode[g.vexnum];
    int i;
    cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
    for (i = 0; i < g.vexnum; i++)
    {
        cin >> g.node[i].data;
        g.node[i].firstarc=NULL;

    }

    cout << "请输入每条边的起点和终点的编号:" << endl;
    for (i = 0; i < g.edge; i++)
    {
        int a;
        int b;
        cin >> a; //起点
        cin >> b; //终点
        ArcNode * next=new ArcNode;
        next->adjvex = b - 1;
        if(kind==0 || kind==1)
        next->weight = -1;
        else {//如果是网图,还需要权重
            cout << "输入该边的权重:" << endl;
            cin >> next->weight;
        }
        next->nextarc = NULL;

        //将边串联起来
        if (g.node[a - 1].firstarc == NULL) {
            g.node[a - 1].firstarc=next;

        }
        else 
        {
            ArcNode * p;
            p = g.node[a - 1].firstarc;
            while (p->nextarc)//找到该链表的最后一个表结点
            {
                p = p->nextarc;
            }
            p->nextarc = next;
        }
    }
}

void print(Graph  g)
{
    int i;
    cout << "图的邻接表为:" << endl;
    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data<<" ";
        ArcNode * now;
        now = g.node[i].firstarc;
        while (now)
        {
            cout << now->adjvex << " ";
            now = now->nextarc;
        }
        cout << endl;
    }
}

int main()
{
    Graph g;
    cout << "有向图的例子" << endl;
    createGraph(g,0);
    print(g);
    cout << endl;

    cout << "无向图的例子" << endl;
    createGraph(g, 1);
    print(g);
    cout << endl;
    return 0;
}
View Code

十字链表的建立,其可以很好的处理顶点的入度:

typedef string Vertextype;
//表结点结构
struct ArcNode {
    int tailvex;   //弧尾的下标,一般都是和对应的头结点下标相同
    int headvex;   //弧头的下标
    ArcNode * hlink; //指向下一个弧头同为headvex的表结点 ,边是箭头的那边
    ArcNode * tlink;  //指向下一个弧尾为tailvex的表结点,边不是箭头的那边
    int weight;  //只有网才会用这个变量

};

//头结点
struct Vnode
{
    Vertextype data;  //这个是记录每个顶点的信息(现在一般都不需要怎么使用)
    ArcNode *firstin; //指向第一条(入度)在该顶点的表结点
    ArcNode *firstout; //指向第一条(出度)在该顶点的表结点
};
View Code

还有邻接多重表,以下是无向图的存储结构:

#include<iostream>
#include<string>
using namespace std;

//表结点
struct ArcNode
{
    int mark; //标志位
    int ivex; //输入边信息的那个起点
    ArcNode * ilink; //依附在顶点ivex的下一条边的信息
    int jvex;   //输入边信息的那个终点
    ArcNode * jlink; //依附在顶点jvex的下一条边的信息
    int weight;
};

//头结点
struct VexNode {
    string data;   //顶点的信息,如顶点名称
    ArcNode * firstedge; //第一条依附顶点的边
};

struct Graph {
    int vexnum;   //顶点的个数
    int edge;    //边的个数
    VexNode *node; //保存顶点信息
};

void createGraph(Graph & g)
{
    cout << "请输入顶点的个数:" << endl;
    cin >> g.vexnum;
    cout << "请输入边的个数(无向图/网要乘2):" << endl;
    cin >> g.edge;
    g.node = new VexNode[g.vexnum];

    int i;
    cout << "输入每个顶点的信息:" << endl;//记录每个顶点的信息
    for (i = 0; i < g.vexnum; i++)
    {
        cin >> g.node[i].data;
        g.node[i].firstedge = NULL;
    }

    cout << "请输入每条边的起点和终点的编号:" << endl;
    for (i = 0; i < g.edge; i++)
    {
        int a, b;
        cin >> a;
        cin >> b;

        ArcNode * next = new ArcNode;
        next->mark = 0;
        next->ivex = a - 1; //首先是弧头的下标
        next->jvex = b - 1; //弧尾的下标
        next->weight = -1;
        next->ilink = NULL;
        next->jlink = NULL;

        //更新顶点表a-1的信息
        if (g.node[a - 1].firstedge == NULL)
        {
            g.node[a - 1].firstedge = next;
        }
        else {
            ArcNode * now;
            now = g.node[a - 1].firstedge;
            while (1) {
                if (now->ivex == (a - 1) && now->ilink == NULL)
                {
                    now->ilink = next;
                    break;
                }
                else if (now->ivex == (a - 1) && now->ilink != NULL) {
                    now = now->ilink;
                }
                else if (now->jvex == (a - 1) && now->jlink == NULL)
                {
                    now->jlink = next;
                    break;
                }
                else if (now->jvex == (a - 1) && now->jlink != NULL) {
                    now = now->jlink;
                }
            }
        }
        //更新顶点表b-1
        if (g.node[b - 1].firstedge == NULL)
        {
            g.node[b - 1].firstedge = next;
        }
        else {
            ArcNode * now;
            now = g.node[b - 1].firstedge;
            while (1) {
                if (now->ivex == (b - 1) && now->ilink == NULL)
                {
                    now->ilink = next;
                    break;
                }
                else if (now->ivex == (b - 1) && now->ilink != NULL) {
                    now = now->ilink;
                }
                else if (now->jvex == (b - 1) && now->jlink == NULL)
                {
                    now->jlink = next;
                    break;
                }
                else if (now->jvex == (b - 1) && now->jlink != NULL) {
                    now = now->jlink;
                }
            }
        }

    }
}

void print(Graph g)
{
    int i;
    for (i = 0; i < g.vexnum; i++)
    {
        cout << g.node[i].data << " ";
        ArcNode * now;
        now = g.node[i].firstedge;
        while (now)
        {
            cout << "ivex=" << now->ivex << " jvex=" << now->jvex << " ";
            if (now->ivex == i)
            {
                now = now->ilink;
            }
            else if (now->jvex == i)
            {
                now = now->jlink;
            }
        }
        cout << endl;
    }
}
int main()
{
    Graph g;
    createGraph(g);
    print(g);
    system("pause");
    return 0;

}
View Code

搞完之后,就牵扯到图的遍历了,分为深度搜索优先(逐渐深入)和广度搜索优先(对一层一层进行处理)

然后在应用里面学到了最小生成树,主要引入为计算多个带权值的最小路径。

普里姆算法(按点生成):

#include<stdio.h>
#include<string.h>
 
#define INF 1000000    //无穷大
#define MAXN 21        //顶点个数最大值
 
int n, m;        //顶点个数、边数
int Edge[MAXN][MAXN];    //邻接矩阵
int lowcost[MAXN];    //记录集合T1中各顶点到集合T内各顶点权值最小的边的权值
int nearvex[MAXN];    //记录集合T1中各顶点距离顶点集合T内哪个顶点最近;当nearvex[i] = -1时,表示顶点i属于集合T
 
void prim(int u0)    //从顶点u0出发执行prim算法
{
    int i, j;    //循环变量
    int sumweight = 0;    //生成树的权值
    for(i = 1; i <= n; i++)    //初始化lowcost数组和nearvex数组
    {
        lowcost[i] = Edge[u0][i];
        nearvex[i] = u0;
    }
    nearvex[u0] = -1;
    for(i = 1; i < n; i++)
    {
        int min = INF;
        int v = -1;
        //在lowcost数组中找最小值
        for(j = 1; j <=n; j++)
            if(nearvex[j]!=-1 && lowcost[j]<min) { v = j; min = lowcost[v]; }
        if(v != -1)    //表示找到权值最小的边
        {
            printf("%d %d %d\n", nearvex[v], v, lowcost[v]);
            nearvex[v] = -1;    //将选出的边的状态置为-1,表示在集合T中
            sumweight += lowcost[v];
            //由于集合T新加入了顶点v,所以要更新lowcost中顶点v与T1集合中各顶点的距离,同时将更新的顶点记录到nextvex中
            for(j = 1; j <= n; j++) if(nearvex[j]!=-1 && Edge[v][j]<lowcost[j])
            {
                lowcost[j] = Edge[v][j];
                nearvex[j] = v;
            }
        }
    }
    printf("The weight of MST is %d\n", sumweight);
}
 
int main()
{
    int i, j;
    int u, v, w;
    scanf("%d%d", &n, &m);
    memset(Edge, 0, sizeof(Edge));
    //构造邻接矩阵
    for(i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &u, &v, &w);
        Edge[u][v] = Edge[v][u] = w;    
    }
    for(i = 1; i <= n; i++)
        for(j = 1; j <=n; j++)
        {
            if(i == j) Edge[i][j] = 0;
            else if(Edge[i][j] == 0) Edge[i][j] = INF;
        }
    printf("The lines chosen are :\n");
    prim(1);    //从顶点1出发构造最小生成树
    return 0;
View Code

克鲁斯卡尔算法(按边生成):

#include <stdio.h>
#include <stdlib.h>

#define MAXEDGE 20
#define MAXVEX  20
#define INFINITY 65535

typedef struct
{
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;//顶点数,边数
}MGraph;

typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;   //对边集数组Edge结构的定义

//创建图的邻接矩阵
void CreateMGraph(MGraph *G) {
    int i, j;

    G->numEdges=11;
    G->numVertexes=7;

    for (i = 0; i < G->numVertexes; i++) {
        for ( j = 0; j < G->numVertexes; j++) {
            if (i==j)
                G->arc[i][j]=0;
            else
                G->arc[i][j] = G->arc[j][i] = INFINITY;
        }
    }
    G->arc[0][1]=7;
    G->arc[0][3]=5;
    G->arc[1][2]=8;
    G->arc[1][3]=9;
    G->arc[1][4]=7;
    G->arc[2][4]=5;
    G->arc[3][4]=15;
    G->arc[3][5]=6;
    G->arc[4][5]=8;
    G->arc[4][6]=9;
    G->arc[5][6]=11;

    for(i = 0; i < G->numVertexes; i++) {
        for(j = i; j < G->numVertexes; j++) {
            G->arc[j][i] =G->arc[i][j];
        }
    }

}

//快速排序的条件
int cmp(const void* a, const void* b) {
    return (*(Edge*)a).weight - (*(Edge*)b).weight;
}

//找到根节点
int Find(int *parent, int f) {
    while ( parent[f] > 0) {
        f = parent[f];
    }
    return f;
}

// 生成最小生成树
void MiniSpanTree_Kruskal(MGraph G) {
    int i, j, n, m;
    int k = 0;
    int parent[MAXVEX]; //用于寻找根节点的数组

    Edge edges[MAXEDGE]; //定义边集数组,edge的结构为begin,end,weight,均为整型

    // 用来构建边集数组并排序(将邻接矩阵的对角线右边的部分存入边集数组中)
    for ( i = 0; i < G.numVertexes-1; i++) {
        for (j = i + 1; j < G.numVertexes; j++) {
            if (G.arc[i][j] < INFINITY) {
                edges[k].begin = i; //编号较小的结点为首
                edges[k].end = j;   //编号较大的结点为尾
                edges[k].weight = G.arc[i][j];
                k++;
            }
        }
    }

    //为边集数组Edge排序
    qsort(edges, G.numEdges, sizeof(Edge), cmp);

    for (i = 0; i < G.numVertexes; i++)
        parent[i] = 0;

    printf("打印最小生成树:\n");
    for (i = 0; i < G.numEdges; i++) {
        n = Find(parent, edges[i].begin);//寻找边edge[i]的“首节点”所在树的树根
        m = Find(parent, edges[i].end);//寻找边edge[i]的“尾节点”所在树的树根

        //假如n与m不等,说明两个顶点不在一棵树内,因此这条边的加入不会使已经选择的边集产生回路
        if (n != m) {
            parent[n] = m;
            printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}

int main(void)
{
    MGraph G;
    CreateMGraph(&G);
    MiniSpanTree_Kruskal(G);

    return 0;
}
View Code

很完美!接下来是一道作业题

给定一个有N个顶点和E条边的无向图,请用DFS和BFS分别列出其所有的连通集。假设顶点从0到N1编号。进行搜索时,假设我们总是从编号最小的顶点出发,按编号递增的顺序访问邻接点。

输入格式:

输入第1行给出2个整数N(0)和E,分别是图的顶点数和边数。随后E行,每行给出一条边的两个端点。每行中的数字之间用1空格分隔。

输出格式:

按照"{ v1​​ v2​​ ... vk​​ }"的格式,每行输出一个连通集。先输出DFS的结果,再输出BFS的结果。

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;

int map[10][10];
bool visited[10];
int result[10];
int k;
int n, m;

//深搜算法 
void dfs(int x) {
    result[k++] = x;
    visited[x] = true;
    for (int i = 0; i < n; i++) {
        if (map[x][i] == 1 && !visited[i]) {
            dfs(i);
        }
    }
}

/* 广搜 */
void bfs(int x) {
    queue<int> q;
    q.push(x);
    visited[x] = 1;
    result[k++] = x;
    while (!q.empty()) {
        int l = q.front();
        q.pop(); 
        for (int i = 0; i < n; i++) {
            if (map[l][i] == 1 && !visited[i]) {
                visited[i] = 1;
                result[k++] = i;
                q.push(i);
            }
        }
    }
}
int main() {
    cin >> n >> m;
    memset(visited, 0, sizeof(visited));
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            map[i][j] = 0;
    while (m--) {
        int i, j;
        cin >> i >> j;
        map[i][j] = 1;
        map[j][i] = 1;
    }
    ///* 列出图深搜所有的连通集 */
    for (int i = 0; i < n; i++)
    {
        k = 0;
        if (!visited[i]) {
            dfs(i);
            cout << "{ ";
            for (int i = 0; i < k; i++)
                cout << result[i] << " ";
            cout << "}" << endl;
        }
    }
    memset(visited, 0, sizeof(visited));
    /* 列出图广搜所有的连通集 */
    for (int i = 0; i < n; i++)
    {
        k = 0;
        if (!visited[i]) {
            bfs(i);
            cout << "{ ";
            for (int i = 0; i < k; i++)
                cout << result[i] << " ";
            cout << "}" << endl;
        }
    }
}
View Code

其实就考了个深搜和广搜的处理,没什么技术含量。。。

007的还没肝完,写完了也许会在总结一下?

 

posted @ 2019-05-19 23:39  我又不乱来aa  阅读(125)  评论(1编辑  收藏  举报