数据结构复习——图

图的创建和遍历

  • 图的创建方式
    • 邻接矩阵方法
    • 邻接表方法
  • 图的遍历方式
    • DFS
    • BFS

图的应用和操作

  • 求连通分量问题
  • 最小生成树问题
    • prime算法
    • 边为主的贪心算法
  • AOV网
    • 一种活动之间具有先行性关系的图
    • 拓扑排序问题
  • AOE网
    • 一种活动的耗时关系的图
    • 关键路径问题
  • 求两点之间的最短路径

邻接矩阵创建并求解连通分量问题

注意:每展开一次深搜或者广搜,都会将一个连通的图进行完全遍历

           所以连通分量的个数简化为调用dfs或者bfs的次数(设置一个全局变量进行解决)

邻接矩阵+广搜

#include<bits/stdc++.h>
#define MAX 300
using namespace std;
int n;
int sum = 0;
int A[MAX+1][MAX+1];
int visited[MAX+1];
//优化存储方式(对称矩阵的压缩存储
int bfsGragh(int pos)
{
    sum++;
    queue<int> q;
    q.push(pos);
    visited[pos] = sum;
    while(!q.empty()){
        //只要队列中非空,开始广搜 利用队列的性质开展广搜
        //拿到队首元素
        int t = q.front();
        q.pop();
        for(int i = t + 1;i <= n;i++){
            //开始搜索
            if(A[pos][i] && !visited[i]){
                q.push(i);
                visited[i] = sum;
            }
        }
    }
}
int main()
{
    cin>>n;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= n;j++){
            cin>>A[i][j];
        }
    }
    for(int i = 1;i <= n;i++){
        if(!visited[i]){
            //如果这个节点没有访问过
            bfsGragh(i);
        }
    }
    cout<<sum;
    return 0;
}

 

邻接表+深搜

#include<bits/stdc++.h>
#define Max 100
using namespace std;
/*typedef int DataType;
struct ArcNode{
    int index;
    ArcNode *next;
};
struct VertexNode{
    DataType data;
    ArcNode *first;
};
VertexNode a[Max];*/
vector<int> v[Max];
int n;
int sum = 0;
int vis[Max];
int num[Max];
void dfsGragh(int pos)
{
    //cout<<"调用一次,pos为"<<pos<<endl;
   for(int i = 0;i <= num[pos] - 1;i++){
        if(!vis[v[pos][i]]){
            vis[v[pos][i]] = 1;
            dfsGragh(v[pos][i]);
        }
   }
}
int main()
{
    int m,temp1,temp2;
    cin>>n;
    cin>>m;
    //将数据存储
    for(int i = 1;i <= m;i++){
        cin>>temp1>>temp2;
        /*
         *如果只将数字按照大小存放一次的话,那么例题中按照写的DFS算法就会将7割裂出来变成独立的点
         *因为我无法通过10找到7)使得连通分量数目增多

        if(temp1 > temp2){
            v[temp2].push_back(temp1);
            num[temp2]++;
        }
        else{
            v[temp1].push_back(temp2);
            num[temp1]++;
        }*/
        v[temp2].push_back(temp1);
        num[temp2]++;
        v[temp1].push_back(temp2);
        num[temp1]++;
    }
    for(int i = 1;i <= n;i++){
        if(!vis[i]){
            //cout<<i<<endl;
            vis[i] = 1;
            dfsGragh(i);
            //注意先后顺序,深搜会将一条线直接搜完,若在主函数中结束一次DFS,则有一条完整的连通分量
            sum++;
        }
    }
    cout<<sum;
    return 0;
}

 最小生成树问题

两种算法:点优先和边优先

prime算法:

/*问题描述】
 已知含有n个顶点(编号从1开始)的带权连通无向图,采用邻接矩阵存储,邻接矩阵以三元组的形式给出
 只给出不包括主对角线元素在内的下三角这部分的元素,且不包括不邻接的顶点对。
 请采用Prim算法,求该连通图从1号顶点出发的最小生成树的权值之和。
【输入形式】
 第1行给出图中结点个数n和三元组的个数num,之后每行给出一个三元组,数之间用1个空格隔开。
 (注意这里顶点的序号是从1到n,而不是0到n-1,程序里要小心!)
【输出形式】
 求解的最小生成树的各条边、各边的权值、最小生成树的权值和
【样例输入】
 5 8
 2 1 7
 3 1 6
 3 2 8
 4 1 9
 4 2 4
 4 3 6
 5 2 4
 5 4 2
【样例输出】
1-3:6
3-4:6
4-5:2
4-2:4
18
【样例说明】
 权值是正整数,可能很大,但不需要考虑整型溢出问题*/
