zoukankan      html  css  js  c++  java
  • DS博客作业04--图

    这个作业属于哪个班级 数据结构--网络2011/2012
    这个作业的地址 DS博客作业04--图
    这个作业的目标 学习图结构设计及相关算法
    姓名 陈佳桐

    0.PTA得分截图

    1.本周学习总结(6分)

    1.1 图的存储结构

    1.1.1 邻接矩阵

    邻接矩阵的结构体定义

    typedef struct              
    {  int edges[MAXV][MAXV];     //邻接矩阵
       int n,e;              //顶点数,弧数
    } MGraph;                //图的邻接矩阵表示类型
    

    建图函数

    void CreateMGraph(MGraph &g, int n, int e)//建图 
    {
    	//n顶点,e弧数
    	g.n = n;
    	g.e = e;
    	int i, j;
    	int a, b;//下标
    	for (i = 1; i <= n; i++)//先进行初始化
    	{
    		for (j = 1; j <= n; j++)
    		{
    			g.edges[i][j] = 0;
    		}
    	}
    	for (i = 1; i <= e; i++)//无向图
    	{
    		cin >> a >> b;
    		g.edges[a][b] = 1;
    		g.edges[b][a] = 1;
    	}
    }
    
    void CreateMGraph(MGraph& g, int n, int e)//建图 
    {
        int i, j;
        int a, b;
        g.n = n; g.e = e;    //
        //建邻接矩阵
        for (i = 0; i <= n; i++)
        {
            for (j = 0; j <= n; j++)
                g.edges[i][j] = 0;    //邻接矩阵初始化
        }
        for (i = 0; i < e; i++)    //构建邻接矩阵
        {
            cin >> a >> b;
            g.edges[a][b] = 1;
            g.edges[b][a] = 1;
        }
    }
    

    1.1.2 邻接表

    邻接矩阵的结构体定义

    typedef struct ANode
    {  int adjvex;            //该边的终点编号
       struct ANode *nextarc;    //指向下一条边的指针
       int info;    //该边的相关信息,如权重
    } ArcNode;                //边表节点类型
    
    typedef struct Vnode
    {  Vertex data;            //顶点信息
       ArcNode *firstarc;        //指向第一条边
    } VNode;                //邻接表头节点类型
    
    typedef struct 
    {  AdjList adjlist;        //邻接表
       int n,e;        //图中顶点数n和边数e
    } AdjGraph;    //邻接表类型
    

    建图函数

     void CreateAdj(AdjGraph*& G, int n, int e) //创建图邻接表
     {
        int i, j, a, b;
        int A[MAXV][MAXV];
        ArcNode* p;
        G = (AdjGraph*)malloc(sizeof(AdjGraph));//申请动态储存
    
        for (i = 0; i <= n; i++)//邻接表头指针指针置零
        {
            G->adjlist[i].firstarc = NULL;
        }
    
        for (i = 0; i < n; i++)//邻接矩阵初始化置零
        {
            for (j = 0; j <= n; j++)
            {
                A[i][j] = 0;
            }
        }
        for (i = 0; i < e; i++)//邻接矩阵对应边置1
        {
            cin >> a >> b;
            A[a][b] = 1; A[b][a] = 1;
        }
    
        //查找邻接矩阵中的每个元素
        for (i = 1; i <= n; i++)
        {
            for (j = 1; j <= n; j++)
            {
                if (A[i][j])
                {
                    p = (ArcNode*)malloc(sizeof(ArcNode));
                    p->adjvex = j;  //存放临节点
                    p->info = A[i][j];  //放权值
                    p->nextarc = G->adjlist[i].firstarc;  //头插法插入节点
                    G->adjlist[i].firstarc = p;  //
                }
            }
        }
        G->n = n; G->e = e;
    
      }
    

    1.1.3 邻接矩阵和邻接表表示图的区别

    适用情况:

    邻接矩阵多用于稠密图的存储,而邻接表多用于稀疏图的存储。

    区别:

    ①对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
    ②邻接矩阵的空间复杂度为o(n2),而邻接表的空间复杂度为o(n+e)。

    1.2 图遍历

    1.2.1 深度优先遍历

    选上述的图,继续介绍深度优先遍历结果

    深度遍历代码:

    DFS方法首先从根节点1开始,其搜索节点顺序是1,2,3,4,5,6,7,8(假定左分枝和右分枝中优先选择左分枝)。

    (1)将起始节点1放入栈stack中,标记为已遍历。

    (2)从stack中访问栈顶的节点1,找出与节点1邻接的节点,有2,9两个节点,我们可以选择其中任何一个,选择规则可以人为设定,这里假设按照节点数字顺序由小到大选择,选中的是2,标记为已遍历,然后放入stack中。

    (3)从stack中取出栈顶的节点2,找出与节点2邻接的节点,有1,3,5三个节点,节点1已遍历过,排除;3,5中按照预定的规则选中的是3,标记为已遍历,然后放入stack中。

    (4)从stack中取出栈顶的节点3,找出与节点3邻接的节点,有2,4两个节点,节点2已遍历过,排除;选中的是节点4,标记为已遍历,然后放入stack中。

    (5)从stack中取出栈顶的节点4,找出与节点4邻接的节点,有3,5,6三个节点,节点3已遍历过,排除;选中的是节点5,标记为已遍历,然后放入stack中。

    (6)从stack中取出栈顶的节点5,找出与节点5邻接的节点,有2,4两个节点,节点2,4都已遍历过,因此节点5没有尚未遍历的邻接点,则将此点从stack中弹出。

    (7)当前stack栈顶的节点是4,找出与节点4邻接的节点,有3,5,6三个节点,节点3,5都已遍历过,排除;选中的是节点6,标记为已遍历,然后放入stack中。

    (8)当前stack栈顶的节点是6,找出与节点6邻接的节点,有4,7,8三个节点,4已遍历,按照规则选中的是7,标记为已遍历,然后放入stack中。

    (9)当前stack栈顶的节点是7,找出与节点7邻接的节点,只有节点6,已遍历过,因此没有尚未遍历的邻接点,将节点7从stack中弹出。

    (10)当前stack栈顶的节点是6,找出与节点6邻接的节点,有节点7,8,7已遍历过,因此将节点8放入stack中。

    (11)当前stack栈顶的节点是8,找出与节点8邻接的节点,有节点1,6,9,1,6已遍历过,因此将节点9放入stack中。

    (12)当前stack栈顶的节点是9,没有尚未遍历的邻接点,将节点9弹出,依次类推,栈中剩余节点8,6,4,3,2,1都没有尚未遍历的邻接点,都将弹出,最后栈为空。
    (13)DFS遍历完成。

    //邻接矩阵代码
    void DFS(MGraph g, int v)//深度遍历 
    {
    	visited[v] = 1;//建立visited数组存储已访问的结点信息
    	if (flag == 0)//控制空格输出
    	{
    		cout << v;
    		flag = 1;
    	}
    	else
    	{
    		cout << " " << v;
    	}
    	for (int i = 1; i <= g.n; i++)
    	{
    		if (g.edges[v][i] == 1 && visited[i] == 0)//未访问且两点之间连通
    		{
    			DFS(g, i);
    		}
    	}
    }
    
    //邻接表代码
    void DFS(AdjGraph* G, int v)     //v节点开始深度遍历
    {
        ArcNode* p;
        visited[v] = 1;              //访问完赋予1值
    
        if (!flag)
        {
            cout << v;
            flag = 1;
        }
        else cout << " " << v;
    
        p = G->adjlist[v].firstarc;  //p指向顶点v的第一个邻接点
        while (p!=NULL)              //遍历
        {
            if (!visited[p->adjvex])
                DFS(G, p->adjvex);
            p = p->nextarc;          //p指向v的下一个邻接点
        }
    }
    

    深度遍历适用哪些问题的求解。(可百度搜索)

    1.全排列问题
    2.连通分量包含顶点数量问题
    3.二维数组寻找最短路径问题
    4.检测无向图中是否含环问题
    5.棋盘问题

    1.2.2 广度优先遍历

    广度遍历代码:

    //邻接矩阵代码:
    void BFS(MGraph g, int v)
    {
        int i, k;
        int cur_node;       
        int queue[MAXV];
        int front, rear;
        front = rear = 0;     //建立队列
    
        
        visited[0] = 0;      //初始化0
    
        visited[v - 1] = 1;
        queue[rear++] = v;//enqueue
    
        cout << v;
    
        while (front != rear)
        {
            cur_node = queue[front++];
            for (i = 0; i < g.n; i++)
            {
                if (visited[i] == 0 && g.edges[cur_node-1][i] != 0)
                {
                    cout << " " << i + 1;
                    queue[rear++] = i + 1;
                    visited[i] = 1;
                }
            }
        }
    }
    
    //邻接表代码
    void BFS(AdjGraph *G, int v)//v节点开始广度遍历
    {
    	ArcNode *p;//新建结点储存当前信息
    	queue<int>q;
    	cout << v;
    	q.push(v);
    	visited[v] = 1;//已访问
    	int w;
    	while (!q.empty())
    	{
    		w = q.front();
    		q.pop();
    		p = G->adjlist[w].firstarc;
    		while (p != NULL)//遍历当前链
    		{
    			if (visited[p->adjvex] == 0)//未访问过
    			{
    				visited[p->adjvex] = 1;
    				cout << " " << p->adjvex;
    				q.push(p->adjvex);
    			}
    			p = p->nextarc;
    		}
    	}
    }
    

    广度遍历适用哪些问题的求解。(可百度搜索)

    1.迷宫问题
    2.两顶点间最短路径问题

    1.3 最小生成树

    用自己语言描述什么是最小生成树。

    1.3.1 Prim算法求最小生成树

    实现Prim算法的2个辅助数组:

    closest数组:表示边的最小权值
    lowcost数组:存放最优的边

    Prim算法代码:

    //最小生成树Prim算法代码: 
    void Prim(Graph G)
    {
    	int v=0;//初始节点
    	closedge C[MaxVerNum];
    	int mincost = 0; //记录最小生成树的各边权值之和
    	//初始化
    	for (int i = 0; i < G.vexnum; i++)
    	{
    		C[i].adjvex = v;
    		C[i].lowcost = G.Edge[v][i];
    	}
    	cout << "最小生成树的所有边:"<< endl;
    	//初始化完毕,开始G.vexnum-1次循环
    	for (int i = 1; i < G.vexnum; i++)
    	{
    		int k;
    		int min = INF;
    		//求出与集合U权值最小的点 权值为0的代表在集合U中
    		for (int j = 0; j<G.vexnum; j++)
    		{
    			if (C[j].lowcost != 0 && C[j].lowcost<min)
    			{
    				min = C[j].lowcost;
    				k = j;
    			}
    		}
    		//输出选择的边并累计权值
    		cout << "(" << G.Vex[k] << "," << G.Vex[C[k].adjvex]<<") ";
    		mincost += C[k].lowcost;
    		//更新最小边
    		for (int j = 0; j<G.vexnum; j++)
    		{
    			if (C[j].lowcost != 0 && G.Edge[k][j]<C[j].lowcost)
    			{   
    				C[j].adjvex = k;
    				C[j].lowcost= G.Edge[k][j];
    			}
    		}
     
    	}
    	cout << "最小生成树权值之和:" << mincost << endl;
    
    

    Prim算法:

    时间复杂度为o(n^2)。

    适用于稠密图的最小生成树,适用于邻接矩阵。

    因为Prim算法需要频繁的取边,使用对应边的权值。

    1.3.2 Kruskal算法求解最小生成树

    实现Kruskal算法的辅助数据结构:

    辅助数组vest用于记录起始点和终止点的下标,通过改变数组的值来改变顶点的所属集合。

    Kruskal算法代码:

           //最小生成树Kruskal算法代码
    void Kruskal(Graph G)
    {
    	sort(l.begin(), l.end(),cmp);
    	int verSet[MaxVerNum];
    	int mincost = 0;
    	for (int i = 0; i < G.vexnum; i++)
    		verSet[i] = i;
    	cout << "最小生成树所有边:" << endl;                          //对各边进行查看
    	
    	int all = 0;
    	for (int i = 0; i < G.arcnum; i++)
    	{
    		if (all == G.vexnum - 1)break;
    		int v1 = verSet[l[i].from];
    		int v2 = verSet[l[i].to];                           //连接两连通分支
    		
    		if (v1 != v2)
    		{
    			cout << "(" << l[i].from << "," << l[i].to << ") ";
    			mincost += l[i].weight;
    			
    			for (int j = 0; j < G.vexnum; j++)
    			{
    				if (verSet[j] == v2)verSet[j] = v1;
    			}
    			all++;                                      //连通分支合并
    		}
    	}
    	cout << "最小生成树权值之和:" <<mincost<<endl;
    

    Kruskal算法时间复杂度:

    <基于上述图结构求Kruskal算法生成的最小生成树的边序列
    Kruskal算法时间复杂度为O(n^2)

    1.4 最短路径

    1.4.1 Dijkstra算法求解最短路径

    基于上述图结构,求解某个顶点到其他顶点最短路径。(结合dist数组、path数组求解)

    Dijkstra算法辅助数据结构:

    <dist[]:记录当前顶点到对象顶点的当前最短路径长度
    path[]:记录对应顶点的前驱顶点

    Dijkstra算法(贪心算法)求解最优解问题:

    void Dijkstra(MGraph g, int v)//源点v到其他顶点最短路径
    {
        int* S = new int[g.n];
        int* dist = new int[g.n];
        int* path = new int[g.n];
        int i,j,k,MINdis;
    
        //初始化各个数组
        for (i = 0; i < g.n; i++)
        {
            S[i] = 0;
            dist[i] = g.edges[v][i];//
            //不需要进行分类,因为不存在边的权值已经初始化为INF
            if (g.edges[v][i] < INF)
            {
                path[i] = v;
            }
            else
            {
                path[i] = -1;
            }
        }
        S[v] = 1, dist[v] = 0, path[v] = 0;
    
        for (i = 0; i < g.n-1; i++)
        {
            //根据dist中的距离从未选顶点中选择距离最小的纳入
            MINdis = INF;
            for (j = 0; j < g.n; j++)
            {
                if (S[j] == 0 && dist[j] < MINdis)
                {
                    MINdis = dist[j];
                    k = j;
                }
            }
    
            S[k] = 1;
            
            //纳入新顶点后更新dist信息和path信息
            for (j = 0; j < g.n; j++)
            {
                if (S[j] == 0)//针对还没被选中的顶点
                {
                    if (g.edges[k][j] < INF //新纳的顶点到未被选中的顶点有边
                        && dist[k] + g.edges[k][j] < dist[j])//源点到k的距离加上k到j的距离比当前的源点到j的距离短
                    {
                        dist[j] = dist[k] + g.edges[k][j];
                        path[j] = k;
                    }
                }
            }
        }
    }
    

    Dijkstra算法的时间复杂度:单顶点时间复杂度为O(n2),对n个顶点时间复杂度为O(n3)。

    使用邻接矩阵存储结构来存储,算法中需要直接获取边的权值,而邻接矩阵获取权值的时间复杂度低于邻接表存储结构。

    1.4.2 Floyd算法求解最短路径

    Floyd算法解决问题:

    <1.求解顶点与顶点间的最短路径以及长度问题。
    2.无向图的最小环问题

    Floyd算法辅助数据结构:

    A[][]用于存放两个顶点之间的最短路径
    path[][],path数组用于存放其的前继结点。

    Floyd算法

    void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D)
    {    
    	int v,w,k;    
    	for(v=0; v<G.numVertexes; ++v)           /* 初始化D与P */  
    	{        
    		for(w=0; w<G.numVertexes; ++w)  
    		{
    			(*D)[v][w]=G.arc[v][w];	/* D[v][w]值即为对应点间的权值 */
    			(*P)[v][w]=w;		/* 初始化P */
    		}
    	}
    	for(k=0; k<G.numVertexes; ++k)   
    	{
    		for(v=0; v<G.numVertexes; ++v)  
    		{        
    			for(w=0; w<G.numVertexes; ++w)    
    			{
    				if ((*D)[v][w]>(*D)[v][k]+(*D)[k][w])
    				{/* 如果经过下标为k顶点路径比原两点间路径更短 */
    					(*D)[v][w]=(*D)[v][k]+(*D)[k][w];/* 将当前两点间权值设为更小的一个 */
    					(*P)[v][w]=(*P)[v][k];           /* 路径设置为经过下标为k的顶点 */
    				}
    			}
    		}
    	}
    }
    

    最短路径算法:

    SPFA 算法:

    <SPFA 算法是 Bellman-Ford算法 的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。
    最坏情况下复杂度和朴素 Bellman-Ford 相同,为 O(VE)。

    //伪代码
    ProcedureSPFA;
    Begin
        initialize-single-source(G,s);
        initialize-queue(Q);
        enqueue(Q,s);
        while not empty(Q) do begin
            u:=dequeue(Q);
            for each v∈adj[u] do begin
                tmp:=d[v];
                relax(u,v);
                if(tmp<>d[v])and(not v in Q)then enqueue(Q,v);
            end;
        end;
    End; 
    
    //C++代码
    
    #include<iostream>
    #include<vector>
    #include<list>
    using namespace std;
    struct Edge
    {
        int to,len;
    };
    bool spfa(const int &beg,//出发点
              const vector<list<Edge> > &adjlist,//邻接表,通过传引用避免拷贝
              vector<int> &dist,//出发点到各点的最短路径长度
              vector<int> &path)//路径上到达该点的前一个点
    //没有负权回路返回0
    //福利:这个函数没有调用任何全局变量,可以直接复制!
    {
        const int INF=0x7FFFFFFF,NODE=adjlist.size();//用邻接表的大小传递顶点个数,减少参数传递
        dist.assign(NODE,INF);//初始化距离为无穷大
        path.assign(NODE,-1);//初始化路径为未知
        list<int> que(1,beg);//处理队列
        vector<int> cnt(NODE,0);//记录各点入队次数,用于判断负权回路
        vector<bool> flag(NODE,0);//标志数组,判断是否在队列中
        dist[beg]=0;//出发点到自身路径长度为0
        cnt[beg]=flag[beg]=1;//入队并开始计数
        while(!que.empty())
        {
            const int now=que.front();
            que.pop_front();
            flag[now]=0;                  //将当前处理的点出队
            for(list<Edge>::const_iterator//用常量迭代器遍历邻接表
                    i=adjlist[now].begin(); i!=adjlist[now].end(); ++i)
                if(dist[i->to]>dist[now]+i->len)                         //不满足三角不等式
                {
                    dist[i->to]=dist[now]+i->len;                        //更新
                    path[i->to]=now;//记录路径
                    if(!flag[i->to])//若未在处理队列中
                    {
                        if(NODE==++cnt[i->to])return 1;                 //计数后出现负权回路
                        if(!que.empty()&&dist[i->to]<dist[que.front()])//队列非空且优于队首(SLF)
                            que.push_front(i->to);                    //放在队首
                        else que.push_back(i->to);                   //否则放在队尾
                        flag[i->to]=1;//入队
                    }
                }
        }
        return 0;
    }
    int main()
    {
        int n_num,e_num,beg;//含义见下
        cout<<"输入点数、边数、出发点:";
        cin>>n_num>>e_num>>beg;
        vector<list<Edge> > adjlist(n_num,list<Edge>());//默认初始化邻接表
        for(int i=0,p; i!=e_num; ++i)
        {
            Edge tmp;
            cout<<"输入第"<<i+1<<"条边的起点、终点、长度:";
            cin>>p>>tmp.to>>tmp.len;
            adjlist[p].push_back(tmp);
        }
        vector<int> dist,path;//用于接收最短路径长度及路径各点
        if(spfa(beg,adjlist,dist,path))cout<<"图中存在负权回路
    ";
        else for(int i=0; i!=n_num; ++i)
            {
                cout<<beg<<"到"<<i<<"的最短距离为"<<dist[i]<<",反向打印路径:";
                for(int w=i; path[w]>=0; w=path[w])cout<<w<<"<-";
                cout<<beg<<'
    ';
            }
    }
    pascal代码
    
    const
      maxp=10000;{最大结点数}
    var{变量定义}
      p,c,s,t:longint;{p,结点数;c,边数;s:起点;t:终点}
      a,b:array[1..maxp,0..maxp]of longint;{a[x,y]存x,y之间边的权;b[x,c]存与x相连的第c个边的另一个结点y}
      d,m:array[1..maxp]of integer;{d:队列,m:入队次数标记}
      v:array[1..maxp]of boolean;{是否入队的标记}
      dist:array[1..maxp]of longint;{到起点的最短路}
      head,tail:longint;{队首/队尾指针}
    procedure init;
    var
      i,x,y,z:longint;
    begin
      read(p,c);
      for i:=1 to c do begin
        readln(x,y,z);{x,y:一条边的两个结点;z:这条边的权值}
        inc(b[x,0]);b[x,b[x,0]]:=y;a[x,y]:=z;{b[x,0]:以x为一个结点的边的条数}
        inc(b[y,0]);b[y,b[y,0]]:=x;a[y,x]:=z;
      end;
      readln(s,t);{读入起点与终点}
    end;
     
    procedure spfa(s:longint);{SPFA}
    var
      i,j,now:longint;
    begin
      fillchar(d,sizeof(d),0);
      fillchar(v,sizeof(v),false);
      for j:=1 to p do dist[j]:=maxlongint;
      dist[s]:=0; v[s]:=true; d[1]:=s; {队列的初始状态,s为起点}
      head:=1; tail:=1;
      while head<=tail do{队列不空}
      begin
        now:=d[head];{取队首元素}
        for i:=1 to b[now,0] do
          if dist[b[now,i]]>dist[now]+a[now,b[now,i]] then
          begin
            dist[b[now,i]]:=dist[now]+a[now,b[now,i]];{修改最短路}
            if not v[b[now,i]] then{扩展结点入队}
            begin
              inc(m[b[now,i]]);
              if m[b[now,i]]=p then begin writeln('no way');halt;end;
                                                    {同一节点入队次数超过p,存在负环}
              inc(tail);
              d[tail]:=b[now,i];
              v[b[now,i]]:=true;
            end;
          end;
        v[now]:=false;{释放结点,一定要释放掉,因为这节点有可能下次用来松弛其它节点}
        inc(head);{出队}
      end;
    end;
     
    procedure print;
    begin
      writeln(dist[t]);
    end;
     
    begin
      init;
      spfa(s);
      print;
    end.
    

    比较:与bfs算法比较,复杂度相对稳定。但在稠密图中复杂度比迪杰斯特拉算法差。

    1.5 拓扑排序

    拓扑排序结构体代码:

    typedef struct Vnode
    {
        Vertex data;               //顶点信息
        int count;                  
        ArcNode* firstarc;        //指向第一条边
    } VNode;                    //邻接表头节点类型
    

    拓扑排序代码删除入度为0的结点

    void TopSort(AdjGraph *G)//邻接表拓扑排序。注:需要在该函数开始计算并初始化每个节点的入度,然后再进行拓扑排序
    {
    	int node[MAXV];
    	int counts = 0;
    	int top = -1;
    	int stacks[MAXV];
    	ArcNode *p;
    	int i, j, k = 0;
    	for (i = 0; i < G->n; i++)//初始化count
    	{
    		G->adjlist[i].count = 0;
    	}
    	for (i = 0; i < G->n; i++)
    	{
    		p = G->adjlist[i].firstarc;
    		while (p)//计算每个结点入度
    		{
    			G->adjlist[p->adjvex].count++;
    			p = p->nextarc;
    		}
    	}
    	for (i = 0; i < G->n; i++)
    	{
    		if (G->adjlist[i].count == 0)//结点为0入栈
    		{
    			stacks[++top] = i;
    		}
    	}
    	while (top > -1)
    	{
    		i = stacks[top--];
    		node[k++] = i;//进入数组
    		counts++;
    		p = G->adjlist[i].firstarc;
    		while (p)
    		{
    			j = p->adjvex;
    			G->adjlist[j].count--;//该节点入度-1
    			if (G->adjlist[j].count == 0)
    			{
    				stacks[++top] = j;
    			}
    			p = p->nextarc;
    		}
    	}
    	if (counts < G->n)//判断个数是否符合
    	{
    		cout << "error!";
    	}
    	else
    	{
    		for (i = 0; i < k; i++)
    		{
    			cout << node[i];
    			if (i != k - 1)
    			{
    				cout << " ";
    			}
    		}
    	}
    }
    

    用拓扑排序代码检查一个有向图有环路

    bool topologicalSort()
    	{
    		cout << "有向图的拓扑排序:" << endl;
    		stack<int> inDegree0VexStack;
    		for (int i = 0; i < vexNum; i++)
    		{
    			if (InDegree[i] == 0)
    			{
    				inDegree0VexStack.push(i);
    			}
    		}
    		int count = 0;//对输出顶点计数
    		while (!inDegree0VexStack.empty())
    		{
    			int i = inDegree0VexStack.top();//输入i号顶点,并计数
    			inDegree0VexStack.pop();
    			//cout << vecNodes[i]->Alphabet << " ";
    			cout << i + 1 << " ";
    			++count;
    			for (int j = 0; j < vexLists[i]->size(); j++)
    			{//对i号顶点的每一个邻接顶点j的入度减1,即i->i的邻接顶点   
    				int nodeNum = vexLists[i]->at(j)->num;
    				if ((--InDegree[nodeNum] == 0))
    				{//若入度减到了0,则入栈
    					inDegree0VexStack.push(nodeNum);
    				}
     
    			}
    		}
    		if (count < vexNum)
    		{//该有向图有环
    			return true;
    		}
    		else
    		{//该有向图无环,可将所有顶点按拓扑有序输出。
    			return false;
    		}
    	}
     
    	bool hasLoop()
    	{
    		if (topologicalSort())
    		{
    			cout << endl; 
    			cout << "该有向图有环!" << endl;
    			return true;
    		}
    		else
    		{
    			cout << endl;
    			cout << "该有向图无环!" << endl;
    			return false;
    		}
    	}//topologicalSort
    

    1.6 关键路径

    AOE-网

    <有向图中,用顶点表示活动,用有向边表示活动之间开始的先后顺序,则称这种有向图为AOV(Activity On Vertex)网络;AOV网络可以反应任务完成的先后顺序(拓扑排序)。
    在AOV网的边上加上权值表示完成该活动所需的时间,则称这样的AOV网为AOE(Activity On Edge)网

    关键路径概念:

    路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径

    关键活动:

    在关键路径上的活动叫关键活动。

    2.PTA实验作业(4分)

    2.1 六度空间(2分)

    <7-2 六度空间 (30 分)
    “六度空间”理论又称作“六度分隔(Six Degrees of Separation)”理论。这个理论可以通俗地阐述为:“你和任何一个陌生人之间所间隔的人不会超过六个,也就是说,最多通过五个人你就能够认识任何一个陌生人。”如图1所示。

    图1 六度空间示意图
    “六度空间”理论虽然得到广泛的认同,并且正在得到越来越多的应用。但是数十年来,试图验证这个理论始终是许多社会学家努力追求的目标。然而由于历史的原因,这样的研究具有太大的局限性和困难。随着当代人的联络主要依赖于电话、短信、微信以及因特网上即时通信等工具,能够体现社交网络关系的一手数据已经逐渐使得“六度空间”理论的验证成为可能。

    假如给你一个社交网络图,请你对每个节点计算符合“六度空间”理论的结点占结点总数的百分比。

    输入格式:
    输入第1行给出两个正整数,分别表示社交网络图的结点数N(1<N≤10^3,表示人数)、边数M(≤33×N,表示社交关系数)。随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个结点的编号(节点从1到N编号)。

    输出格式:
    对每个结点输出与该结点距离不超过6的结点数占结点总数的百分比,精确到小数点后2位。每个结节点输出一行,格式为“结点编号:(空格)百分比%”。

    输入样例:
    10 9
    1 2
    2 3
    3 4
    4 5
    5 6
    6 7
    7 8
    8 9
    9 10
    输出样例:
    1: 70.00%
    2: 80.00%
    3: 90.00%
    4: 100.00%
    5: 100.00%
    6: 100.00%
    7: 100.00%
    8: 90.00%
    9: 80.00%
    10: 70.00%

    2.1.1 伪代码

    伪代码

    用邻接矩阵构图
    初始化,对每条边赋值为1
     while (!qu.empty() && level < 6)
        {
            循环遍历顶点
            {
                若该顶点未被访问过,且该顶点与i之前存在边
                {
                    count++;//满足六度空间理论的结点数+1
                    i出栈
                    visited[i] = 1;//标记结点i为已访问
                    tail = i;
                }
            }
            if (last == temp)
            {
                该层遍历完成,level++
                last = tail;
            }
        }
    循环遍历每个顶点
    {
    BFS(每个顶点)
    输出: (和所求顶点距离小于等于6的顶点数) * 100.00 / 总顶点数
    }
    

    2.1.2 提交列表

    2.1.3 本题知识点

    链表的相关操作

    广度优先遍历

    邻接矩阵的构建

    2.2 村村通

    2.2.1 伪代码

    int main()
    {
    	输入边数和顶点数;
    	Create(n, e);
    	int num=0;
    	num = Prim(n, e);
    }
    void Create(int n, int e)
    {
    	对矩阵初始化;
    	修改矩阵;
    }
    int Prim(int n, int e)
    {
    	int closet[];//保存顶点下标
    	int lowcost[];//保存权值
    	int cost = 0;
    	lowcost[1] = 0;
    	lowcost[1] = 0;
    	初始化lowcost[]和closet;
    	for (i = 2; i <= 2; i++)
    	{
    		初始化min,j,k;
    		while (j < n)
    		{
    			找到权值最小的点记录下标;
    		}
    		if (判断下标是否改变, 若有证明连通)
    		{
    			记录cost和访问顶点操作;
    		}
    		else return -1;
    		修改lowcost和closet;
    	}
    }
    
    

    2.2.2 提交列表

    2.2.3 本题知识点

    图的邻接矩阵储存结构及其建图方法。

    建树初始化的时候有一个小细节是矩阵对角线的初始化可以置0,其他地方才置INF。

    二维指针动态开辟数组空间的方法。

  • 相关阅读:
    int.Parse()及其异常判断
    三个框框的EditBox
    等价类的划分方法与EditorBox问题等价类划分
    初学软件测试
    软件测试方法的分类细谈
    浅谈软件测试之回归测试
    白盒测试——基本路径法
    初探灰盒测试——介于白盒测试与黑盒测试的测试
    对闰年测试的非法输入处理的思考
    等价类测试——进一步完善的Web输入合法验证
  • 原文地址:https://www.cnblogs.com/keepgoingccc/p/14802518.html
Copyright © 2011-2022 走看看