| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |

| ---------- | ----------------- |
| 这个作业的地址 | DS博客作业04--图 |
| 这个作业的目标 | 学习图结构设计及相关算法 |
| 姓名 | 陈垚嘉 |

0.PTA得分截图

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

1.1 图的存储结构

邻接矩阵的结构体定义:

typedef struct{
    VertexType vexs[MaxVex];
    EdgeType   arc[MaxVex][MaxVex];
    int numVertexs, numEdges;
}AdjacencyMatrix;
建图函数:
//创建邻接矩阵
void CreateAMatrix(AdjacencyMatrix* AM)
{
    cout << "输入顶点数和边数:";
    cin >> AM->numVertexs >> AM->numEdges;
    cout << "==============================\n";
    cout << "输入各个顶点:" << endl;

    //邻接矩阵顶点输入
    for(int i = 0; i<AM->numVertexs; i++)
    {
        char v;
        cout << "顶点:" << i + 1;
        cin >> v;
        AM->vexs[i] = v;
    }

        //邻接矩阵初始化
    for (int i = 0; i < AM->numVertexs; i++)
    {
        for (int j = 0; j<AM->numVertexs; j++)
        {
            AM->arc[i][j] = INF;
        }
    }

    cout << "==============================\n";
    //输入边的值
    for (int k = 0; k<AM->numEdges; k++)
    {
        char i, j, w;
        cout << "输入边(vi,vj)中的下标i和j和权重w:";
        cin >> i >> j >> w;
        AM->arc[i + Acsii][j + Acsii] = w;
        AM->arc[j + Acsii][i + Acsii] = AM->arc[i + Acsii][j + Acsii];
    }

}

1.1.1 邻接矩阵(不用PPT上的图)

邻接矩阵:

    0   10  0   30   100
    0   0   50   0    0
    0   0   0    0   10
    0   60  20   0   60
    0   0   0    0   0

1.1.2 邻接表



邻接表的结构体定义:

typedef struct EdgeNode/* 边表结点  */
{
    int adjvex;/* 邻接点域,存储该顶点对应的下标 */
    EdgeType weight;/* 用于存储权值,对于非网图可以不需要 */
    struct EdgeNode *next; /* 链域,指向下一个邻接点 */
} EdgeNode;

typedef struct VextexNode/* 顶点表结点 */
{
    VertexType data;/* 顶点域,存储顶点信息 */
    EdgeNode *firstedge;/* 边表头指针 */
} VextexNode, AdjList[MAXVEX];

typedef struct
{
    AdjList adjList;
    int numNodes, numEdges; /* 图中当前顶点数和边数 */
} GraphAdjList;
建图函数:
void CreateALGraph(GraphAdjList *Gp)
{
    int i, j, k;
    EdgeNode *pe;
    cout << "输入顶点数和边数(空格分隔):" << endl;
    cin >> Gp->numNodes >> Gp->numEdges;

    for (i = 0 ; i < Gp->numNodes; i++)
    {
        cout << "输入顶点信息:" << endl;
        cin >> Gp->adjList[i].data;
        Gp->adjList[i].firstedge = NULL;/* 将边表置为空表 */
    }

    for (k = 0; k <  Gp->numEdges; k++)/* 建立边表 */
    {
        cout << "输入边(vi,vj)的顶点序号i,j(空格分隔):" << endl;
        cin >> i >> j;
        pe = (EdgeNode *)malloc(sizeof(EdgeNode));
        pe->adjvex = j;/* 邻接序号为j */
        /* 将pe的指针指向当前顶点上指向的结点 */
        pe->next = Gp->adjList[i].firstedge;
        Gp->adjList[i].firstedge = pe;/* 将当前顶点的指针指向pe */

        pe = (EdgeNode *)malloc(sizeof(EdgeNode));
        pe->adjvex = i;
        pe->next = Gp->adjList[j].firstedge;
        Gp->adjList[j].firstedge = pe;

    }
}

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

