一、图的基本存储结构
- 邻接矩阵
- 邻接表
这是图的两种基本表示方法,当然还有改进的十字链表、多重邻接矩阵等,在这里就不展开。邻接矩阵适用于稠密图,因为它是以顶点来表示的;邻接表适用于稀疏图,对于边较少的图比较合适。
二、图的创建
- 邻接矩阵
#include<iostream>
#include<string>
using namespace std;
class Map{
int VertexNum;//顶点数
int ArcNum;//边数
string *Vertex;//顶点字符数组
int **matrix;//邻接矩阵
public:
void CreateMap();
void CreateMap2();
int GetVexNo(string x);//找到字符串对应的顶点下标
};
void Map::CreateMap(){//这种是直接输入矩阵信息的方式
cin >> VertexNum;
Vertex = new string[VertexNum];
matrix = new int*[VertexNum];
for(int i=0; i < VertexNum; ++i){
matrix[i] = new int[VertexNum]();//初始化为0
}
for(int i = 0; i < VertexNum; ++i){
for(int j = 0; j < VertexNum; ++j){
cin >> matrix[i][j];
}
}
}
void Map::CreateMap2(){//这种是通过输入边信息创建图
cin >> VertexNum;
Vertex = new string[VertexNum];
matrix = new int*[VertexNum];
for(int i=0; i < VertexNum; ++i){
matrix[i] = new int[VertexNum]();//初始化为0
}
for(int i=0; i < VertexNum; ++i){
cin >> Vertex[i];
}
string x, y;
int p1, p2;
cin >> ArcNum;
for(int i=0; i < ArcNum; ++i){
cin >> x >> y;
p1 = GetVexNo(x);
p2 = GetVexNo(y);
matrix[p1][p2] = 1;
matrix[p2][p1] = 1;//无向图要做
}
}
int Map::GetVexNo(string x){
int i;
for(i = 0; i < VertexNum; ++i){
if(x == Vertex[i]){
break;
}
}
return i;
}
- 邻接表
#include<iostream>
#include<string>
using namespace std;
struct Node{
int VertexNo;//顶点在顶点数组中的下标
Node* next;//保存与之相连的顶点,构成边信息
Node(){ next = NULL; }
};
class Map{
int VertexNum;//顶点数
int ArcNum;//边数
string *Vertex;//顶点字符数组
Node* AdjList;//邻接表
public:
void CreateMap();
int GetVexNo(string x);
void InsertLinkList(string x, string y);
};
void Map::CreateMap2(){//通过输入边信息创建图
cin >> VertexNum;
Vertex = new string[VertexNum];
AdjList = new Node[VertexNum];
for(int i=0; i < VertexNum; ++i){
cin >> Vertex[i];
}
string x, y;
int p1, p2;
cin >> ArcNum;
for(int i=0; i < ArcNum; ++i){
cin >> x >> y;
InsertLinkList(x, y);
InsertLinkList(y, x);//无向图要做
}
}
int Map::GetVexNo(string x){
int i;
for(i = 0; i < VertexNum; ++i){
if(x == Vertex[i]){
break;
}
}
return i;
}
void Map::InsertLinkList(string x, string y){
int i, j;
j = GetVexNo(x);
i = GetVexNo(y);
Node* p = AdjList + j;
Node* s = new Node();
s->VertexNo = i;
while(p->next){
p = p->next;
}
p->next = s;
}
三、图的遍历
- 深度优先搜索(DFS)
- 广度优先搜索(BFS)
这两种方法实现不难,这里不提。注意DFS核心是递归,类似树的先序遍历;BFS核心是队列,类似树的层次遍历即可。
四、图的应用
- DS图—图的邻接矩阵存储及度计算
- 题目描述
假设图用邻接矩阵存储。输入图的顶点信息和边信息,完成邻接矩阵的设置,并计算各顶点的入度、出度和度,并输出图中的孤立点(度为0的顶点)
–程序要求–
若使用C++只能include一个头文件iostream;若使用C语言只能include一个头文件stdio
不允许使用第三方对象或函数实现本题的要求
- 输入
测试次数T,每组测试数据格式如下:
图类型 顶点数 (D—有向图,U—无向图)
顶点信息
边数
每行一条边(顶点1 顶点2)或弧(弧尾 弧头)信息
- 输出
每组测试数据输出如下信息(具体输出格式见样例):
图的邻接矩阵
按顶点信息输出各顶点的度(无向图)或各顶点的出度 入度 度(有向图)。孤立点的度信息不输出。
图的孤立点。若没有孤立点,不输出任何信息。
- 样例输入
2
D 5
V1 V2 V3 V4 V5
7
V1 V2
V1 V4
V2 V3
V3 V1
V3 V5
V4 V3
V4 V5
U 5
A B C D E
5
A B
A C
B D
D C
A D
- 样例输出
0 1 0 1 0
0 0 1 0 0
1 0 0 0 1
0 0 1 0 1
0 0 0 0 0
V1: 2 1 3
V2: 1 1 2
V3: 2 2 4
V4: 2 1 3
V5: 0 2 2
0 1 1 1 0
1 0 0 1 0
1 0 0 1 0
1 1 1 0 0
0 0 0 0 0
A: 3
B: 2
C: 2
D: 3
E
#include<iostream>
using namespace std;
class Map{
int len;//顶点数目
char type;//图的类型
string *v;//顶点数组
int **p;//邻接矩阵
public:
Map(){
int i, j;
cin >> type >> len;
v = new string[len];
for(i=0;i<len;i++)
cin>>v[i];
p=new int*[len];
for(i=0;i<len;i++)
p[i]=new int[len];
for(i=0;i<len;i++)
for(j=0;j<len;j++)
p[i][j]=0;
}
int FindPoint(string ch){ //查找顶点在顶点数组中的下标
for(int i=0;i<len;i++){
if(ch==v[i])
return i;
}
return -1;
}
void CreateMap(){ //创建邻接矩阵
int i,edge,v1,v2;
string ch1,ch2;
cin >> edge;
for(i = 0; i < edge; i++){
cin>>ch1>>ch2;
v1=FindPoint(ch1);
v2=FindPoint(ch2);
p[v1][v2]=1;
if(type=='U') //无向图要多处理一步
p[v2][v1]=1;
}
}
void Display(){ //输出显示
int i,j;
for(i=0;i<len;i++){ //显示邻接矩阵
for(j=0;j<len-1;j++)
cout<<p[i][j]<<" ";
cout<<p[i][j]<<endl; //每行最后一个数据后没有空格,要单独处理
}
if(type=='D'){ //有向图的输出信息:出度、入度、度
for(i=0;i<len;i++){
int a=0,b=0;
for(j=0;j<len;j++){
a+=p[i][j]; //出度
b+=p[j][i]; //入度
}
if(a==0&&b==0)
cout<<v[i]<<endl;
else
cout<<v[i]<<": "<<a<<" "<<b<<" "<<a+b<<endl;
}
}
else if(type=='U'){ //无向图的输出信息:度
for(i=0;i<len;i++){
int a=0;
for(j=0;j<len;j++)
a+=p[i][j];
if(a==0)
cout<<v[i]<<endl;
else
cout<<v[i]<<": "<<a<<endl;
}
}
}
~Map(){
for(int i=0;i<len;i++)
delete []p[i];
delete []p;
delete []v;
len=0;
type='0';
}
};
int main(){
int t;
cin>>t;
while(t--){
Map test;
test.CreateMap();
test.Display();
}
return 0;
}
- 图的连通分量
-
求解图的连通分量可借助DFS,要知道DFS每一次的搜索都会找到图中的一个连通分量。标记走过的点,记录图总的DFS次数即可求出图的连通分量数目。
-
题目描述
输入无向图顶点信息和边信息,创建图的邻接矩阵存储结构,计算图的连通分量个数。
- 输入
测试次数t
每组测试数据格式如下:
第一行:顶点数 顶点信息
第二行:边数
第三行开始,每行一条边信息
- 输出
每组测试数据输出,顶点信息和邻接矩阵信息
输出图的连通分量个数,具体输出格式见样例。
每组输出直接用空行分隔。
- 样例输入
3
4 A B C D
2
A B
A C
6 V1 V2 V3 V4 V5 V6
5
V1 V2
V1 V3
V2 V4
V5 V6
V3 V5
8 1 2 3 4 5 6 7 8
5
1 2
1 3
5 6
5 7
4 8
- 样例输出
A B C D
0 1 1 0
1 0 0 0
1 0 0 0
0 0 0 0
2
V1 V2 V3 V4 V5 V6
0 1 1 0 0 0
1 0 0 1 0 0
1 0 0 0 1 0
0 1 0 0 0 0
0 0 1 0 0 1
0 0 0 0 1 0
1
1 2 3 4 5 6 7 8
0 1 1 0 0 0 0 0
1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 1 1 0
0 0 0 0 1 0 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 0 0 0
3
#include<iostream>
using namespace std;
class Map{
int len; //顶点数
string *name; //顶点数组
int **p; //邻接矩阵
int count;//访问元素计数
int *v;//访问标识符
public:
Map(){ //初始化
count=0;
cin>>len;
v=new int[len];
name=new string[len];
p=new int*[len];
for(int i=0;i<len;i++){
cin>>name[i];
v[i]=0;
p[i]=new int[len];
for(int j=0;j<len;j++)
p[i][j]=0;
}
}
int FindPoint(string ch){ //查找顶点字母在数组中的下标
for(int i=0;i<len;i++){
if(ch==name[i])
return i;
}
return -1;
}
void CreateMap(){ //建立邻接矩阵
int i,edge,v1,v2;
string ch1,ch2;
cin>>edge;
for(i=0;i<edge;i++){
cin>>ch1>>ch2;
v1=FindPoint(ch1);
v2=FindPoint(ch2);
p[v1][v2]=1;
p[v2][v1]=1;
}
}
void dsf(int k){ //DSF深度遍历
v[k]=1;
for(int i=0;i<len;i++){
if(p[k][i] && !v[i]){
dsf(i);
}
}
}
int DFSTraverse(){ //用count记录调用DSF的次数,即有几个联通分量
for(int i=0;i<len;i++)
if(!v[i]){
count++;
dsf(i);
}
return count;
}
void Display(){ //显示数据
int i,j;
for(i=0;i<len-1;i++)
cout<<name[i]<<" ";
cout<<name[i]<<endl;
for(i=0;i<len;i++){
for(j=0;j<len-1;j++)
cout<<p[i][j]<<" ";
cout<<p[i][j]<<endl;
}
cout<<DFSTraverse()<<endl<<endl;
}
~Map(){
for(int i=0;i<len;i++)
delete[] p[i];
delete[] p;
delete[] v;
delete[] name;
len=0;
}
};
int main(){
int t;
cin>>t;
while(t--){
Map test;
test.CreateMap();
test.Display();
}
return 0;
}
- DS图—图非0面积
- 题目描述
编程计算由"1"围成的下列图形的面积。面积计算方法是统计"1"所围成的闭合曲线中"0"点的数目。如图所示,在10*10的二维数组中,"1"围住了15个点,因此面积为15。
- 输入
测试次数t
每组测试数据格式为:
数组大小m,n
一个由0和1组成的m*n的二维数组
- 输出
对每个二维数组,输出符号"1"围住的"0"的个数,即围成的面积。假设一定有1组成的闭合曲线,但不唯一。
- 样例输入
2
10 10
0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 1 1 0 0 0
0 0 0 0 1 0 0 1 0 0
0 0 0 0 0 1 0 0 1 0
0 0 1 0 0 0 1 0 1 0
0 1 0 1 0 1 0 0 1 0
0 1 0 0 1 1 0 1 1 0
0 0 1 0 0 0 0 1 0 0
0 0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0
5 8
0 1 1 0 0 1 1 0
1 0 1 0 1 0 0 1
0 1 0 1 0 0 1 0
0 1 0 0 1 1 1 0
0 0 0 0 0 0 0 0
- 样例输出
15
5
//该题思路:先将1边界的外围的0全变成1,然后计算0的个数。
#include "iostream"
using namespace std;
class Map {
private:
bool **Visit;//访问标识
int **Matrix;//矩阵
int m; //行
int n; //列
void SetOne(int x, int y) { //将1的外围的0都变成1,这种递归可以处理一片完整的区域(将从0开始的范围都变成1)
if (x<0 || y<0 || x>=m || y>=n || Matrix[x][y] || Visit[x][y])
return;
Matrix[x][y]=1;
Visit[x][y]=true;
SetOne(x-1,y); //上
SetOne(x+1,y); //下
SetOne(x,y-1); //左
SetOne(x,y+1); //右
}
public:
void SetMatrix() {
cin>>m>>n;
Matrix=new int*[m]; //给数组分配空间
Visit=new bool*[m];
for(int i=0;i<m;i++){
Matrix[i]=new int[n];
Visit[i]=new bool[n];
}
for (int i=0; i<m; i++) //数组初始化
for (int j=0; j<n; j++){
cin>>Matrix[i][j];
Visit[i][j]=false;
}
}
int HowMany() {
//用两个循环,处理整个边界,把边界上从0开始的一片区域都变成1
for (int i=0; i<m; i++) {
SetOne(i,0); //每行的第一列
SetOne(i,n-1); //每行的最后一列
}
for (int i=0; i<n; i++) {
SetOne(0,i); //每列的第一行
SetOne(m-1,i); //每列的最后一行
}
//开始统计被1包围的区域里面的0的数目
int res=0;
for (int i=1; i<m; i++) //计算0的个数
for (int j=1; j<n; j++)
if (!Matrix[i][j])
res++;
return res;
}
};
int main() {
int m,n,t;
cin>>t;
while (t--) {
Map mp;
mp.SetMatrix();
cout<<mp.HowMany()<<endl;
}
return 0;
}
设置访问数组对本题结果没有影响,但是习惯在访问时设置访问数组标记走过的点,用处很大。