#include<bits/stdc++.h>
#define MAX 30
#define intMAX 2147483647
using namespace std;
int n;
int g[MAX][MAX];
//已有点集到目的点的最少消费 
struct {
    int lowcost;
    int dir;
}closedge[MAX + 1];
int vis[MAX];
int MinRoad()
{
    int result;
    int max = intMAX;
    for(int i = 1;i <= n;i++){
    	//lowcost要么为0(已经纳入点集),要么为最大值(没有路),要么为具体数值(也是MinRoad的主要遍历对象)
		//该If条件可以筛选掉前两个 
        if(closedge[i].lowcost < max && closedge[i].lowcost){
            max = closedge[i].lowcost;
            result = i;
        }
    }
    return result;
}
//点集合的形式 
void prime(int pos)
{
    int maxNum = intMAX;
    int sum = 0;
    int v;
    //vis[pos] = 1;
    closedge[pos].lowcost = 0;
    for(int i = 1;i <= n;i++){
        if(i != pos){
            closedge[i].dir = pos;
            if(g[pos][i]){
                closedge[i].lowcost = g[pos][i];
            }
            else{
                closedge[i].lowcost = intMAX;
            }
        }
    }
    //最小生成树若有N个顶点,则有N-1条边
    for(int h = 1;h <= n - 1;h++){
        //找到本趟中路权重最小的那个
        v = MinRoad();
        cout<<closedge[v].dir<<"-"<<v<<":"<<closedge[v].lowcost<<endl;
        sum += closedge[v].lowcost;
        //将找到的最小点纳入体系
        closedge[v].lowcost = 0;
        for(int i = 1;i <= n;i++){
        	//遍历所有的点,看lowcost是否发生变化 
            if(g[i][v] && (g[i][v] < closedge[i].lowcost)){
                closedge[i].dir = v;
                //cout<<"发生一次变化,此时目标点由  "<<pos<<"变为 "<<v<<endl;
                closedge[i].lowcost = g[i][v];
            }
        }
    }
    cout<<sum;
}
int main()
{
    int t1,t2,t3,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= n;j++){
            g[i][j] = 0;
        }
    }
    for(int i = 1;i <= m;i++){
        cin>>t1>>t2>>t3;
        g[t1][t2] = t3;
        g[t2][t1] = t3;
    }
    prime(1);
    return 0;
}

