图论的两个经典问题。
1、先介绍树的概念:
树的概念挺简单的,一个祖先,一个儿子只能有一个父亲节点,不能形成环。n个节点只能有n-1条边,要不然会形成环。(易得知)
2、再来讲讲我用来存图的两种方式:
1、邻接矩阵(使用于稠密图)
map[b][a] = map[a][b] = c;代表的意思是a到b的距离为c.
如图(网上找的图):
2、邻接表(适用于稀疏图)
struct Edge {///数组的下标代表边的另一个端点
int v; //边端点,另一端点已知
int w; //边权值
Edge(int v_ = 0, int w_ = INFINITE): v(v_), w(w_) { }
};
vector< vector > G(110); //图的邻接表
如图(网上找的图):
开始进入正题。(有些题目我会用两种存图方式写,有些只能用其中一种)
一、最小生成树
1、概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
我们先介绍prim算法,然后再介绍kruskal算法。
prim算法的思路很简单。就是从一个起点开始进行连通始终寻找没有访问过且最小的边来进行连通。
POJ:1258 http://poj.org/problem?id=1258
题意很简单就是根据n个点求一颗最小生成树。
prim代码如下:
#include
#include
#include
#define maxn 100 + 15
#define inf 200000005
int dis[maxn], vis[maxn];
int map[maxn][maxn];
int n, sum;
using namespace std;
void prim() {
memset(vis, 0, sizeof(vis));
for(int i = 0; i < n; ++i) dis[i] = map[i][0];
vis[0] = 1;
dis[0] = 0;
for(int i = 0; i < n - 1; ++i) { ///n-1条边
int k, temp = inf; ///temp用来找最小的边,k存储最小边的对应的点
for(int j = 0; j < n; ++j) {
if(!vis[j] && dis[j] < temp) temp = dis[j], k = j;
}
vis[k] = 1; ///标记找到的点
sum += dis[k]; ///最小边加入到最小生成树里面
for(int j = 0;j map[k][j]) dis[j] = map[k][j]; ///存储小的
}
cout << sum << endl;
}
int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j) {
int value;
scanf("%d", &value);
map[i][j] = value; ///邻接矩阵存图
}
sum = 0;
prim();
}
return 0;
}
kruskal算法的思路:把边按从小到大排序,然后运用并查集的知识按照边的两个端点进行合并,判断是不是同一个连通分量如果不是进行合并,如果是的话则跳过。读到n-1条边合并之后则是最小生成树。
代码如下:(POJ1258)
#include
#include
#include
#include
#include
#define maxn 100 + 15
int father[maxn];
using namespace std;
///邻接矩阵存图
struct Edge {
int s, e, v;
Edge(int ss, int ee, int vv): s(ss), e(ee), v(vv) {}
Edge() {}
};
///按边从大到小排序
bool cmp(Edge a,Edge b)
{
return a.v < b.v;
}
vectorvp;
///并查集
int Find(int x) {
if(x == father[x]) return x;
else return father[x] = Find(father[x]);
}
void Union(int a, int b) {
int aa = Find(a);
int bb = Find(b);
if(aa == bb) return ;
else father[bb] = aa;
}
void Clear(int n) {
for(int i = 0; i < n; ++i) father[i] = i;
}
int main() {
int n;
while(scanf("%d", &n) != EOF) {
Clear(n);
vp.clear();
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j) {
int value;
scanf("%d", &value);
vp.push_back(Edge(i,j,value));
}
sort(vp.begin(),vp.end(),cmp); ///按边排序
int sum = 0;
int num = 0;
for(int i = 0; i < vp.size(); ++i) {
int a = vp[i].s, b = vp[i].e, c = vp[i].v;
if(Find(a) != Find(b)) { ///是不是同一个祖先
num++;
sum += c;
Union(a, b); ///合并成统一个连通分量
}
if(num == n - 1) break; ///找到n-1条边,数已经生成,退出。
}
cout << sum << endl;
}
return 0;
}
1、概念:若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。
这里我介绍两种个算法。floyd,dijkstra
HDU2544 http://acm.hdu.edu.cn/showproblem.php?pid=2544
裸裸的最短路。
2、dijkstra代码:
#include
#include
#include
#define MIN(a,b) a>b?b:a
#define maxn 201
#define INF 200000005
int map[maxn][maxn];
int dis[maxn];
bool vis[maxn];
using namespace std;
int main() {
int n, m;
while(scanf("%d%d", &n, &m) != EOF) {
if(n == 0 && m == 0)
break;
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++)
if(i == j)
map[i][j] = 0; ///自己到自己的距离为0
else
map[i][j] = INF; ///初始到其他点的距离为无穷大
}
for(int i = 1; i <= m; i++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
map[a][b] = map[b][a] = c; ///双向距离相等
}
for(int i = 1; i <= n; i++)
dis[i] = map[1][i];
vis[1] = true;
for(int i = 1; i < n; i++) {
int temp = INF;
int k;
for(int j = 1; j <= n; j++) {
if(vis[j]) continue;
if(temp > dis[j]) {
temp = dis[j];
k = j;
}
}
vis[k] = true;
for(int j = 1; j <= n; j++) {
if(vis[j]) continue;
dis[j] = MIN(dis[j], dis[k] + map[k][j]);
}
}
cout << dis[n] << endl;
}
return 0;
}
3、floyd算法(更新了所有点的最短路时间复杂度为O(n^3)):
///HDU 2544.
#include
#include
#include
#define maxn 100 + 15
#define inf 100000005
int a[maxn][maxn], dis[maxn], vis[maxn];
int n, m;
using namespace std;
int main() {
while(scanf("%d%d", &n, &m) != EOF && (n && m)) {
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++) {
a[i][j] = 0;
if(i != j) a[i][j] = inf;
}
for(int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
///更新最小值
if(a[x][y] > z) a[x][y] = a[y][x] = z;
}
///更新了所有点的最短路
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; ++j) {
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
}
printf("%d\n", a[1][n]);
}
return 0;
}
POJ3159
代码:
#include
#include
#include
#include
#include
#include
#define maxn 30000 + 5
#define inf 200000005
int vis[maxn];
int n, m;
using namespace std;
///邻接表存图
struct edge {
int e, cc; ///本身自带一个节点,e代表另一个节点 v代表权值
};
bool operator < (const edge &a, const edge &b) {
return a.cc > b.cc;
}
///优先队列
priority_queuepq;
///邻接表
vector< vector >v;
int main() {
while(scanf("%d%d", &n, &m) != EOF) {
edge d, z;
v.clear();
v.resize(n + 1);
for(int i = 0; i < m; ++i) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d.e = b;
d.cc = c;
v[a].push_back(d);///邻接表存图
}
memset(vis,0,sizeof(vis));
d.e = 1;
d.cc = 0;
pq.push(d);
while(!pq.empty()) {
d = pq.top();///不能使用pq.front();
pq.pop();
if(vis[d.e]) continue; ///表示已经走过
vis[d.e] = 1;
if(d.e == n) break; ///找到该点
for(int i = 0 ; i < v[d.e].size(); ++i) {
z.e = v[d.e][i].e;
if(vis[z.e]) continue; ///邻接表里的点有没有访问过
z.cc = v[d.e][i].cc + d.cc;
pq.push(z);
}
}
cout << d.cc << endl;
}
return 0;
}