0.PTA得分截图
1.本周学习总结
1.1 总结图内容
1.1 图存储结构
- 邻接矩阵
- 结构体
#define MAXV 20
typedef struct { //图的定义
int edges[MAXV][MAXV]; //邻接矩阵
int n,e; //顶点数,弧数
} MGraph; //图的邻接矩阵表示类型
- 建立代码
void CreateMGraph(MGraph &g, int n, int e)
{
int i, j, k;
for (i = 0; i <= n; i++)//矩阵初始化
{
for (j = 0; j <= n; j++)
{
g.edges[i][j] = 0;
}
}
for (k = 1; k <= e; k++)
{
cin >> i >> j;
g.edges[i][j] = 1;
g.edges[j][i] = 1;
}
g.e = e;
g.n = n;1. 结构体
}
- 特点:
- 适用于稠密图
- 无向图一定轴对称,有向图不一定
- 每点由行指向列
- 邻接表
- 结构体
#define MAXV 20
typedef struct ANode {
int adjvex; //该边的终点编号
struct ANode *nextarc; //指向下一条边的指针
int info; //该边的相关信息,如权重
} ArcNode; //边表节点类型
typedef struct Vnode {
int data; //顶点信息
ArcNode *firstarc; //指向第一条边
} VNode; //邻接表头节点类型
typedef struct {
VNode adjlist[MAXV]; //邻接表
int n,e; //图中顶点数n和边数e
} AdjGraph;
- 建立代码
void CreateGraph(Adjgraph *&g, int V, int E)
{
int i, j, a, b;
ArcNode *p;
g = new Adjgraph;
for (i = 1; i <= V; i++)
g->adjlist[i].firstarc = NULL;
for (i = 1; i <= E; i++)
{
cin >> a >> b;
p = new ArcNode;
p->adjnode = b;
//p->color = -1;
p->nextarc = g->adjlist[a].firstarc;
g->adjlist[a].firstarc = p;
p = new ArcNode;
p->adjnode = a;
//p->color = -1;
p->nextarc = g->adjlist[b].firstarc;
g->adjlist[b].firstarc = p;
}
g->n = V;
g->e = E;
}
- 特点:
- 适用于稀疏图
1.2图遍历及应用。包括DFS,BFS.如何判断图是否连通、如何查找图路径、如何找最短路径。
- DFS
以邻接表为存储结构
void DFS(AdjGraph *G, int v)
{
int i, j;
ArcNode *p;
visited[v] = 1;
if (flag == 0)
{
cout << v;
flag = 1;
}
else
cout << " " << v;
p = G->adjlist[v].firstarc;
while (p)
{
if (!visited[p->adjvex])
DFS(G, p->adjvex);
p = p->nextarc;
}
}
以邻接矩阵为存储结构
void DFS(MGraph g, int v)
{
int i,j;
i = v;
visited[v] = 1;
if (flag == 0)
{
cout << v;
flag = 1;
}
else
cout << " " << v;
for (j = 1; j <= g.n; j++)
{
if(g.edges[i][j]&&!visited[j])
DFS(g, j);
}
}
- BFS
邻接矩阵:
void BFS(MGraph g, int v)
{
visited[v] = 1; //当前节点已访问过,数组值置为1
queue<int>qu;
qu.push(v); //节点入队列
cout << v << " ";
int item, i;
while (!qu.empty())
{
item = qu.front();
qu.pop();
for (i = 1; i <= g.n; i++)
{
if (!visited[i]&&g.edges[item][i]==1) //节点i未访问过,且队头元素item表示的节点与节点i间有边
{
qu.push(i);
cout << v << " ";
visited[i] = 1;
}
}
}
}
邻接表:
void BFS(AdjGraph* G, int v)
{
queue<int>qu;
qu.push(v);
ArcNode* ptr;
int item;
cout << v << " ";
visited[v] = 1; //当前节点已访问过,数组值置为1
while (!qu.empty())
{
item = qu.front();
ptr = G->adjlist[item].firstarc; //边指针ptr指向item表示的节点所连的第一条边
while (ptr != NULL)
{
if (visited[ptr->adjvex] == 0) //该边的终点还未被访问
{
qu.push(ptr->adjvex);
visited[ptr->adjvex] = 1;
cout << ptr->adjvex << " ";
}
ptr = ptr->nextarc;
}
qu.pop();
}
}
1.3最小生成树相关算法及应用
1.3.1 定义:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal算法或prim算法求出。
1.3.2 特点:边数一定为n-1;树不唯一
1.3.3 Prim算法
1. 特点:
- 时间复杂度:O(n2)
- 适用于稠密图,存储结构为邻接矩阵
2. 代码
void prim(MGraph g, int v)
{
int lowcost[MAXV], min, i, j, k = 0;
int closest[MAXV];
int sum = 0;
for(i = 1; i <= g.n; i++) //给数组lowcost[]和closest[]置初值
{
lowcost[i] = g.edges[v][i];
closest[i] = v;
}
lowcost[v] = 0; //顶点v已经加入树中
for (i = 1; i < g.n; i++) //找出(n-1)个顶点
{
min = 10000;
k = 0;
for (j = 1; j <= g.n; j++) //找出离树中节点最近的顶点k
{
if (lowcost[j] != 0 && lowcost[j] < min)
{
min = lowcost[j];
k = j; //k记录最近顶点的编号
}
}
if (k == 0) //不是连通图
{
cout << "-1" << endl;
return;
}
sum += min; //变量sum存储最小生成树中边的权值
lowcost[k] = 0; //顶点k已经加入树中
for (j = 1; j <= g.n; j++)
{
if (lowcost[j] != 0 && g.edges[k][j] < lowcost[j])
{
lowcost[j] = g.edges[k][j];
closest[j] = k;
}
}
}
cout << sum << endl;
}
1.3.4 Kruskal算法
1. 特点
- 时间复杂度:O(eloge);
- 适用于稀疏图,存储结构为邻接表
2. 代码:
typedef struct {
int u; //边的起始顶点
int v; //边的终止顶点
int w; //边的权值
}Edge;
//改进的克鲁斯卡尔算法(使用了堆排序,并查集)
void Kruskal(AdjGraph* g)
{
int i,j,k,u1,v1,sn1,sn2;
UFSTree t[MAXSize]; //并查集,树结构
ArcNode* p;
Edge E[MAXSize];
k=1; // E数组的下标从1开始计
for(i = 0; i < g.n; i++)
{
p=g->adjlist[i].firstarc;
while(p!=NULL)
{
E[k].u=i;
E[k].v=p->adjvex;
E[k].w=p->weight;
k++;
p=p->nextarc;
}
}
HeapSort(E,g.e); //采用堆排序对E数组按权值递增排序
MAKE_SET(t,g.n); //初始化并查集树t
k=1; //k表示当前构造生成树的第几条边,初值为1
j=1; //E中边的下标,初值为1
while(k<g.n) //生成的边数为n-1
{
u1=E[j].u;
v1=E[j].v; //取一条边的头尾顶点编号u1和v1
sn1=FIND_SET(t,u1);
sn2=FIND_SET(t,v1); //分别得到两个顶点所属的集合编号
if(sn1!=sn2) //两顶点属不同集合
{
k++; //生成边数增1
UNION(t, u1, v1); //将u1和v1两个顶点合并
}
j++; //下一条边
}
}
1.4 最短路径相关算法及应用,可适当拓展最短路径算法
- Dijkstra算法
-
特点:
- 只能求一点到其他点的距离
-
代码:
void Dijkstra(MGraph g, int v)
{
int dist[MAXV], path[MAXV];
int s[MAXV];
int mindis, i, j, u;
for (i = 0; i < g.n; i++)
{
dist[i] = g.edges[v][i];
s[i] = 0;
if (g.edges[v][i] < INF)
{
path[i] = v;
}
else
{
path[i] = -1;
}
}
s[v] = 1;
for (i = 0; i < g.n; i++)
{
mindis = INF;
for (j = 0; j < g.n; j++)
{
if (s[j] == 0 && dist[j] < mindis)
{
u = j;
mindis = dist[j];
}
}
s[u] = 1;
for (j = 0; j < g.n; j++)
{
if (s[j] == 0)
{
if (g.edges[u][j] < INF && dist[u] + g.edges[u][j] < dist[j])
{
dist[j] = dist[u] + g.edges[u][j];
path[j] = u;
}
}
}
}
}
- Floyd算法
-
特点:
- 优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
- 缺点:时间复杂度比较高,不适合计算大量数据。
-
代码:
void Floyd(Graph G)
{
int A[MAXVEX][MAXVEX]; //建立A数组
int path[MAXVEX][MAXVEX]; //建立path数组
int i, j, k;
for (i=0;i<G.n;i++)
for (j=0;j<G.n;j++)
{
A[i][j]=G.edges[i][j];
if (i!=j && G.edges[i][j]<INF)
path[i][j]=i; //i和j顶点之间有一条边时
else
path[i][j]=-1;
}
for (k=0;k<G.n;k++) //求Ak[i][j]
{
for (i=0;i<G.n;i++)
for (j=0;j<G.n;j++)
if (A[i][j]>A[i][k]+A[k][j]) //找到更短路径
{
A[i][j]=A[i][k]+A[k][j]; //修改路径长度
path[i][j]=path[k][j]; //修改最短路径为经过顶点k
}
}
}
1.5 拓扑排序、关键路径
- 拓扑排序:在一个有向图中找到拓扑序列的过程
- 代码:
typedef struct //表头结点类型
{
Vertex data; //顶点信息
int count; //存放顶点入度
ArcNode *firstarc; //指向第一条边
}VNode;
void TopSort(AdjGraph *G) //拓扑排序算法
{
int i,j;
int St[MAXV],top=-1; //栈St的指针为top
ArcNode *p;
for (i=0;i<G->n;i++) //入度置初值0
G->adjlist[i].count=0;
for (i=0;i<G->n;i++) //求所有顶点的入度
{
p=G->adjlist[i].firstarc;
while (p!=NULL)
{
G->adjlist[p->adjvex].count++;
p=p->nextarc;
}
}
for (i=0;i<G->n;i++) //将入度为0的顶点进栈
if (G->adjlist[i].count==0)
{
top++;
St[top]=i;
}
while (top>-1) //栈不空循环
{
i=St[top];top--; //出栈一个顶点i
printf("%d ",i); //输出该顶点
p=G->adjlist[i].firstarc; //找第一个邻接点
while (p!=NULL) //将顶点i的出边邻接点的入度减1
{
j=p->adjvex;
G->adjlist[j].count--;
if (G->adjlist[j].count==0) //将入度为0的邻接点进栈
{
top++;
St[top]=j;
}
p=p->nextarc; //找下一个邻接点
}
}
}
- 关键路径:从有向图的源点到汇点的最长路径
1.2.谈谈你对图的认识及学习体会。
- 图的认识: 图感觉是树的延伸,它的复杂度以及可应用的范围都变大了,从不带权值的朋友圈,到带权值的交通网络和计算机网络搭建,所以我想在日后的学习和工作中可以经常接触到这一块.
- 学习体会:像上面说的图的应用较广,学习中也常接触生活中常见的问题,这给了我更多学习它的兴趣,但学习的过程注定是枯燥的,因此......PTA刷的不太行。
2.阅读代码(0--5分)
2.1 题目及解题代码
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* findMinHeightTrees(int n, int** edges, int edgesSize, int* edgesColSize, int* returnSize){
int* pQueue = (int*)malloc(n * sizeof(int)); // 队列
memset(pQueue, 0, n * sizeof(int));
int front = -1; // 队头下标
int rear = -1; // 队尾下标
int curr = 0; // 当前队头元素
int lenghOfQue = 0; // 队长
if (1 == n && 0 == edgesSize)
(
*returnSize = 1;
return pQueue;
}
int* pIndegree = (int*)malloc(n * sizeof(int)); // 入度数组
memset(pIndegree, 0, n * sizeof(int)); // 入度初始为0
int** ppGraph = (int**)malloc(n * sizeof(int*)); // 二维数组邻接表
int* pColOfGraph = (int*)malloc(n * sizeof(int)); // 每个结点相邻结点个数
memset(pColOfGraph, 0, n * sizeof(int));
int row = 0;
int col = 0;
for (row = 0; row <= n - 1; row++)
{
ppGraph[row] = (int*)malloc(n * sizeof(int*));
}
for (row = 0; row <= edgesSize - 1; row++) // 初始化邻接表:图 入度 相邻点
{
ppGraph[edges[row][1]][pIndegree[edges[row][1]]++] = edges[row][0];
ppGraph[edges[row][0]][pIndegree[edges[row][0]]++] = edges[row][1];
pColOfGraph[edges[row][1]]++;
pColOfGraph[edges[row][0]]++;
}
for (row = 0; row <= n - 1; row++) // 入度为1的结点进队
{
if (1 == pIndegree[row])
{
pQueue[++rear] = row;
}
}
while (2 < n) // 结果只能是1或者2,证明略
{
lenghOfQue = rear - front; // 队长
n -= lenghOfQue; // 更新结点个数
while (lenghOfQue--)
{
curr = pQueue[++front]; // peek & pop
for (col = 0; col <= pColOfGraph[curr] - 1; col++)
{
--pIndegree[curr];
--pIndegree[ppGraph[curr][col]];
if (1 == pIndegree[ppGraph[curr][col]])
{
pQueue[++rear] = ppGraph[curr][col]; // push
}
}
}
}
*returnSize = rear - front;
return pQueue + front + 1;
}
时间复杂度:O(N + E);
空间复杂度:O(N)O(N);
2.1.1 该题的设计思路
使用数组(或者哈希表)记录每个节点的颜色: color[node]。颜色可以是 0, 1,或者未着色.搜索节点,考虑图是非连通的情况。对每个未着色节点,从该节点开始深度优先搜索着色。每个邻接点都可以通过当前节点着相反的颜色。如果存在当前点和邻接点颜色相同,则着色失败。使用栈完成深度优先搜索,栈类似于节点的 “todo list”,存储着下一个要访问节点的顺序。在 graph[node] 中,对每个未着色邻接点,着色该节点并将其放入到栈中。
2.1.3 运行结果
2.1.4分析该题目解题优势及难点。
我想本题最大的难度在于非连通图的判定,该解法借助栈对每个需要着色的点进行记录,然后再一一进行遍历,所以本题解最大的优势就是结合了栈对需要进行保存,然后先进后出的遍历。