Kruskal算法(边优先):自己离散数学的做法

  1. 将各边的权重进行排序
  2. 从小到大选取,只要不构成回路,及边的两个端点属于两个不同的连通分量
    /* kruskal算法思想
     *    在kruskal算法中,相比与prim算法,采取的边优先的原则,所以在本种算法种需要记录边的信息
     *    - 将边进行排序(其中存储边信息时应该保留边的端点信息,终点信息,和边的权重
     *    - 难点:本算法的难点在于如何不形成回路
     *            注意:最小生成树中的每条边都是桥,即切断后连接两个不同的连通分量
     *            解决:设置一个辅助数组,对端点进行标记,若端点之间连通,则标记相同,在按照大小选边的时候增设一个if判断
     *                   来避免出现回路
    */
    #include<bits/stdc++.h>
    #define MAX 100
    using namespace std;
    //边节点用于保留端点信息和权重信息
    struct Edge{
        char ori;
        char end;
        int weight;
    }edges[MAX + 1],minTree[MAX + 1];
    struct Node{
        int mark;
        bool in;
    };
    int n;
    //根据输入特征构建的创建函数
    int createGragh()
    {
        int count = 0;
        char orr,end;
        int m,x;
        for(int i = 1;i <= n - 1;i++){
            cin>>orr>>m;
            //保存起点信息到数组中
            for(int j = 1;j <= m;j++){
                count++;
                edges[count].ori = orr;
                cin>>end>>x;
                edges[count].end = end;
                edges[count].weight = x;
            }
        }
        /* 测试输出
        for(int i = 1;i <= count;i++){
            cout<<edges[i].ori<<"-"<<edges[i].end<<":"<<edges[i].weight<<endl;
        }*/
        return count;
    }
    
    void sortEdges(int count)
    {
        //对边按照权重进行排序(本题采用冒泡排序)
        Edge temp;
        bool flag = true;
        //对冒牌排序进行复习
        for(int i = 1;i <= count - 1 && flag;i++){
            //本次循环控制趟数(走N - 1趟)
            flag = false;
            for(int j = 1;j <= count - i;j++){
                //1.保证不会下标越界 2.外层循环每走一次,多一个已经排序好的量
                if(edges[j].weight > edges[j+1].weight){
                    temp = edges[j];
                    edges[j] = edges[j+1];
                    edges[j+1] = temp;
                    flag = true;
                }
            }
        }
    }
    
    //核心算法函数生成最小生成树
    void kruskal(int count)
    {
        int num = 0,sumWeight = 0;
        Node flag[n + 1];
        //初始化标记数组,给每个顶点做不同的标记
        for(int i = 1;i <= n;i++){
            flag[i].mark = i;
            flag[i].in = false;
        }
        for(int i = 1;i <= count;i++){
            if(flag[edges[i].ori - 64].mark != flag[edges[i].end - 64].mark){
                //如果两个顶点在不同的连通分量
                num ++;
                //保存边的信息
                minTree[num] = edges[i];
                sumWeight += edges[i].weight;
                
                //修改端点的mark值 
                if(flag[edges[i].end - 64].in == true){
                    //和源头节点在同一个连通分支+取了头节点的值作为Mark的标记,保证其全部修改为尾节点的Mark值 
                    int ori = flag[edges[i].ori - 64].mark;
                    for(int j = 1;j <= n;j++){
                        if(flag[j].mark == ori){
                            flag[j].mark = flag[edges[i].end - 64].mark;
                        }
                    }
                    //flag[edges[i].ori - 64] = flag[edges[i].end - 64];
                }
                else{
                	//反过来 
                    int end = flag[edges[i].end - 64].mark;
                    for(int j = 1;j <= n;j++){
                        if(flag[j].mark == end){
                            flag[j].mark = flag[edges[i].ori - 64].mark;
                        }
                    }
                }
                
                //修改端点的Bool信息 
                flag[edges[i].ori - 64].in = true;
                flag[edges[i].end - 64].in = true;
            }
            
            //最小生成树有N - 1条边 
            if(num == n-1)
                break;
        }
        cout<<sumWeight<<endl;
    }
    int main()
    {
    	int edgeNum;
        cin>>n;
        edgeNum = createGragh();
        sortEdges(edgeNum);
        kruskal(edgeNum);
        return 0;
    }

 拓扑排序和关键路径问题

拓扑排序

  /*4

0(空格)1(空格)2(空格)3

0(空格)1

1(空格)2

3(空格)2

-1(空格)-1

【样例输出】

3(空格)0(空格)1(空格)2(空格)*/
#include<bits/stdc++.h>
#define MAX 100
using namespace std;
typedef int DataType;
struct ArcNode{
    int index;
    ArcNode *next;
};
struct VertexNode{
    DataType data;
    ArcNode *first;
};
VertexNode G[MAX];
int indegree[MAX];
int n;
DataType result[MAX];
void FindID()
{
    ArcNode *p;
    for(int i = 0;i < n;i++){
        indegree[i] = 0;
    }
    for(int i = 0;i < n;i++){
        p = G[i].first;
        while(p != NULL){
            //有后驱节点
            indegree[p->index]++;
            p = p->next;
        }
    }
}
void TopoSort()
{
    stack<int> s;
    int count = 0,j;
    ArcNode *p;
    FindID();
    for(int i = 0;i < n;i++){
        //找到初始状态下无前驱的节点
        if(indegree[i] == 0){
            s.push(i);
        }
    }
    while(!s.empty()){
        //只要栈不为空
        j = s.top(); s.pop();
        result[count] = G[j].data;
        //cout<<G[j].data<<" ";
        count++;
        p = G[j].first;
        while(p != NULL){
            indegree[p->index]--;
            if(indegree[p->index] == 0){
                s.push(p->index);
            }
            p = p->next;
        }
    }
    if(count < n)
        cout<<"ERROR";
    else{
        for(int i = 0;i < n;i++){
            cout<<result[i]<<" ";
        }
    }
}
int main()
{
    int temp1,temp2;
    ArcNode *p;
    cin>>n;
    for(int i = 0;i < n;i++){
        //放入节点数据值,并初始化指针为空
        DataType data;
        cin>>data;
        G[i].data = data;
        G[i].first = NULL;
    }
    cin>>temp1>>temp2;
    while(temp1 != -1 && temp2 != -1){
        p = new ArcNode;
        p->index = temp2;
        p->next = G[temp1].first;
        G[temp1].first = p;
        cin>>temp1>>temp2;
    }
    TopoSort();
    return 0;
}

