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<<"图中存在负权回路\n";
    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<<'\n';
        }
}
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。

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

posted @ 2021-05-23 22:57  落叶满长安  阅读(109)  评论(1编辑  收藏  举报