数据结构实验三目录
- 0.实验要求
- 1.实验思路
-
- 1.1实现图的存储
-
- 1.1.1 图的邻接矩阵存储
- 1.1.2 使用图的邻接矩阵建立一个图
- 1.1.3 图的邻接表存储
- 1.1.4 使用图的邻接表建立一个图
- 1.2. 图的邻接矩阵和图的邻接表两种存储结构的转换
-
- 1.2.1 图的邻接矩阵转向图的邻接表
- 1.2.2 图的邻接表转向图的邻接矩阵
- 1.3 图的深度优先遍历
-
- 1.3.1 邻接矩阵表示的图的深度优先遍历
-
- 1.3.1.1 递归
- 1.3.1.2 非递归
- 1.3.2 邻接表表示的图的深度优先遍历
-
- 1.3.2.1 递归
- 1.3.2.2 非递归
- 1.4图的广度优先遍历
-
- 1.4.1 邻接矩阵表示的图的广度优先遍历
- 1.4.2邻接表表示的图的广度优先遍历
- 1.5主程序
- 1.6结果分析
0.实验要求
1.实验思路
1.1实现图的存储
1.1.1 图的邻接矩阵存储
图的邻接矩阵存储就是,把图看成是一个二维矩阵,矩阵A[i][j]的值就表示节点i和节点j之间的边的权重,如果顶点i和顶点j之间没有边,则设置A[i][j] = 正无穷大。
因此,用邻接矩阵的形式存储图的结构,我们需要以下三个信息
- 邻接矩阵
- 所有的顶点的信息
- 图的顶点个数和边的个数
#include<iostream>
#include<fstream>
#include<queue>
#include<stack>
#define max 100
typedef int data;//权重的数据类型
using namespace std;
bool visited[max];
int dfn[max];//顶点的先深编号
int count=1;
//邻接矩阵的存储结构
typedef struct{
int vertex[max];//邻接矩阵的顶点数组
data edge[max][max];//邻接矩阵的边值
int n,e;//邻接矩阵的顶点和边的个数
}mygraph;
看一个实例,假设边的权重为1表示两个顶点之间有边,边的权重为0表示两个顶点之间没有边。
1.1.2 使用图的邻接矩阵建立一个图
- 指定图的顶点的个数和边的个数
- 初始化邻接表和邻接矩阵
- 根据给定的顶点和顶点的权重改变邻接矩阵的值。 注意:对于无向图来说,邻接矩阵是对称的,所以如果顶点i , j之间权重为k,则A[i][j] = A[j][i] = k
void CreateGraph(mygraph *G){ // 创建邻接矩阵
int i,j,k,w;
cout<<"输入顶点个数和边的个数"<<endl;
cin>>G->n>>G->e;//输入顶点个数和边的个数;
for(i=0;i<G->n;i++){
G->vertex[i]=i;
}
for(i=0;i<G->n ;i++)
for(j=0;j<G->n ;j++)
G->edge[i][j]=0;//初始化邻接矩阵
for(k=0;k<G->e;k++){
cout<<"输入边(i,j)和权重w"<<endl;
cin>>i>>j>>w;
G->edge[i][j]=w;//输入边(i,j)和权重w
G->edge[j][i]=w;
}
}
void wenjianCreateGraph(mygraph *G){ // 从文件创建邻接矩阵
fstream in;
in.open("tu1.txt");
int i,j,k,w;
in>>G->n>>G->e;//输入顶点个数和边的个数;
for(i=0;i<G->n;i++){
G->vertex[i]=i;
}
for(i=0;i<G->n ;i++)
for(j=0;j<G->n ;j++)
G->edge[i][j]=0;//初始化邻接矩阵
for(k=0;k<G->e;k++){
in>>i>>j>>w;
G->edge[i][j]=w;//输入边(i,j)和权重w
G->edge[j][i]=w;
}
in.close();
}
算法时间复杂度:O(n^2+n+2e )//n个顶点初始化,n^2个边的初始化,输入无向图的2e条边
算法空间复杂度:O(n^2+n )//存储n个顶点及n^2个边值信息
void shuchugraph(mygraph *t){ //打印邻接矩阵的信息
for(int i=0;i<t->n ;i++){
cout<<"\t"<<i;
}
cout<<endl;
for(int i=0;i<t->n ;i++){
cout<<i<<"\t";
for(int j=0;j<t->n ;j++){
if(t->edge[i][j]==0){
cout<<"∞\t";
}
else
cout<<t->edge[i][j]<<"\t";
}
cout<<endl;
}
}
1.1.3 图的邻接表存储
图的邻接表存储,就是对于每个顶点v来说,和这个顶点v相连的所有邻接点构成一个线性表。
我们需要把顶点分成两类,一类是主顶点,一类是和顶点链接的边顶点。
对于每个主顶点,我们需要存储以下信息:
- 有一个指针域,指向的是和顶点v邻接的第一个顶点。
- 顶点的下标,指明了顶点的编号
- 一个标记布尔类型的值,标记这个顶点是否被访问过(用于图的遍历)
typedef struct { //顶点表节点
int dingdian;//顶点表节点
bool zouguo;//标记节点是否被访问过
Edgenode *firstedge;//边链表头指针
}Vertex;
对于每一个边顶点,我们需要存储以下信息。
- 顶点下标
- 和主顶点邻接的边的权重
- 指向下一边表节点的指针
typedef struct node{ //边表节点
int xiabiao;//顶点下标
data cost;/和主顶点邻接的边的权重
struct node *next;//指向下一边表节点的指针
}Edgenode;
那么整个图就可以用一个主顶点数组来表示,同时需要指明图的边的个数和顶点的个数。
typedef struct { //图的邻接表
Vertex vexlist[max];//顶点数组
int n,e;//顶点个数和边的个数
}adjgraph;
看一个例子
1.1.4 使用图的邻接表建立一个图
输入顶点个数和边的个数
初始化顶点邻接表
根据给定的邻接顶点i和j,创建边顶点p和q分别存储顶点i和j的信息,让主顶点i指向q,且让主顶点j指向边顶点q。
void CreateadjGraph(adjgraph *G){ // 创建邻接表
int i,k,j;
data weight;
cout<<"请输入顶点个数和边的个数"<<endl;
cin>>G->n >>G->e ;
for(i=0;i<G->n;i++){
G->vexlist[i].dingdian=i;
G->vexlist[i].firstedge=NULL;//初始化邻接表
}
for(k=0;k<G->e ;k++){
cout<<"输入边(i,j)和权重w"<<endl;
cin>>i>>j>>weight;
Edgenode* p=new Edgenode;
p->xiabiao =j;p->cost =weight;
p->next =G->vexlist[i].firstedge ;
G->vexlist[i].firstedge =p;
Edgenode* q=new Edgenode;
q->xiabiao =i;q->cost =weight;
q->next =G->vexlist[j].firstedge ;
G->vexlist[j].firstedge =q;
}
}
void wenjianCreateadjGraph(adjgraph *G){ // 从文件创建邻接表
fstream in;
in.open("tu2.txt");
int i,k,j;
data weight;
in>>G->n >>G->e ;
for(i=0;i<G->n;i++){
G->vexlist[i].dingdian=i;
G->vexlist[i].firstedge=NULL;//初始化邻接表
}
for(k=0;k<G->e ;k++){
in>>i>>j>>weight;
Edgenode* p=new Edgenode;
p->xiabiao =j;p->cost =weight;
p->next =G->vexlist[i].firstedge ;
G->vexlist[i].firstedge =p;
Edgenode* q=new Edgenode;
q->xiabiao =i;q->cost =weight;
q->next =G->vexlist[j].firstedge ;
G->vexlist[j].firstedge =q;
}
in.close();
}
void shuchuadjgraph(adjgraph *t){ //打印邻接表
for(int i=0;i<t->n ;i++){
cout<<i<<":\t";
Edgenode *p;
p=t->vexlist[i].firstedge;
while(p!=NULL){
cout<<p->xiabiao<<"\t";
p=p->next ;
}
cout<<endl;
}
}
1.2. 图的邻接矩阵和图的邻接表两种存储结构的转换
1.2.1 图的邻接矩阵转向图的邻接表
- 思路很简单,先初始化邻接矩阵,根据顶点个数和边的个数。
- 然后遍历邻接矩阵,如果A[i][j] 不等于0 , 则表示顶点i 和 顶点j
之间邻接,边权重为A[i][j],然后用之前讲过的邻接表创建图的方法来创建邻接表。
void jzhuanhuanb(mygraph *G,adjgraph *T){ //将邻接矩阵G转化为邻接表T
T->n=G->n ;//初始化邻接表
T->e =G->e;//初始化邻接表
for(int i=0;i<G->n;i++ ){ //初始化邻接表
T->vexlist[i].dingdian =i;
T->vexlist[i].firstedge=NULL;
}
for(int j=0;j<G->n;j++){
for(int k=0;k<G->n ;k++){
if(G->edge[j][k]!=0){
Edgenode *p=new Edgenode;
p->xiabiao=G->vertex[k];//顶点赋值
p->cost =G->edge[j][k]; //边权重赋值
p->next =T->vexlist[j].firstedge ;
T->vexlist[j].firstedge=p;
}
}
}
}
1.2.2 图的邻接表转向图的邻接矩阵
思路同上,先初始化,然后遍历每个顶点的邻接顶点,然后给邻接矩阵赋值即可。
void bzhuanhuanj(mygraph *G,adjgraph *T){ //将邻接表转换为邻接矩阵
Edgenode *p;
G->n =T->n;//初始化邻接矩阵
G->e =T->e ;//初始化邻接矩阵
int i,j;
for(i=0;i<G->n;i++){
for( j=0;j<G->n;j++){
G->edge[i][j]=0;//初始化邻接矩阵
}
}
for(int k=0;k<G->n;k++){
p=T->vexlist[k].firstedge ;
while(p!=NULL){
G->edge[k][p->xiabiao]=p->cost;//将边权重赋值
p=p->next ;
}
}
}
1.3 图的深度优先遍历
1.3.1 邻接矩阵表示的图的深度优先遍历
1.3.1.1 递归
思路:借助一个标记数组,标记了每个顶点是否被访问过。
- 首先对每个顶点标记为未访问过。
- 然后对于顶点v,如果顶点v没有被访问过,则访问这个顶点,并且把这个顶点标记为访问过,然后递归的访问这个顶点邻接的顶点。
- 访问完顶点v的所有邻接的节点后,把森林的个数加1,然后再去访问没有访问过的顶点k,再转2.
void DFSX(mygraph *G,int x){ //辅助深度递归 (通过邻接矩阵)
visited[x]=true;
cout<<G->vertex[x]<<" ";
for(int j=0;j<G->n;j++){
if(!visited[j]&&G->edge[x][j]!=0)
DFSX(G,G->vertex[j]);
}
}
void diguishen(mygraph *G){ //邻接矩阵深度递归算法
for(int i=0;i<G->n;i++){ //将每个节点标记为未访问过
visited[i]=false;
}
int count=1;//森林个数
for(int i=0;i<G->n;i++){
if(!visited[i]){
cout<<"森林"<<count++<<":";
DFSX(G,i);
cout<<endl;
}
}
}
1.3.1.2 非递归
一般对于所有的递归算法来说,要把它变成非递归算法,则要利用栈。
算法原理:
- 首先对每个顶点标记为未访问过。
- 然后遍历所有的顶点,对于顶点v,如果顶点v没有被访问过,则访问这个顶点,并且把这个顶点标记为访问过,并且把这个顶点压入栈中。
- 当栈不为空时,取栈顶顶点t,然后访问所有和顶点t邻接的顶点x。如果顶点x没有被访问过,则访问这个顶点,并且把这个顶点标记为访问过,并且把这个顶点x压入栈中。然后再去访问顶点x邻接的所有顶点。
- 考察完顶点v所邻接的所有顶点之后,再去访问没有访问过的顶点。
void feidiguishen2(mygraph *G){ //邻接矩阵深度非递归算法
for(int l=0;l<G->n ;l++)//将每个节点标记为未访问过
visited[l]=false;
stack<int>s;
int count=1;//森林个数
for(int j=0;j<G->n;j++){
if(!visited[j]){
cout<<"森林"<<count++<<":";
visited[j]=true;
s.push(j);
cout<<G->vertex[j]<<" ";
while(!s.empty() ){
int t = s.top();
bool flag = false;
for(int k=0;k<G->n ;k++){
if(G->edge[t][k]!=0){
if(!visited[k]){
visited[k]=true;
cout<<G->vertex[k]<<" ";
s.push(k);
flag = true;
break ;
}
}
}
if (flag == false )
s.pop() ;
}
cout<<endl;
}
}
}
1.3.2 邻接表表示的图的深度优先遍历
思路一样,就是代码不一样
1.3.2.1 递归
void DFSX2(adjgraph *G,int x){ //邻接表深度递归辅助算法
Edgenode *p;
visited[x]=true;//标记为访问过
cout<<G->vexlist[x].dingdian<<" ";
p=G->vexlist[x].firstedge;
while(p){
if(!visited[p->xiabiao])
DFSX2(G,p->xiabiao);
p=p->next ;
}
}
void diguishen2(adjgraph *G){ //邻接表深度递归算法
for(int i=0;i<G->n;i++){ //将每个节点标记为未访问过
visited[i]=false;
}
int count=1;
for(int i=0;i<G->n;i++){ //遍历每一个节点
if(!visited[i]){
cout<<"森林"<<count++<<":";
DFSX2(G,i);
cout<<endl;
}
}
}
1.3.2.2 非递归
void feidiguishen(adjgraph *G){ //邻接表深度非递归算法
stack<int>s;
Edgenode *p;
for(int j=0;j<G->n;j++){ //将每个节点标记为未访问过
visited[j]=false; }
int count=1;//森林数目
for(int i=0;i<G->n;i++){ //遍历每个节点
if(!visited[i]){
cout<<"森林"<<count<<":";
visited[i]=true;
s.push(i);
cout<<G->vexlist[i].dingdian<<" ";
p=G->vexlist[i].firstedge;
while(!s.empty()){
p=G->vexlist[s.top()].firstedge ;
bool flag = false;
while(p){
if(!visited[p->xiabiao ]){
visited[p->xiabiao ]=true;
cout<<G->vexlist[p->xiabiao].dingdian<<" ";//输出该节点
s.push(p->xiabiao );
p=G->vexlist[p->xiabiao].firstedge ;
flag = true;
break;
}
else{
p=p->next ;
}
}
if(flag == false){
s.pop() ;
}
}
cout<<endl;
count++;
}
}
}
1.4图的广度优先遍历
广度优先遍历和我们之前将的二叉树的层序遍历几乎一样。有一个不同的地方就是图不一定是连通的,可能有几个森林,而二叉树一定是连通的。
思路:借助队列来实现。
- 首先对每个顶点标记为未访问过。
- 然后遍历所有的顶点,对于顶点v,如果顶点v没有被访问过,则访问这个顶点,并且把这个顶点标记为访问过,并且把这个顶点压入队列中。
- 当队列不为空时,取队首顶点t,然后访问所有和顶点t邻接的顶点x。如果顶点x没有被访问过,则访问这个顶点,并且把这个顶点标记为访问过,把这个顶点x压入队列中。每一次访问顶点就从队首取出一个顶点即可。
- 当队列为空时,再去考察其他没有访问过的顶点。
1.4.1 邻接矩阵表示的图的广度优先遍历
void guangdu2(mygraph *G) { //邻接矩阵广度优先遍历
for(int j=0;j<G->n;j++){
visited[j]=false;
}
queue<int>s;
int count=1;
for(int i=0;i<G->n;i++){
if(!visited[i]){
cout<<"森林"<<count++<<":";
visited[i]=true;
cout<<G->vertex[i]<<" ";
s.push(G->vertex[i]);
while(!s.empty() ){
int k=s.front() ;
s.pop() ;
for(int j=0;j<G->n;j++){
if(G->edge [k][j]!=0){
if(!visited[j]){
visited[j]=true;
cout<<G->vertex[j]<<" ";
s.push(G->vertex[j]);
}
}
}
}
cout<<endl;
}
}
}
1.4.2邻接表表示的图的广度优先遍历
void guangdu(adjgraph *G){ //邻接表广度优先遍历
for(int j=0;j<G->n;j++){
visited[j]=false;
}
queue<int>s;
Edgenode *p;
int count=1;
for(int i=0;i<G->n;i++){
if(!visited[i]){
cout<<"森林"<<count++<<":";
visited[i]=true;
cout<<G->vexlist[i].dingdian<<" ";
s.push(G->vexlist[i].dingdian);
while(!s.empty() ){
p=G->vexlist[s.front()].firstedge;
s.pop() ;
while(p!=NULL){
if(!visited[p->xiabiao]){
visited[p->xiabiao]=true;
cout<<G->vexlist[p->xiabiao].dingdian<<" ";
s.push(p->xiabiao);
}
p=p->next;
}
}
cout<<endl;
}
}
}
1.5主程序
void zrh(){
int x=0;//用来判断当前的无向图是用邻接矩阵还是邻接表来表示的,0代表邻接矩阵,1代表邻接表
cout<<"-----------------------------------------------------------------"<<endl;
cout<<"0.退出 |"<<endl;
cout<<"1.通过从文件读入创建一个无向图的邻接矩阵 |"<<endl;
cout<<"2.通过从文件读入创建一个无向图的邻接表 |"<<endl;
cout<<"3.通过输入创建一个无向图的邻接矩阵 |"<<endl;
cout<<"4.通过输入创建一个无向图的邻接表 |"<<endl;
cout<<"5.显示邻接矩阵 |"<<endl;
cout<<"6.显示邻接表 |"<<endl;
cout<<"7.将邻接矩阵转化为邻接表并显示 |"<<endl;
cout<<"8.将邻接表转化为邻接矩阵并显示 |"<<endl;
cout<<"9.深度优先递归遍历无向图 |"<<endl;
cout<<"10.深度优先非递归遍历无向图 |"<<endl;
cout<<"11.广度优先遍历无向图 |"<<endl;
cout<<"-----------------------------------------------------------------"<<endl;
mygraph g;//创建无向图 的邻接矩阵
adjgraph t;//创建无向图的邻接表
int n;
cin>>n;
while(n){
switch(n){
case 1:
wenjianCreateGraph(&g);
x=0;
break;
case 2:
wenjianCreateadjGraph(&t);
x=1;
break;
case 3:
CreateGraph(&g);
x=0;
break;
case 4:
CreateadjGraph(&t);
x=1;
break;
case 5:
shuchugraph(&g);
x=0;
break;
case 6:
shuchuadjgraph(&t);
x=1;
break;
case 7:
adjgraph t1;
cout<<"邻接矩阵为:"<<endl;
shuchugraph(&g);
cout<<"转化为邻接表后为:"<<endl ;
jzhuanhuanb(&g,&t1);
shuchuadjgraph(&t1);
break;
case 8:
mygraph t2;
cout<<"邻接表为:"<<endl;
shuchuadjgraph(&t);
cout<<"转化为邻接矩阵后为:"<<endl ;
bzhuanhuanj(&t2,&t);
shuchugraph(&t2);
break;
case 9:
if(x==1){
diguishen2(&t);
}
else{
diguishen(&g);
}
break;
case 10:
if(x==1){
feidiguishen(&t);
}
else{
feidiguishen2(&g);
}
break;
case 11:
if(x==0){
guangdu2(&g);
}
else{
guangdu(&t);
}
break;
default :
n=0;
break;
}
cout<<"-----------------------------------------------------------------"<<endl;
cout<<"0.退出 |"<<endl;
cout<<"1.通过从文件读入创建一个无向图的邻接矩阵 |"<<endl;
cout<<"2.通过从文件读入创建一个无向图的邻接表 |"<<endl;
cout<<"3.通过输入创建一个无向图的邻接矩阵 |"<<endl;
cout<<"4.通过输入创建一个无向图的邻接表 |"<<endl;
cout<<"5.显示邻接矩阵 |"<<endl;
cout<<"6.显示邻接表 |"<<endl;
cout<<"7.将邻接矩阵转化为邻接表并显示 |"<<endl;
cout<<"8.将邻接表转化为邻接矩阵并显示 |"<<endl;
cout<<"9.深度优先递归遍历无向图 |"<<endl;
cout<<"10.深度优先非递归遍历无向图 |"<<endl;
cout<<"11.广度优先遍历无向图 |"<<endl;
cout<<"-----------------------------------------------------------------"<<endl;
if(x==0)
cout<<"当前无向图为邻接矩阵表示法,请根据提示选择正确的操作:"<<endl;
else
cout<<"当前无向图为邻接表表示法,请根据提示选择正确的操作:"<<endl;
if(n!=0)
cin>>n;
}
}