[关闭]
@P2Oileen 2017-08-06T06:19:32.000000Z 字数 5465 阅读 1734

Homework 4

标★的题目为选做题,仅供学有余力的同学尝试,并且评分标准也更严格。请先做完不标★的题目,再尝试做标一个★的题目,再尝试做更多★的题目。

本次作业总分:64+3.2



“链式前向星”(11.7分)

我们来介绍一个存储有向图的方法(如果要存储无向图,一般情况下可以把每条边存成两条边,这里不详细介绍了)。

我们用类似邻接表的思想来存储,但并不直接套用vectorlist。假定每条边上有一个权值,那么我们可以用这样的方法表示一条边:

  1. struct Edge
  2. {
  3. Edge *pre; // 在表中的上一条边
  4. int to; // 边的目标点
  5. Weight weight; // 边的权值,我们不太关心具体是什么类型
  6. };

假设图的点用[0, n)的整数编号,边用[0, m)的整数编号,nm均小于MAXNMAXM,则我们只需要用两个数组描述这张图:

  1. Edge edge[MAXM]; // 存储每条边的信息
  2. Edge *first[MAXN]; // 存储每个点的表尾是哪条边(可以为NULL)
  3. int edgeCnt; // 记录目前一共有多少条边

初始化的过程是比较显然的:

  1. void init()
  2. {
  3. edgeCnt = 0;
  4. memset(first, NULL, sizeof(first));
  5. }

(1)

完成这个“遍历”的代码:

  1. void traverse(int u)
  2. {
  3. // 输出从u出发的所有的边信息
  4. for(Edge *cur = first[u]; ______; cur = cur->pre)
  5. cout << "Found edge from " << u
  6. << " to " << cur->to
  7. << " weight " << cur->weight
  8. << endl;
  9. // 这个是按照被添加顺序的逆序输出的
  10. }

cur!=NULL

(2)

完成这个“插入”的代码:

  1. void addEdge(int u, int v, Weight w)
  2. {
  3. Edge *cur = &edge[edgeCnt++];
  4. cur->to = ______;
  5. cur->weight = ______;
  6. cur->pre = ______;
  7. ______ = ______;
  8. }

v
w
first[u]
first[u]
cur

(3)

完成这个"查找"的代码:

  1. bool findEdge(int u, int v, Weight &w)
  2. {
  3. // 找是否存在从u到v的边
  4. // 保证在调用该函数时最多有一条符合条件的边
  5. // 如果有,在w中存下其权值,并返回true
  6. // 如果没有,不要改w,并返回false
  7. for(______; ______; ______)
  8. if(______)
  9. {
  10. w = ______;
  11. return true;
  12. }
  13. return false;
  14. }

Edge* cur=first[u]
cur!=NULL
cur=cur->pre
cur->to==v
cur->weight

★(4)

完成这个"删除"的代码:

  1. void removeEdge(int u, int v)
  2. {
  3. // 删除从u到v的所有边
  4. ______;
  5. for(______; ______; ______)
  6. if(______)
  7. {
  8. ______;
  9. ______;
  10. }
  11. }

if(first[u]->to==v) {first[u]=first[u]->pre; return;}
Edge* cur=first[u]
cur!=NULL
cur=cur->pre
cur->pre->to==v
cur->pre=cur->pre->pre;
removeEdge(v,u)//这个是我瞎猜的


复杂度(21.5分)

对于以下这些存储有向图的方法:

