数据结构 图(Part I)

啊一堆算法我真的会谢

一、图的深搜、广搜(邻接矩阵)

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了啊啊啊啊啊啊啊,这谁看了不说一句像啊

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值