*在存储方式上:邻接矩阵用一维数组存放顶点,用二维数组存放邻接关系。而邻接表用头节点存放顶点用表结点存放邻接关系。
*从操作角度来说:邻接矩阵易于判定顶点是否邻接,查顶点的邻接点但是插入、删除顶点复杂。邻接表易于:查询某顶点的邻接点,边或弧的插入、删除但是判定顶点是否邻接,比邻接矩阵低效。
稀疏图选择邻接表比较合适,稠密图适合邻接矩阵。
如果定点数为n,边数为e
1.若采用邻接矩阵存储,时间复杂度为O(n^2); 
2.若采用邻接链表存储,建立邻接表或逆邻接表时,若输入的顶点信息即为顶点的编号,则时间复杂度为O(n+e);若输入的顶点信息不是顶点的编号,需要通过查找才能得到顶点在图中的位置,则时间复杂度为O(n
e);

1.2 图遍历

1.2.1 深度优先遍历

深度遍历从A开始,结果并不唯一。其中一个结果为哦A-B-C-E-D。
深度遍历代码:

int visitedDFS[MAXV] = { 0 };									//全局数组,记录是否遍历
void DFS(ListGraph* LG, int v) {
	EdgeNode* p;
	visitedDFS[v] = 1;											//记录已访问,置 1
	printf("%2d", v);											//输出顶点编号
	p = LG->adjList[v].firstEdge;								//p 指向顶点 v 的第一个邻接点
	while (p != NULL) {
		if (visitedDFS[p->adjVer] == 0 && p->weight != INF) {	//如果 p->adjVer 没被访问,递归访问它
			DFS(LG, p->adjVer);
		}
		p = p->nextEdge;										//p 指向顶点 v 的下一个邻接点
	}
}

深度优先遍历求解哪些问题:可以解决连通域求解问题、最长路径问题等

1.2.2 广度优先遍历


同样,广度优先遍历结果不唯一。其中从顶点A出发一个结果为:A-B-D-E-C

void BFSTraverse(AdjacencyMatrix* AM)
{
    queue<int> Q;
    for (int i = 0; i<AM->numVertexs; i++)
        visited[i] = 0;

    //InitQueue(&Q);                        //初始化一个空列表
    for (int i = 0; i<AM->numVertexs; i++)
    {
        if (!visited[i])
        {
            visited[i] = 1;
            cout << AM->vexs[i];
            //EnQueue(&Q, i);               //将i插入到队列的队尾
            Q.push(i);

            while (!Q.empty())
            {
                i = Q.front();            //删除队列的队头元素,并用i返回这个值
                Q.pop();                  //你用的时候用front取出来尽管用,等到用完了再pop。
                                        //删除队列的队头元素,并用i返回这个值
                                        //这边除了在队列中把第一个元素删掉以外
                                        //还要把这个值返回,就像表中给的,还要根据
                                        //返回的A来找到B和F呢
                for (int j = 0; j<AM->numVertexs; j++)
                {
                    if (!visited[j] && AM->arc[i][j] != INF)
                    {
                        visited[j] = 1;
                        cout << AM->vexs[j];
                        //EnQueue(&Q, &j);
                        Q.push(j);     //这就相当于把B和F分步输入到队列中
                    }
                }

            }
        }
    }
}

广度优先遍历解决问题:最短路径问题,其思想是BFS(广度优先遍历)在一般的带权图中是不能解决最短路问题,了解BFS的都知道,BFS是根据节点到源节点之间的节点数遍历的,也就是先访问离源节点节点数最少的点。要使得BFS能计算最短路径,需要图结构满足所有的权值相等。

1.3 最小生成树

最小生成树是指由n个结点的连通图的生成树时原图的极小连通子图,并且一定包含原图中所有的节点,并且有保持图连通的最少的边。

1.3.1 Prim算法求最小生成树


基于上述图结构求prim算法生成的最小生成树的边序列:
step1:

step2:

step3:

step4:

最后得到生成树为:

实现prim算法的两个辅助数组是什么及其作用:
为了实现Prim算法,我们需要依照思想设置一些辅助数组。
int lowcost[v] 表示以v为终点的边(u,v)的权值,v 是当前尚未选入生成树的顶点;
int nearest[v] 保存边(u,v)的另一个顶点u,u 在生成树上;
bool visited[i] 标志某个顶点当前是否已被选入在生成树上。
Prim算法代码:

void prim(int source)       //起点
{
    memset(lowcost,INF,sizeof(lowcost));
    memset(visited,false,sizeof(visited));
    visited[source] = true;
    for(int i=0;i<vertex_num;i++){
            lowcost[i] = matrix[source][i];
            nearest[i] = source;   
    }
    int min_cost;                       //最小权值
    int min_cost_index;                 //最小权值对应的边的未在最小生成树的那一点
    sum = 0;
    for(int i=1;i<vertex_num;i++){           //寻找除起点以外的n-1个点
        min_cost = INF;
        for(int j=0;j<vertex_num;j++){
            if(visited[j]==false && lowcost[j]<min_cost){
                min_cost = lowcost[j];
                min_cost_index = j;             //定位顶点
            }
        }
        visited[min_cost_index] = true;         //将已进入最小代价生成树的结点标志位true
        sum += lowcost[min_cost_index];
        for(int j=0;j<vertex_num;j++){           //以找到的最小下标为起点更新lowcost数组
            if(visited[j]==false && matrix[min_cost_index][j]<lowcost[j]){
                lowcost[j] = matrix[min_cost_index][j];
                nearest[j] = min_cost_index;
            }
        }
    }
}

Prim算法的时间复杂度,适合什么图?
Prim算法的时间复杂度为O(V2),不依赖于E,因此它适用于求解边稠密的图的最小生成树。

13.2 Kruskal算法求解最小生成树

基于上述图结构求Kruskal算法生成的最小生成树边序列
step1:


step2:

step3:

step4:

实现Kruskal算法的辅助数据结构是什么?其作用是?

Kruskal算法用到的数据结构有:

1、边顶点与权值存储结构(即图是由连接某一条边的两个顶点,以及这条边的权值来进行存储)

2、并查集:并查集就是一个用双亲表示法所表示的森林,我们可以利用这个结构来查找某一个顶点的双亲,进而找到根结点。这样,我们就能判断某两个顶点是否同源,在图中的表现就是加上这条边后会不会形成环。如果形成环,就不是简单图。并查集以顶点为基准,有几个顶点,就有几项。

分析Kruskal算法的时间复杂度,使用什么图结果,为什么

克鲁斯卡尔算法,从边的角度求网的最小生成树,时间复杂度为O(eloge)。和普里姆算法恰恰相反,更适合于求边稀疏的网的最小生成树。因为克鲁斯卡尔算法主要针对边展开,边数少时效率会很高,所以对于稀疏图有优势。

1.4 短路径

1.4.1 Dijkstra算法求解最短路径


从顶点A开始

Dijkstra算法如何解决贪心算法无法求解最用问题?展示算法中的解决代码。

求解过程:
1、对于一个有向带权图<V,E>,将N中顶点点分成两组:
第一组S:已求出的最短路径的终点的集合。(初始时,只包含源点V0)
第二组V-S:尚未求出的最短路径的顶点集合。(初始时为,V-{V0})
2、算法按将各顶点与V0同最短路径长度递增的次序,逐个将集合V-S中的顶点,加入到集合S中去。
3、在这个过程中,总能保持从V0到集合S中各顶点的路径长度时始终不大于到集合V-S中各顶点的路径长度。
代码:

void ShortestPath_Dijkstra(AMGraph G, int V0){
	//step1 n个顶点依次初始化
	int n =G.vexnum;  
	for(int v=0;v<n;v++){
		S[v] = false;
		D[v] = G.arcs[V0][v];
		if(D[v]<MaxInt){
			Path[v] = V0;
		} else {
			Path[v] = -1;
		}
	}
	//step2 将源点V0划入已确定集合S中 
	S[V0] = true;
	D[V0] = 0; // 源点V0到源点V0的最短路径长度必然为0
	//step3 贪心算法策略:
	//			3.1 循环遍历所有结点:
	//				3.2 先确定当前最短路径的终点v;
	//				3.3 然后,将v划入已确定集合S中;
	//				3.4 最后,以利用结点v更新所有尚未确定的结点的最短路径
	int v;
	int min;
	D[G.vexnum] = MaxInt;
	for(int i=1;i<n;i++){//3.1循环遍历所有结点 (即 求从源点V0到图中每一顶点(共计n-1个顶点)的最短路径) 
		//3.2 确定当前最短路径的终点v;
		min = MaxInt;
		for(int w=0;w<n;w++){
			if(S[w]==false && D[w]<min){//比本轮循环中,已知的最短路径还短 【易错/易漏】 S[w]==false : 必须满足当前结点 Vw 属于尚未确定的结点 
				v = w;
				min = D[w];
			}
		}
		//3.3 然后,将v划入已确定集合S中;
		S[v] = true;
		//3.4 最后,以利用结点v更新所有尚未确定的结点的最短路径
		for(int w=0;w<n;w++){
			//↓更新Vw结点的最短路径长度为 D[v] + G.arcs[v][w] 
			//cout<<"S["<<w<<"]:"<<S[w]<<"D["<<v<<"]"<<D[v]<<"G.arcs["<<v<<"]["<<w<<"]"<<"D["<<w<<"]"<<D[w]<<endl; 
			if(S[w]==false && (D[v] + G.arcs[v][w] < D[w])){//【易错/易漏】 S[w]==false : 必须满足当前结点 Vw 属于尚未确定的结点 
				D[w] = D[v] + G.arcs[v][w];
				Path[w] = v; // 更新 结点Vw的前驱为 v 
			}
		}
		v = G.vexnum;
	} 
}

Dijkstra算法的时间复杂,适用什么图结构,为什么
Dijkstra 时间复杂度:O(n^3),Dijkstra算法适用于求图中两节点之间最短路径,适用于边的长度均不为负数的有向图,它计算从一个起始顶点到其他所有顶点的最短路径的长度。因为他的求解过程可以求出初始节点到各个节点的最短路径长度。

1.4.2 Floyd算法求解最短路径

Floyd算法解决什么问题:
Floyd算法可以解决多源最短路径问题。
Floyd算法需要哪些辅助数据结构:
数组: D[MVNum][MVNum]; // 记录顶点Vi和Vj之间的最短路径长度
数组 Path[MVNum][MVNum]; // 最短路径上顶点Vj的前一顶点的序号
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单
比较:

1.5 拓扑排序


拓扑排序结果并不唯一,如上图的拓扑排序有:C-E-A-B-F;E-A-C-B-F;E-C-A-B-F等。
代码:

void TopLogicalSort(Graphlnk<T, E> &G) {
 int i, w, v;
 int n; // 顶点数
 int *count = new int[DefaultVertices]; // 入度数组
 int top = -1;
  
 // 清零
 for(i = 0; i< DefaultVertices; i++)
 count[i] = 0;
 // 输入顶点和边
 G.inputGraph(count);
 n = G.numberOfVertices(); // 获取图的顶点数
 for(i = 0; i < n; i++) { // 检查网络所有顶点
 if(count[i] == 0) { // 入度为0的顶点进栈
  count[i] = top;
  top = i;
 }
 }
 // 进行拓扑排序,输出n个顶点
 for(i = 0; i < n; i++) {
 if(top == -1) { // 空栈
  cout << "网络中有回路!" << endl;
  return;
 } else {
  v = top;
  top = count[top];
  cout << G.getValue(v) << " "; // 输出入度为0的顶点
  w = G.getFirstNeighbor(v); // 邻接顶点
  while(w != -1) { // 扫描出边表
  if(--count[w] == 0) { // 邻接顶点入度减1,如果入度为0则进栈
   count[w] = top;
   top = w;
  }
  w = G.getNextNeighbor(v, w); // 兄弟结点(取顶点v的邻接顶点w的下一邻接顶点)
  }
 }
 }
 cout << endl;
}
  