关键路径

/* 关键:
 *    求解两个数组
 * 输入格式:
 *6 8
  0 1 3
  0 2 2
  1 3 2
  1 4 3
  2 3 4
  2 5 3
  3 5 2
  4 5 1
*/
#include<bits/stdc++.h>
#define MAX 500
using namespace std;
struct ArcNode{
    int weight;
    int index;
    ArcNode *next;
};
struct VertexNode{
    int data;
    ArcNode *first;
};
VertexNode g[MAX + 1];
int indegree[MAX+1];
void createGragh(int n,int m)
{
    ArcNode *p,*q;
    int x,y,w;
    for(int i = 0;i < n;i++){
        g[i].data = i;
        g[i].first = NULL;
    }
    for(int i = 0;i < m;i++){
        cin>>x>>y>>w;
        p = new ArcNode;
        p->weight = w;
        p->index = y;
        p->next = NULL;
        q = g[x].first;
        if(g[x].first == NULL){
            g[x].first = p;
        }
        else{
            while(q->next != NULL)
                q = q->next;
            q->next = p;
        }
    }
}
void FindID(int n)
{
    ArcNode *p;
    for(int i = 0;i < n;i++){
        p = g[i].first;
        while(p != NULL){
            //有后驱节点
            indegree[p->index]++;
            p = p->next;
        }
    }

    /*for(int i = 0;i < n;i++){
        cout<<indegree[i]<<" "<<endl;
    }*/
}
int ve[MAX];
int vl[MAX];
void TopoOrder(int n)
{
    FindID(n);
    stack<int> s; stack<int> t;
    int j;
    ArcNode *p;
    for(int i = 0;i < n;i++){
        ve[i] = 0;
    }
    for(int i = 0;i < n;i++){
        if(indegree[i] == 0){
            s.push(i);
        }
    }
    while(!s.empty()){
        j = s.top(); s.pop();
        t.push(j);
        //count++;
        p = g[j].first;
        while(p != NULL){
            if(--indegree[p->index] == 0){
                s.push(p->index);
            }
            if(ve[j] + p->weight > ve[p->index]){
                ve[p->index] = ve[j] + p->weight;
            }
            p = p->next;
        }
    }

    //关键路径算法
    for(int i = 0;i < n;i++){
        vl[i] = ve[n - 1];
    }
    while(!t.empty()){
        j = t.top(); t.pop();
        p = g[j].first;
        while(p != NULL){
            if((vl[p->index] - p->weight) < vl[j]){
                vl[j] = vl[p->index] - (p->weight);
            }
            p = p->next;
        }
    }
    cout<<ve[n-1]<<endl;
    j = 0;
    for(int i = 1;i < n;i++){
        //j用来记录上一个关键活动
        if(ve[i] == vl[i] ){
            //找到一个关键活动
            p = g[j].first;
            while(p->index != i)
                p = p->next;
            cout<<j<<" "<<i<<endl;
            j = i;
        }
    }


}
int main()
{
    int n,m;
    cin>>n>>m;
    createGragh(n,m);
    TopoOrder(n);
    return 0;
}

 最短路径问题

弗洛伊德(点优先法)

