# 一、综述
图的表示方法通常有两种,即邻接表表示法和邻接矩阵表示法。这两种方法都可以表示有向图和无向图
### 1.邻接表表示法
(1)用邻接表表示无向图
![](https://box.kancloud.cn/2016-02-02_56b02bd2b45ad.gif)
![](https://box.kancloud.cn/2016-02-02_56b02bd2c017c.gif)
(2)用邻接表表示有向图
![](https://box.kancloud.cn/2016-02-02_56b02bd2ce610.gif)
![](https://box.kancloud.cn/2016-02-02_56b02bd2dab30.gif)
(3)邻接表的优点及适用场合
使用邻接表表示稀疏图比较紧凑
### 2.邻接矩阵表示法
(1)用邻接矩阵表示无向图
![](https://box.kancloud.cn/2016-02-02_56b02bd2b45ad.gif)
![](https://box.kancloud.cn/2016-02-02_56b02bd2ead45.gif)
(2)用邻接矩阵表示有向图
![](https://box.kancloud.cn/2016-02-02_56b02bd2ce610.gif)
![](https://box.kancloud.cn/2016-02-02_56b02bd308de2.gif)
(3)邻接矩阵的优点与适用场合
用邻接矩阵表示稠密图可以很快地判别两个给定顶点是否存在邻接边
# 二、代码
### 1.邻接表表示法 "Link_Graph.h"
~~~
#include <iostream>
using namespace std;
#define N 100
struct Vertex;
struct Edge
{
int start;
int end;
int value;
Edge *next;
Edge(int s, int e, int v):start(s),end(e),value(v),next(NULL){}
};
struct Vertex
{
Edge *head;
Vertex():head(NULL){};
};
class Link_Graph
{
public:
int n;
Vertex *V;
Link_Graph(int num):n(num)
{
V = new Vertex[n+1];
}
~Link_Graph(){delete []V;}
void AddSingleEdge(int start, int end, int value = 1)
{
Edge *NewEdge = new Edge(start, end, value);
if(V[start].head == NULL || V[start].head->end > end)
{
NewEdge->next = V[start].head;
V[start].head = NewEdge;
}
else
{
Edge *e = V[start].head, *pre = e;
while(e != NULL && e->end < end)
{
pre = e;
e = e->next;
}
if(e && e->end == end)
{
delete NewEdge;
return;
}
NewEdge->next = e;
pre->next = NewEdge;
}
}
void AddDoubleEdge(int a, int b, int value = 1)
{
AddSingleEdge(a, b, value);
AddSingleEdge(b, a, value);
}
void DeleteSingleEdge(int start, int end)
{
Edge *e = V[start].head, *pre = e;
while(e && e->end < end)
{
pre = e;
e = e->next;
}
if(e == NULL || e->end > end) return;
if(e == V[start].head)
V[start].head = e->next;
else
pre->next = e->next;
delete e;
}
void DeleteDoubleEdge(int a, int b)
{
DeleteSingleEdge(a, b);
DeleteSingleEdge(b, a);
}
//22.1-1
void OutDegree();
void InDegree();
//22.1-2
void Print();
//22.1-3
Link_Graph* Reverse();
//22.1-5
Link_Graph* Square();
};
void Link_Graph::OutDegree()
{
int i, cnt = 0;
Edge *e;
for(i = 1; i <= n; i++)
{
cnt = 0;
e = V[i].head;
while(e)
{
cnt++;
e = e->next;
}
cout<<"顶点"<<i<<"的出度为"<<cnt<<endl;
}
}
void Link_Graph::InDegree()
{
int ret[N+1] = {0}, i;
Edge *e;
for(i = 1; i <= n; i++)
{
e = V[i].head;
while(e)
{
ret[e->end]++;
e = e->next;
}
}
for(i = 1; i <= n; i++)
cout<<"顶点"<<i<<"的入度为"<<ret[i]<<endl;
}
void Link_Graph::Print()
{
int i;
Edge *e;
for(i = 1; i <= n; i++)
{
cout<<i;
e = V[i].head;
while(e)
{
cout<<"->"<<e->end;
e = e->next;
}
cout<<endl;
}
}
Link_Graph* Link_Graph::Reverse()
{
Link_Graph *ret = new Link_Graph(n);
int i;
//遍历图G中的每一条边,以终点为起点,以起点为终点,加入到新图RET中
for(i = 1; i <= n; i++)
{
Edge *e = V[i].head;
while(e)
{
ret->AddSingleEdge(e->end, e->start);
e = e->next;
}
}
return ret;
}
Link_Graph* Link_Graph::Square()
{
int i;
Link_Graph *ret = new Link_Graph(n);
//遍历图中每个顶点
for(i = 1; i <= n; i++)
{
//遍历以该顶点为起点的边
Edge *e = V[i].head, *e2;
while(e)
{
//把原来有的边加入到新图中
ret->AddSingleEdge(e->start, e->end);
//把以E的终点为起点的边加入新图中
e2 = V[e->end].head;
while(e2)
{
ret->AddSingleEdge(e->start, e2->end);
e2 = e2->next;
}
e = e->next;
}
}
return ret;
}
~~~
### 2.邻接矩阵表示法
~~~
#include <iostream>
using namespace std;
#define N 100
class Mat_Graph
{
public:
int n;
int Map[N+1][N+1];
Mat_Graph(int num):n(num)
{
memset(Map, 0, sizeof(Map));
}
void AddDoubleEdge(int a, int b, int value = 1)
{
AddSingleEdge(a, b, value);
AddSingleEdge(b, a, value);
}
void AddSingleEdge(int start, int end, int value = 1)
{
Map[start][end] = value;
}
void DeleteDoubleEdge(int a, int b)
{
DeleteSingleEdge(a, b);
DeleteSingleEdge(b, a);
}
void DeleteSingleEdge(int start, int end)
{
Map[start][end] = 0;
}
//22.1-2
void Print();
//22.1-3
Mat_Graph* Reverse();
//22.1-5
Mat_Graph* Square();
//22.1-6
bool Universal_Sink();
};
void Mat_Graph::Print()
{
int i, j;
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
cout<<Map[i][j]<<' ';
cout<<endl;
}
}
Mat_Graph* Mat_Graph::Reverse()
{
int i, j;
Mat_Graph *ret = new Mat_Graph(n);
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
if(Map[i][j])
ret->AddSingleEdge(j, i);
}
return ret;
}
Mat_Graph* Mat_Graph::Square()
{
int i, j, k;
Mat_Graph *ret = new Mat_Graph(n);
//遍历图中每个顶点
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
{
if(Map[i][j])
{
//把原来有的边加入到新图中
ret->AddSingleEdge(i, j);
//把以E的终点为起点的边加入新图中
for(k = 1; k <= n; k++)
if(Map[j][k])
ret->AddSingleEdge(i, k);
}
}
}
return ret;
}
bool Mat_Graph::Universal_Sink()
{
//i指向有可能是汇的编号最小的点
int i = 1, j = 1;
while(j <= n)
{
//map[i][j] = 0,那么j没有i的入,一定不是汇,i可能是汇
if(Map[i][j] == 0)
j++;
//若map[i][j] = 1,则(1)i有出,i不是汇(2)map[i][i+1..j-1]=0,i+1..j-1都不是汇(3)j及j以后的点可能是汇
//若i=j,j也不是汇,j+1可能是汇
else if(i == j)
{
i++;
j++;
}
//若i!=j,j以前的点都不是汇,j可能是汇
else i = j;
}
//没有汇
if(i > n)
return false;
//找到有可能是汇的点,但是还是需要验证一下
else
{
//汇的纵向都是1,除了map[i][i]
for(j = 1; j <= i-1; j++)
{
if(Map[i][j] == 1)
return false;
}
//汇的横向都是0
for(j = 1; j <= n; j++)
if( i != j && Map[j][i] == 0)
return false;
return true;
}
}
~~~
###
3.main.cpp
~~~
#include <iostream>
using namespace std;
#include "Link_Graph.h"
#include "Mat_Graph.h"
void Test1()
{
cout<<"Test1"<<endl;
int n, a, b;
cin>>n;
Link_Graph *LG = new Link_Graph(n);
while(cin>>a>>b && a && b)//输入00时结束
LG->AddSingleEdge(a, b);
LG->OutDegree();
LG->InDegree();
delete LG;
}
void Test2()
{
cout<<"Test2"<<endl;
Link_Graph *LG = new Link_Graph(6);
Mat_Graph *MG = new Mat_Graph(6);
int edge[6][2] = {{1,2},{1,3},{2,4},{2,5},{3,6},{3,6}};
int i = 0;
for(i = 0; i < 6; i++)
{
LG->AddSingleEdge(edge[i][0], edge[i][1]);
MG->AddSingleEdge(edge[i][0], edge[i][1]);
}
LG->Print();
MG->Print();
delete LG;
delete MG;
}
void Test3()
{
cout<<"Test3"<<endl;
int n, a, b;
cin>>n;
Link_Graph *LG = new Link_Graph(n);
Mat_Graph *MG = new Mat_Graph(n);
while(cin>>a>>b && a && b)//输入00时结束
{
LG->AddSingleEdge(a, b);
MG->AddSingleEdge(a, b);
}
Link_Graph *NewLG = LG->Reverse();
NewLG->Print();
Mat_Graph *NewMG = MG->Reverse();
NewMG->Print();
delete LG;
delete MG;
delete NewLG;
delete NewMG;
}
void Test5()
{
cout<<"Test5"<<endl;
int n, a, b;
cin>>n;
Link_Graph *LG = new Link_Graph(n);
Mat_Graph *MG = new Mat_Graph(n);
while(cin>>a>>b && a && b)//输入00时结束
{
LG->AddSingleEdge(a, b);
MG->AddSingleEdge(a, b);
}
Link_Graph *NewLG = LG->Square();
NewLG->Print();
Mat_Graph *NewMG = MG->Square();
NewMG->Print();
delete LG;
delete MG;
delete NewLG;
delete NewMG;
}
void Test6()
{
cout<<"Test6"<<endl;
int n, a, b;
cin>>n;
Mat_Graph *MG = new Mat_Graph(n);
while(cin>>a>>b && a && b)//输入00时结束
MG->AddSingleEdge(a, b);
if(MG->Universal_Sink())
cout<<"包含通用的汇"<<endl;
else
cout<<"不包含通用的汇"<<endl;
delete MG;
}
/*
6
1 2
1 4
4 2
2 5
5 4
3 5
3 6
6 6
0 0
*/
int main()
{
Test1();
Test2();
Test3();
Test5();
Test6();
return 0;
}
~~~
# 三、练习
22.1-1
计算出度的时间:E
计算入度的时间:E
~~~
void Test1()
{
cout<<"Test1"<<endl;
int n, a, b;
cin>>n;
Link_Graph *LG = new Link_Graph(n);
while(cin>>a>>b && a && b)//输入00时结束
LG->AddSingleEdge(a, b);
LG->OutDegree();
LG->InDegree();
delete LG;
}
~~~
22.1-2
~~~
void Test2()
{
cout<<"Test2"<<endl;
Link_Graph *LG = new Link_Graph(6);
Mat_Graph *MG = new Mat_Graph(6);
int edge[6][2] = {{1,2},{1,3},{2,4},{2,5},{3,6},{3,6}};
int i = 0;
for(i = 0; i < 6; i++)
{
LG->AddSingleEdge(edge[i][0], edge[i][1]);
MG->AddSingleEdge(edge[i][0], edge[i][1]);
}
LG->Print();
MG->Print();
delete LG;
delete MG;
}
~~~
邻接表表示:
1->2->3
2->4->5
3->6->7
4
5
6
7
矩阵表示:
0110000
0001100
0000011
0000000
0000000
0000000
0000000
22.1-3
邻接表表示的有向图的转置
~~~
Link_Graph* Link_Graph::Reverse()
{
Link_Graph *ret = new Link_Graph(n);
int i;
//遍历图G中的每一条边,以终点为起点,以起点为终点,加入到新图RET中
for(i = 1; i <= n; i++)
{
Edge *e = V[i].head;
while(e)
{
ret->AddSingleEdge(e->end, e->start);
e = e->next;
}
}
return ret;
}
~~~
邻接矩阵表示的有向图的转置
~~~
Mat_Graph* Mat_Graph::Reverse()
{
int i, j;
Mat_Graph *ret = new Mat_Graph(n);
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
if(Map[i][j])
ret->AddSingleEdge(j, i);
}
return ret;
}
~~~
22.1-4
遍历多重图中的每一条边,通过AddDoubleAdge()函数把它加入到另一个图中
22.1-5
邻接表表示的有向图的平方图
~~~
Link_Graph* Link_Graph::Square()
{
int i;
Link_Graph *ret = new Link_Graph(n);
//遍历图中每个顶点
for(i = 1; i <= n; i++)
{
//遍历以该顶点为起点的边
Edge *e = V[i].head, *e2;
while(e)
{
//把原来有的边加入到新图中
ret->AddSingleEdge(e->start, e->end);
//把以E的终点为起点的边加入新图中
e2 = V[e->end].head;
while(e2)
{
ret->AddSingleEdge(e->start, e2->end);
e2 = e2->next;
}
e = e->next;
}
}
return ret;
}
~~~
邻接矩阵表示的有向图的平方图
~~~
Mat_Graph* Mat_Graph::Square()
{
int i, j, k;
Mat_Graph *ret = new Mat_Graph(n);
//遍历图中每个顶点
for(i = 1; i <= n; i++)
{
for(j = 1; j <= n; j++)
{
if(Map[i][j])
{
//把原来有的边加入到新图中
ret->AddSingleEdge(i, j);
//把以E的终点为起点的边加入新图中
for(k = 1; k <= n; k++)
if(Map[j][k])
ret->AddSingleEdge(i, k);
}
}
}
return ret;
}
~~~
22.1-6
如果A[i, j] = 1,即(i, j)∈E(1≤i≤|V|,1≤j≤|V|,E是G的边集),那么顶点i就不可能是通用汇点,因为它有一条出边。
现在假设A[i, j] = 0,即(i, j)∉E,且i≠j。在这种情况下,顶点j就不可能是通用汇点,因为它的入度小于|V|-1
因此,这个问题等价于:给定一个有向图G的|V|×|V|邻接矩阵A,在O(|V|)时间内判断是否存在一个整数j(1≤j≤|V|),使得对于所有的i(1≤i≤|V|且i≠j)都有A[i, j] = 1,对于所有的k(1≤k≤|V|)都有A[j, k] = 0。更形象地说,就是判断A里面是否有这样一个“十”字:这“十”字的横全是0,竖全是1(除了“十”字的中心)。
~~~
bool Mat_Graph::Universal_Sink()
{
//i指向有可能是汇的编号最小的点
int i = 1, j = 1;
while(j <= n)
{
//map[i][j] = 0,那么j没有i的入,一定不是汇,i可能是汇
if(Map[i][j] == 0)
j++;
//若map[i][j] = 1,则(1)i有出,i不是汇(2)map[i][i+1..j-1]=0,i+1..j-1都不是汇(3)j及j以后的点可能是汇
//若i=j,j也不是汇,j+1可能是汇
else if(i == j)
{
i++;
j++;
}
//若i!=j,j以前的点都不是汇,j可能是汇
else i = j;
}
//没有汇
if(i > n)
return false;
//找到有可能是汇的点,但是还是需要验证一下
else
{
//汇的纵向都是1,除了map[i][i]
for(j = 1; j <= i-1; j++)
{
if(Map[i][j] == 1)
return false;
}
//汇的横向都是0
for(j = 1; j <= n; j++)
if( i != j && Map[j][i] == 0)
return false;
return true;
}
}
~~~
22.1-7
B = -BT
BBT = -B2
22.1-8
类似于邻接表与邻接矩阵的折中策略
- 前言
- 第6章 堆排序
- 6-3 Young氏矩阵
- 第7章 快速排序
- 第8章 线性时间排序
- 第9章 排序和顺序统计学算法导论
- 算法导论 9.1-1 求第二小元素
- 第10章 10.1 栈和队列
- 第10章 10.2 链表
- 第10章 10.3 指针和对象实现
- 第10章 10.4 有根树的表示
- 第11章-散列表
- 第12章 二叉查找树
- 第13章 红黑树
- 第14章 14.1 动态顺序统计
- 算法导论-14-1-最大重叠点
- 算法导论-14-2-Josephus排列
- 第14章 14.3 区间树
- 第15章 动态规划
- 第16章 贪心算法
- 第18章 B树
- 第19章-二项堆
- 第20章 斐波那契堆
- 第21章 用于不相交集合的数据结构
- 第22章 图的基本算法 22.1 图的表示
- 第22章 图算法 22.2 广度优先搜索
- 第22章 图算法 22.3 深度优先搜索
- 第22章 图的基本算法 22.4 拓扑排序
- 第22章 图的基本算法 22.5 强联通分支