边的定义:

template <class T, class E>
struct Edge { // 边结点的定义
 int dest; // 边的另一顶点位置
 Edge<T, E> *link; // 下一条边链指针
};
顶点的定义:

struct Vertex { // 顶点的定义
 T data; // 顶点的名字
 Edge<T, E> *adj; // 边链表的头指针
};
伪代码:
定义一个int型顶点数 n 存储顶点的个数
定义一个int型数组count存储入度
for(顶点个数未遍历完)
初始化入度为零;
for(所有结点未遍历完){
if(入度为零){
  顶点v进栈;
}
}
for(遍历拓扑排序中所有顶点){
if(空栈){
  图中有回路;
       }else{
         取栈顶元素;
         While(当前存在顶点){
           If(入度为零)
                   出栈;
            }
         取邻接顶点;
         }
}

如何用拓扑排序代码检查一个有向图是否有环路:
如果能够用拓扑排序完成对图中所有节点的排序的话,就说明这个图中没有环。
而如果不能完成,则说明有环。

1.6 关键路径

什么叫AOE-网:

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

完成整个工程所需的时间等于从源点到汇点的最长路径长度,即该路径中所有活动的持续时间之和最大。这条路径称为关键路径(critical path)。关键路径上所有活动都是关键活动。所谓关键活动(critical activity),是不按期完成会影响整个工程进度的活动。只要找到关键活动,就可以找到关键路径。

2.PTA实验作业(4分)

2.1 六度空间(2分)

2.1.1 伪代码(贴代码,本题0分)

传入G[MAXVEX][MAXVEX]为图G的邻接矩阵
队列初始化;
For(j=0;j<=Nv;j++) do
          Visited[j]=0;//初始所有节点未访问
End for
Visited[i]=1;
i入队
While(队列不为空)
{
出队;
      For(j=1;j<=Nv;j++) do
if(顶点未被访问)do
                    Visited[j]=1;//访问
                    入队;
     End if
End fo
If(temp==last)do
level++;
        Last = tail;
        cnt++;
        j++;
End do
If(当前访问第六层)do
break;
End do
}
返回 cnt;

2.1.2 提交列表

2.1.3 本题知识点

邻接表的建图和广度遍历BFS
使用node和lastnode两个变量来记录每一层次结束的最后结点,从而使level在一层遍历结束后增加
使用level记录遍历层次来控制广度遍历所遍历的层次,在达到规定距离时退出循环,得到距离内结点个数

2.2 村村通或通信网络设计或旅游规划(2分)

传入图G的邻接矩阵
for (i = 1; i <= n; i++) do//对链接矩阵初始化
  for (j = 1; j <= n; j++) do
   G[i][j] = 999999;
  end for
end for
输入图的各条边的权值;
for (i = 1; i <= n; i++) do//将和源点所有相通的点对应边的权值复制给数组cost[i]
  cost[i] = G[1][i];
end do
for (t = 1; t < n; t++) do
  min = 999999;//初始化最小值
  for (i = 1; i <= n; i++) do
   if (如果不是源点到自身并且权值小于最小值) do
    min = cost[i];//重新赋值最小值
   End if
 End for
 if (k != 0) do
   sum = sum + cost[k];//全职求和
   for (j = 1; j <= n; j++) do
if (G[k][j] < cost[j]) do
     cost[j] = G[k][j];
End if   
End for  
End if
 End for
 for (i = 1; i <= n; i++) { //遍历所有路径
  if (cost[i] != 0) { //如果未被访问
   flag = 1;
   break;
  }
 }
 if (flag == 1) do   
  cout<<"-1";
End if
 else  
  cout<<sum;
 return 0;

伪代码为思路总结,不是简单翻译代码。

2.2.2 提交列表

2.2.3 本题知识点

这道题根本是考察求最小生成树的算法,可以用Prim或者Kruskal算法解决

posted @ 2021-05-28 19:56  山高念做垚  阅读(89)  评论(0编辑  收藏  举报