@P2Oileen
2017-08-06T06:19:32.000000Z
字数 5465
阅读 1907
标★的题目为选做题,仅供学有余力的同学尝试,并且评分标准也更严格。请先做完不标★的题目,再尝试做标一个★的题目,再尝试做更多★的题目。
本次作业总分:64+3.2
我们来介绍一个存储有向图的方法(如果要存储无向图,一般情况下可以把每条边存成两条边,这里不详细介绍了)。
我们用类似邻接表的思想来存储,但并不直接套用vector
或list
。假定每条边上有一个权值,那么我们可以用这样的方法表示一条边:
struct Edge
{
Edge *pre; // 在表中的上一条边
int to; // 边的目标点
Weight weight; // 边的权值,我们不太关心具体是什么类型
};
假设图的点用[0, n)
的整数编号,边用[0, m)
的整数编号,n
与m
均小于MAXN
与MAXM
,则我们只需要用两个数组描述这张图:
Edge edge[MAXM]; // 存储每条边的信息
Edge *first[MAXN]; // 存储每个点的表尾是哪条边(可以为NULL)
int edgeCnt; // 记录目前一共有多少条边
初始化的过程是比较显然的:
void init()
{
edgeCnt = 0;
memset(first, NULL, sizeof(first));
}
完成这个“遍历”的代码:
void traverse(int u)
{
// 输出从u出发的所有的边信息
for(Edge *cur = first[u]; ___①___; cur = cur->pre)
cout << "Found edge from " << u
<< " to " << cur->to
<< " weight " << cur->weight
<< endl;
// 这个是按照被添加顺序的逆序输出的
}
cur!=NULL
完成这个“插入”的代码:
void addEdge(int u, int v, Weight w)
{
Edge *cur = &edge[edgeCnt++];
cur->to = ___②___;
cur->weight = ___③___;
cur->pre = ___④___;
___⑤___ = ___⑥___;
}
v
w
first[u]
first[u]
cur
完成这个"查找"的代码:
bool findEdge(int u, int v, Weight &w)
{
// 找是否存在从u到v的边
// 保证在调用该函数时最多有一条符合条件的边
// 如果有,在w中存下其权值,并返回true
// 如果没有,不要改w,并返回false
for(___⑦___; ___⑧___; ___⑨___)
if(___⑩___)
{
w = ___⑾___;
return true;
}
return false;
}
Edge* cur=first[u]
cur!=NULL
cur=cur->pre
cur->to==v
cur->weight
完成这个"删除"的代码:
void removeEdge(int u, int v)
{
// 删除从u到v的所有边
___⑿___;
for(___⒀___; ___⒁___; ___⒂___)
if(___⒃___)
{
___⒄___;
___⒅___;
}
}
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)
//这个是我瞎猜的
对于以下这些存储有向图的方法:
vector
实现的邻接表,每个vector
的元素无序//2vector
实现的邻接表,每个vector
的元素有序//3list
实现的邻接表,每个list
的元素无序//4list
实现的邻接表,每个list
的元素有序//5set
实现的邻接表//6设图有个点,条边,写出下列复杂度。除了空间复杂度以外,如果是均摊,则必须注明这个复杂度是均摊复杂度,同时注明此时的最坏复杂度。
removeEdge
函数为准)类型\复杂度 | 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个【完全二叉树】
对于一个真二叉树,假设有k个叶子,那么有n-k个点不是叶子。其中有n-k-1个点满足:自己不是叶子,也是某个点的儿子。n-k个点不是叶子,但是为了满足真二叉树性质,需要有2*n-2*k个儿子。总共的儿子的数量=n-k-1+k=2n-2k ,得到k=(n+1)/2.
请你证明这一点。你可以分别证明“1→2”“2→3”“3→1”,或分别证明“1→3”“3→2”“2→1”,或用你喜欢的方法证明。
下面这段代码试着在某个有向图上进行BFS。它从某个起点出发开始搜索,按照搜索的顺序依次输出每个点及其到起点的距离。
int dist[MAXN];
bool visit[MAXN];
queue<int> q;
q.push(起点);
dist[起点] = 0;
memset(visit, false, sizeof(visit));
while(q.size())
{
int u = q.top(); q.pop();
//visit[u] = true;
输出:搜索到了点u,其距离为dist[u]
for(u出发的每一条边的另一个点v)
if(!visit[v])
{
dist[v] = dist[u] + 1;
q.push(v);
//visit[v]=1;
}
}
1 2
2 1
1 1
1 2
2 3
2 4
1 5
5 6
5 7
以下程序试图输出序列[1, 2, 3, ..., n]
的全排列。请将其补全。
bool visit[MAXN];
int a[MAXN];
int n;
void dfs(int x)
{
if(x > n)
{
for(int i = 1; i <= n; i++)
cout << a[i] << " \n"[i == n];
}
else
{
for(int i = 1; i <= n; i++)
if(!visit[i])
{
___①___;//visit[i]=1
a[x] = i;
dfs(x + 1);
___②___;//visit[i]=0
}
}
}
int main()
{
memset(visit, false, sizeof(visit));
cin >> n;
dfs(___③___);//1
return 0;
}
在通常的有关树的题目当中,输入数据格式往往只像一般的无向图一样,只给出“谁和谁之间有边”的信息,而不告诉你点之间的父子关系。
本题中我们要做的就是用DFS的方法求出父子关系来。
设输入的树有n
个点,用[1, n]
这些正整数编号。
假设输入格式为n - 1
行,每行两个整数表示一条边。先将这些边存到一个邻接表中:
vector<int> adj[MAXN];
主程序中调用:
for(int i = 1; i < n; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
___①___adj[u].push_back(v);adj[v].push_back(u);
}
然后我们用DFS求出父亲。令点1
为根,并规定其父亲为0
:
int father[MAXN]; // 记录每个点的父亲
void dfs(int u, int fa)
{
// 当前搜索u的子树,且u的父亲为fa
father[x] = fa;
for(adj[u]中的每个元素v)
if(___②___)//v!=fa
dfs(___③___, ___④___);//v //u
}
主程序中调用:
dfs(1, 0);
最后,如果有需要的话,建立每个点的儿子列表:
vector<int> childs[MAXN];
主程序中调用:
for(int i = 1; i <= n; i++)
___⑤___;//for(adj[u]中的每个元素v) childs[u].push_back(v);
某同学希望写个程序判断一个n
个点的无向图是否是连通图。首先,他写了一个DFS程序:
vector<bool> visited;
void dfs(int x)
{
// 假设所有的点标号为[0, n)的整数
if(visited[x])
return;
visited[x] = true;
for(每个与x相邻的点y)
dfs(y);
}
他想到了两种方法判断。第一种是:
bool connected1()
{
visited.resize(n);
for(int i = 0; i < n; i++)
visited[i] = false;
dfs(0);
for(int i = 0; i < n; i++)
if(!visited[i])
return false;
return true;
}
然而,他之前写了另一个程序,用来计算一个图的连通分支数:
int components()
{
visited.resize(n);
for(int i = 0; i < n; i++)
visited[i] = false;
int ans = 0;
for(int i = 0; i < n; i++)
if(!visited[i])
{
dfs(i);
++ans;
}
return ans;
}
所以,他的第二种方法就是:
bool connected2()
{
return components() == 1;
}
现在,他希望你判断这两种方法是否等价。假定输入的n
不太大,最多不会超过几万的级别。
你需要做以下几件事情的任何一个就算完成本题:
对于个点的二分图:
n-1
(n/2)^(n/2)
对于一个树来说,两点之间只有一条路径,所以一定没有环。
给你一个有向图,有个点和条边。
下面假定方案存在。
下面如果要求方案数,你可以假定只需对某个不超过计算机字长的质数取模。
给你一个有向无环图,边上带权(为整数),和一个特殊点作为起点。
Written with StackEdit.