设图有个点,条边,写出下列复杂度。除了空间复杂度以外,如果是均摊,则必须注明这个复杂度是均摊复杂度,同时注明此时的最坏复杂度。

  1. 空间复杂度
  2. 添加一条边的时间复杂度(假设边的起点当前的入度为
  3. 查找一条边的时间复杂度(同上)
  4. 删除一条边的时间复杂度(假设这条边已被找到,同上;但对于“链式前向星”,以上述removeEdge函数为准)
  5. 列出从某个点出发的所有边的时间复杂度(同上)
类型\复杂度 1 2 3 4 5
1 n^2 O(1) O(1) O(1) O(n)
2 n+m O(1) O(k) O(1) O(k)
3 n+mlogm O(klogk) O(k) O(1) O(k)
4 n+m O(1) O(k) O(1) O(k)
5 n+mlogm O(klogk) O(k) O(1) O(k)
6 n+m O(logk) O(logk) O(logk) O(klogk)
7 n+m O(1) O(k) O(1) O(k)

子图计数(4分)

对于一个个点,条边的无向简单图:

  1. 有多少个生成子图? 2^m
  2. 有多少个导出子图?
  3. 有多少个边导出子图?
  4. 给出一个时间复杂度为的算法,计算其有多少个子图。


时间复杂度(3.1分)

某同学设计了一个算法,这个算法接受一棵树作为输入,其运行时间是所有点的度数的次方之和。对于以下的值,分别求这个算法的最坏时间复杂度,并给出一个例子(无需证明)。


二叉树的叶子(6分)

  1. 对于8个点的二叉树,求其最少与最多的叶子个数,并分别举出一个例子。
    最少:一个【退化成链】 最多:4个【完全二叉树】
  2. 证明所有个点的真二叉树的叶子个数都相同,并求出这个叶子个数。

    对于一个真二叉树,假设有k个叶子,那么有n-k个点不是叶子。其中有n-k-1个点满足:自己不是叶子,也是某个点的儿子。n-k个点不是叶子,但是为了满足真二叉树性质,需要有2*n-2*k个儿子。总共的儿子的数量=n-k-1+k=2n-2k ,得到k=(n+1)/2.


★★无聊的证明题(0.3分)

  1. n个结点,n-1条边的连通无向图
  2. 无向无环连通图
  3. 任意两个结点之间有且仅有一条简单路径的无向图

请你证明这一点。你可以分别证明“1→2”“2→3”“3→1”,或分别证明“1→3”“3→2”“2→1”,或用你喜欢的方法证明。


BFS


阅读理解(3分)

下面这段代码试着在某个有向图上进行BFS。它从某个起点出发开始搜索,按照搜索的顺序依次输出每个点及其到起点的距离。

  1. int dist[MAXN];
  2. bool visit[MAXN];
  3. queue<int> q;
  4. q.push(起点);
  5. dist[起点] = 0;
  6. memset(visit, false, sizeof(visit));
  7. while(q.size())
  8. {
  9. int u = q.top(); q.pop();
  10. //visit[u] = true;
  11. 输出:搜索到了点u,其距离为dist[u]
  12. for(u出发的每一条边的另一个点v)
  13. if(!visit[v])
  14. {
  15. dist[v] = dist[u] + 1;
  16. q.push(v);
  17. //visit[v]=1;
  18. }
  19. }
  1. 给出一个图,使得上述代码不正确(包括但不限于:死循环、运行错误、输出距离错误、多次输出同一个点等)。
    1 2
    2 1
    1 1
  2. 改正上述代码。
  3. 举出一个有向图的例子,使得在正确的BFS下,虽然每个点到起点的距离是唯一确定的,但是输出所有点的顺序(即BFS序列)会随着每个点遍历相邻点的顺序的变化而发生变化。
    1 2
    2 3
    2 4
    1 5
    5 6
    5 7

DFS


全排列(3分)

以下程序试图输出序列[1, 2, 3, ..., n]的全排列。请将其补全。

  1. bool visit[MAXN];
  2. int a[MAXN];
  3. int n;
  4. void dfs(int x)
  5. {
  6. if(x > n)
  7. {
  8. for(int i = 1; i <= n; i++)
  9. cout << a[i] << " \n"[i == n];
  10. }
  11. else
  12. {
  13. for(int i = 1; i <= n; i++)
  14. if(!visit[i])
  15. {
  16. ______;//visit[i]=1
  17. a[x] = i;
  18. dfs(x + 1);
  19. ______;//visit[i]=0
  20. }
  21. }
  22. }
  23. int main()
  24. {
  25. memset(visit, false, sizeof(visit));
  26. cin >> n;
  27. dfs(______);//1
  28. return 0;
  29. }

找父亲(5分)

在通常的有关树的题目当中,输入数据格式往往只像一般的无向图一样,只给出“谁和谁之间有边”的信息,而不告诉你点之间的父子关系。

本题中我们要做的就是用DFS的方法求出父子关系来。

设输入的树有n个点,用[1, n]这些正整数编号。

(1)

假设输入格式为n - 1行,每行两个整数表示一条边。先将这些边存到一个邻接表中:

  1. vector<int> adj[MAXN];
  2. 主程序中调用:
  3. for(int i = 1; i < n; ++i)
  4. {
  5. int u, v;
  6. scanf("%d%d", &u, &v);
  7. ______adj[u].push_back(v);adj[v].push_back(u);
  8. }

(2)

然后我们用DFS求出父亲。令点1为根,并规定其父亲为0

  1. int father[MAXN]; // 记录每个点的父亲
  2. void dfs(int u, int fa)
  3. {
  4. // 当前搜索u的子树,且u的父亲为fa
  5. father[x] = fa;
  6. for(adj[u]中的每个元素v)
  7. if(______)//v!=fa
  8. dfs(______, ______);//v //u
  9. }
  10. 主程序中调用:
  11. dfs(1, 0);

(3)

最后,如果有需要的话,建立每个点的儿子列表:

  1. vector<int> childs[MAXN];
  2. 主程序中调用:
  3. for(int i = 1; i <= n; i++)
  4. ______;//for(adj[u]中的每个元素v) childs[u].push_back(v);

★两个方法等价吗?(0.1分)

某同学希望写个程序判断一个n个点的无向图是否是连通图。首先,他写了一个DFS程序:

  1. vector<bool> visited;
  2. void dfs(int x)
  3. {
  4. // 假设所有的点标号为[0, n)的整数
  5. if(visited[x])
  6. return;
  7. visited[x] = true;
  8. for(每个与x相邻的点y)
  9. dfs(y);
  10. }

他想到了两种方法判断。第一种是:

  1. bool connected1()
  2. {
  3. visited.resize(n);
  4. for(int i = 0; i < n; i++)
  5. visited[i] = false;
  6. dfs(0);
  7. for(int i = 0; i < n; i++)
  8. if(!visited[i])
  9. return false;
  10. return true;
  11. }

然而,他之前写了另一个程序,用来计算一个图的连通分支数:

  1. int components()
  2. {
  3. visited.resize(n);
  4. for(int i = 0; i < n; i++)
  5. visited[i] = false;
  6. int ans = 0;
  7. for(int i = 0; i < n; i++)
  8. if(!visited[i])
  9. {
  10. dfs(i);
  11. ++ans;
  12. }
  13. return ans;
  14. }

所以,他的第二种方法就是:

  1. bool connected2()
  2. {
  3. return components() == 1;
  4. }

现在,他希望你判断这两种方法是否等价。假定输入的n不太大,最多不会超过几万的级别。

你需要做以下几件事情的任何一个就算完成本题:


二分图


二分图(4分)

对于个点的二分图:

  1. 最少有多少条边?n-1
  2. 最多有多少条边?(n/2)^(n/2)
  3. 如果给每个点染色,使得任意一条边连接的两个点的颜色都不同,有多少种方案?
  4. 证明个点的树一定是二分图。
    对于一个树来说,两点之间只有一条路径,所以一定没有环。

拓扑排序与有向无环图


拓扑排序及方案(4.2分)

给你一个有向图,有个点和条边。

下面假定方案存在。

下面如果要求方案数,你可以假定只需对某个不超过计算机字长的质数取模。


有向无环图上的单源最短路(1.3分)

给你一个有向无环图,边上带权(为整数),和一个特殊点作为起点。

  1. 假设所有的边权均为1,简要描述一个算法,能在的时间内求出起点到每个点的最短路长度。
    /dijkstra算法:每次将当前点所连接的所有点入队,更新到新点的最短路径,最后得到终点最短路/
  2. ★不对边权做任何假设(可能为负数),简要描述一个算法,能在的时间内求出起点到每个点的最短路长度(提示:可先拓扑排序,当然这不是必要的)。
  3. ★不对边权做任何假设,并另外给出一个特殊点作为终点,简要描述一个算法,能在的时间内求出起点到终点的最短路长度,并输出一条可行的路径。
  4. ★★不对边权做任何假设,并另外给出一个特殊点作为终点,简要描述一个算法,能在的时间内求出起点到终点的最短路长度,并输出最短路的条数。

Written with StackEdit.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注