数据结构之图练习

一、图的基本存储结构
  1. 邻接矩阵
  2. 邻接表

这是图的两种基本表示方法,当然还有改进的十字链表、多重邻接矩阵等,在这里就不展开。邻接矩阵适用于稠密图,因为它是以顶点来表示的;邻接表适用于稀疏图,对于边较少的图比较合适。

二、图的创建
  1. 邻接矩阵
#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;
}
  1. 邻接表
#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核心是队列,类似树的层次遍历即可。

四、图的应用
  1. 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;
}
  1. 图的连通分量
  • 求解图的连通分量可借助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;
}

  1. 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;
}

设置访问数组对本题结果没有影响,但是习惯在访问时设置访问数组标记走过的点,用处很大。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值