#include<bits/stdc++.h>
#define MAX 50
#define intMAX 9999
using namespace std;
int g[MAX][MAX];
struct {
    int lowcost;
    int dir;
}closedge[MAX + 1];
//找出每趟中路径最短的节点值
int Lowcost(int n)
{   int max = intMAX;
    int result;
    for(int i = 0;i < n;i++){
        if(closedge[i].lowcost < max && closedge[i].lowcost){
            max = closedge[i].lowcost;
            result = i;
        }
    }
    return result;
}
void MinRoad(int n,int pos)
{
    int min[MAX];
    int v;
    //path[i]存放i的路径点
    vector<int> path[n + 1];
    //初始化数组
    for(int i = 0;i < n;i++){
        closedge[i].lowcost = 0;
        min[i] = 0;
        path[i].push_back(pos);
    }
    for(int i = 0;i < n;i++){
        if(i != pos){
            closedge[i].dir = pos;
            closedge[i].lowcost = g[pos][i];
        }
    }
    //每寻找一次一个节点纳入,共走N-1次
    for(int h = 1;h <= n-1;h++){
        v = Lowcost(n);
        min[v] = closedge[v].lowcost;
        //找到的最小点纳入原点集合
        closedge[v].lowcost = 0;
        for(int i = 0;i < n;i++){
            if(g[v][i] && ((g[v][i] + min[v]) < closedge[i].lowcost)){
                closedge[i].lowcost = g[v][i] + min[v];
                closedge[i].dir = v;
                //cout<<"进行一次vector入容器,i的值和v的值分别为"<<i<<"   "<<v<<endl;

                //注意:要把前一个节点的路要复刻过来,所以要走一个判断和循环
                if(path[v].size() > 1){
                    for(int k = 1;k < path[v].size();k++){
                        int j = path[v][k];
                        path[i].push_back(j);
                    }
                }
                path[i].push_back(v);
            }
        }
    }
    for(int i = 0;i < n;i++)
        if(i != pos)
            path[i].push_back(i);
    for(int i=0;i < n;i++){
        cout<<"from "<<pos<<" to "<<i<<":dist = "<<min[i]<<" path:";
        /*if(pos == 2 && i==1){
            cout<<2<<" "<<3<<" "<<1<<endl;
        }
        else if(pos == 3&& i==0){
            cout<<3<<" "<<1<<" "<<2<<" "<<0<<endl;
        }*/
        for(int j = 0;j < path[i].size();j++){
            cout<<path[i][j]<<" ";
        }
            cout<<endl;
    }
    //cout<<path[0][1];
    //cout<<endl<<path[0].size();
}
int main()
{
    int n,ori,w;
    cin>>n;
    for(int i = 0;i < n;i++){
        for(int j = 0;j < n;j++){
            cin>>w;

            g[i][j] = w;
        }
    }
    for(int i = 0;i < n;i++)
        MinRoad(n,i);
    return 0;
}

迪杰斯特拉最短路径算法(边优先法)

#include<bits/stdc++.h>
#define MAX 50
#define intMAX 2147483647
//输入格式 顶点数N,原点ori; 邻接矩阵(为0即无边,不为0则为权重值)
using namespace std;
int g[MAX][MAX];
struct {
    int lowcost;
    int dir;
}closedge[MAX + 1];
//找出每趟中路径最短的节点值
int Lowcost(int n)
{
    int result;
    int max = intMAX;
    for(int i = 1;i <= n;i++){
        if(closedge[i].lowcost < max && closedge[i].lowcost){
            max = closedge[i].lowcost;
            result = i;
        }
    }
    return result;
}
void MinRoad(int n,int pos)
{
    int min[MAX];
    bool vis[MAX];
    int v;
    //初始化最短路径数组
    for(int i = 1;i <= n;i++){
        min[i] = 0;
        vis[i] = false;
    }
    //初始化closedge数组
    closedge[pos].lowcost = 0;
    for(int i = 1;i <= n;i++){
        if(i != pos){
            closedge[i].dir = pos;
            //如果存在路径
            if(g[pos][i]){
                closedge[i].lowcost = g[pos][i];
            }
            //不存在路径取无穷
            else{
                closedge[i].lowcost = intMAX;
            }
        }
    }
    //每寻找一次一个节点纳入,共走N-1次
    for(int h = 1;h <= n-1;h++){
        v = Lowcost(n);
        min[v] = closedge[v].lowcost;
        vis[v] = true;
        //找到的最小点纳入原点集合
        closedge[v].lowcost = 0;
        for(int i = 1;i <= n;i++){
            if(g[v][i] && ((g[v][i] + min[v]) < closedge[i].lowcost)){
                closedge[i].lowcost = g[v][i] + min[v];
                closedge[i].dir = v;
            }
        }
    }
    for(int i = 1;i <= n;i++){
        if(i != pos){
            if(vis[i] == true)
                cout<<min[i]<<" ";
            else
                cout<<-1<<" ";
        }
    }
    cout<<endl;
}
int main()
{
    int n,ori,w;
    cin>>n>>ori;
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= n;j++){
            cin>>w;
            g[i][j] = w;
        }
    }
    MinRoad(n,ori+1);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值