啊一堆算法我真的会谢
一、图的深搜、广搜(邻接矩阵)
1.dfs
其中arr代表邻接矩阵,walked用来标记有无走过
void dfs(int x) {
cout << x << " ";
walked[x] = 1;
for (int i = 0; i < n; i++) {
if (arr[x][i] && !walked[i]) {
dfs(i);
}
}
}
2.bfs
void bfs(){
queue<int> q;
q.push(0);
walked[0]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<n;i++)
if(arr[x][i]&&!walked[i]) {
walked[i]=1;
q.push(i);
}
cout<<x<<" ";
}
}
二、邻接表的建立(链式前向星!)
一种神奇的头插法
如果是有权图就有weight,并且u的下标是从1开始的
#define MAXN 100
struct EDGE {
int to, next, weight;
} edge[MAXN];
int cnt = 0, head[MAXN];
void add_edge(int u, int v, int w) {
cnt++;
edge[cnt].weight = w;
edge[cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
然后就是对其进行操作:
for (int i = head[x]; i; i = edge[i].next) {
int v = edge[i].to;
...//遍历
当然也是也可以实现dfs和bfs的,就不写啦
三、图的连通分量
图的连通分量与图的极大连通子图一样
1.可以用并查集来判断图有几个连通分量
int father[MAXN];
int find(const int& x) {
if(father[x] == x) return x;
return father[x] = find(father[x]);
} //边查找边路径压缩
int main(){
for(int i=1; i<=n; i++) {
father[i] = i;
}//初始化
for(...){
int x = find(...);
int y = find(...);
if(x == y) continue;
father[y] = x;//当然,father[x] = y也一样啦
}
for(i=0;...){
if(father[i]==i) cnt++;
}
}
//合并两个集合,不过在这个题里可以不用
void merge(const int& a, const int& b) {
father[find(a)]=find(b);
}
以上的思路就是,如果两顶点连通,并且他们不在一个集合里,那就让他们的祖先指向另一个祖先,最后再看看有几个集合就可以了
ps:并查集好哇,后面的Kruskal算法也要用到,理解后就不难了ヾ(◍°∇°◍)ノ゙
2.也可以用dfs/bfs(一共进行了几次dfs/bfs)
for(int i=0;i<n;i++){
if(!walked[i]) {
dfs(i);
cnt++;
}
}
四、最小生成树
是一种极小连通子图,无环,一定是无向图!无向图!无向图!
当边权不同的时候是唯一的,边权相同当然可能不唯一。
1. Kruscal算法
贪心选择边。
并且不能形成回路,适合稀疏图
时间复杂度为O(eloge) e为边数 ps:但是代码看起来像O(e)啊!不理解...
struct EDGE {
int from, to, w;
} edge[MAX];
void add_edge(int f, int t, int we) {
cnt++;
edge[cnt].from = f, edge[cnt].to = t, edge[cnt].w = we;
}//这只是存储边,不是链式前向星!!
int find(const int &x) {
if (fa[x] == x)
return x;
return fa[x]=find(fa[x]);
}
bool cmp(EDGE a, EDGE b) {
if (a.w != b.w)
return a.w < b.w;
else if (a.from != b.from)
return a.from < b.from;
else
return a.to < b.to;
}
int kruskal() {
for (int i = 1; i <= n; i++) fa[i] = i; //n为顶点数,cnt为边的数目
sort(edge + 1, edge + 1 + cnt, cmp); //把边按权重的升序排列
int sum = 0;
for (int i = 1; i <= cnt; i++) {
int x = find(edge[i].from);
int y = find(edge[i].to);
if (x == y)
continue;
fa[x] = y;
sum += edge[i].w;
//如果要写出树的生成过程,把edge[i].from和.to push_back进答案里即可
}
return sum;
}
2.Prim算法
选择点。
在当前已选的点的补集中,选一条权重最小的边将其连接起来,并且不能成环。
时间复杂度为O(n) n为顶点的数目 ps:实际看起来也不像啊??这得n^2了吧……唉,还是按书本上来背吧
int prim(const int& x) {
memset(walked,0,sizeof(walked));
memset(dist,0x3f3f3f3f,sizeof(dist));
dist[x]=0;//自己到自己的距离是0,从x这个结点开始生成树
int sum=0;
for(int i=1; i<=n; i++) {
int current=n+1;//下一个节点
for(int j=1; j<=n; j++)
if( !walked[j] && dist[j]<dist[current]) current=j;
if(dist[current]==0x3f3f3f3f) return ERROR;//提前终止
sum+=dist[current];
walked[current]=true;
if(current!=x) cout<<pre[current]<<" "<<current<<endl; //输出生成树的过程
for(int k=1; k<=n; k++) {
//只更新还没有找到的最小权值
if(!walked[k]) {
if(dist[k]>connect[current][k]) {
dist[k]=connect[current][k];
pre[k] = current; //pre[k]用来记录点k是从current(现在的点)过来的
}
}
}
}
return sum;
}
这也太像Dijkstra了啊啊啊啊啊啊啊,这谁看了不说一句像啊