Cristiano的图总结

一、图的基本概念
1.1 比较
线性结构:数据元素之间仅具有线性关系,每个元素最多只有一个前驱和一个后驱
树结构:具有层次关系,每个节点最多只有一个双亲,可以有多个孩子
图结构:任意两个顶点之间都可能有关系
1.2

二、图的存储结构以及实现
2.1 图的遍历方法
2.1.1 深度优先遍历:递归思想
基本思想:
①访问顶点v
②从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优化遍历
③重复上两步操作,直至图中所有和v有路径想通的顶点都被访问到

2.1.2 广度优先遍历
基本思想:(用队列实现)
①访问顶点v
②依次访问v的各个未被访问的邻接点v1,v2,v3…
③分为从v1,v2,v3…出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问,直至图中所有的顶点被访问

2.1 邻接矩阵
存储方法:①用一个一维数组存储图中顶点的信息;②用一个二维数组存储图中边的信息。
主类方法汇总:

class MGraph{
    
    
	private:
		int vertexNum , arcNum;          //图的顶点数与边数 
		char vertex[MaxSize];			//存储图中的顶点信息
		int arc[MaxSize][MaxSize];      //存储图中的边的信息
		int visited[MaxSize];
	public:
		MGraph(char a[],int n,int e);
		~MGraph(){
    
    } 
		void DFSTraverse(int v);		//深度优先遍历图
		void BFSTraverse(int v);		//广度优先遍历图
};

构造函数:
伪代码:
①确定图的顶点个数和边的个数
②输入顶点信息存储在一维数组vertex中
③初始化邻接矩阵
④依次输入每条边存储到邻接矩阵当中
⑤初始化visit数组

MGraph::MGraph(char a[],int n,int e){
    
    
	vertexNum = n;
	arcNum = e;
	int m,z;
	for(int i=0;i<vertexNum;i++){
    
    
		vertex[i] = a[i];
	}
	for(int i=0;i<vertexNum;i++){
    
    
		for(int j=0;j<vertexNum;j++)
			arc[i][j] = 0;
	}
	for(int k=0;k<arcNum;k++){
    
    
		cout<<"请输入邻接点之间有边的序号"<<endl;
		cin>>m>>z;
		arc[m][z] = 1;
		arc[z][m] = 1; 
	}
	for(int i=0;i<vertexNum;i++)
		visited[i] = 0;
}

深度优先遍历算法
伪代码:
传入一个参数,表示源点,并将源点的哨兵值改为1
循环,判断,判断条件,如果该点与顶点k有边且顶点k没有被访问过,递归调用函数

void MGraph::DFSTraverse(int v){
    
    
	cout<<vertex[v]<<" ";
	visited[v] = 1;
	for(int k=0;k<vertexNum;k++){
    
    
		if(arc[v][k] == 1 && visited[k] == 0){
    
    
			DFSTraverse(k);
		}
	}
}

广度优先遍历算法
伪代码:
①初始化队列
②输出顶点信息
③顶点入队列
④循环,循环条件,队列不为空

  • 判断如果与顶点相连接的顶点未被访问过,输出,且进入队列中
void MGraph::BFSTraverse(int v){
    
    
	for(int i=0;i<vertexNum;i++)
		visited[i] = 0;
	int front,rear;
	front = rear = -1;
	cout<<vertex[v]<<" ";
	visited[v] = 1;
	int Q[MaxSize];
	Q[++rear] = v;
	while(front != rear){
    
    
		v = Q[++front];
		for(int k=0;k<vertexNum;k++){
    
    
			if(arc[v][k] == 1 && visited[k] == 0){
    
    
				cout<<vertex[k]<<" ";
				visited[k] = 1;
				Q[++rear] = k;
			}
		} 
	}
}

2.2 邻接表
一种循序存储与链式存储相结合的存储方法,类似于树的孩子链表表示法,
一般定义两个结构体:
结构体VertexNode用于存储顶点的数据和指向边表中第一个结点
结构体ArcNode用于存储该顶点的邻接点在顶点表中的下标

//边的存储 
struct ArcNode{
    
    
	int adjvex;
	struct ArcNode* next;
};
//顶点的存储 
struct VertexNode{
    
    
	char vextex;         //顶点数据          
	ArcNode* firstedge;  //与此顶点有链接的下一个顶点的序号 
};

构造函数

ALGraph::ALGraph(char a[],int n,int e){
    
    
	vertexNum = n;
	arcNum = e;
	int m,z;
	for(int i=0;i<vertexNum;i++){
    
    
		adjlist[i].vextex = a[i];
		adjlist[i].firstedge = NULL;
	}
	for(int k=0;k<arcNum;k++){
    
    
		cout<<"请输入邻接点之间有边的序号"<<endl;
		cin>>m>>z;
		ArcNode* arc = new ArcNode;
		arc->adjvex = z;
		arc->next = adjlist[m].firstedge;		//头插法插入数据
		adjlist[m].firstedge = arc;
	} 
	for(int i=0;i<vertexNum;i++)
		visited[i] = 0;
}

深度优先遍历算法

void ALGraph::DFSTraverse(int v){
    
    
	cout<<adjlist[v].vextex<<" ";
	visited[v] = 1;
	ArcNode* p = adjlist[v].firstedge;
	while(p!=NULL){
    
    
		int j = p->adjvex;
		if(visited[j] == 0)
			DFSTraverse(j);
		p = p->next;
	} 
}

广度优先遍历算法

void ALGraph::BFSTraverse(int v){
    
    
	for(int i=0;i<vertexNum;i++)
		visited[i] = 0;
	int front,rear;
	front = rear = -1;
	cout<<adjlist[v].vextex<<" ";
	visited[v] = 1;
	int Q[MaxSize];
	Q[++rear] = v;
	while(front != rear){
    
    
		v = Q[++front];
		ArcNode* p = adjlist[v].firstedge;
		while(p!=NULL){
    
    
			if(visited[p->adjvex] == 0){
    
    
				cout<<adjlist[p->adjvex].vextex<<" ";
				visited[p->adjvex] = 1;
				Q[++rear] = p->adjvex;
			}
			p = p->next;
		}
	}
}

三、图的应用之最小生成树
3.1 Prim算法

typedef struct shortEdge{
    
           //候选最短的边集 
	int adjvex;             	//候选最短边的邻接点的点(其中数组的下标表示起点,数组元素代表的终点) 
	int lowcost;            	//候选最短边的权值 
}ShortEdge;

在构造函数中,对没有边的顶点的权值要赋值大些


for(int i=0;i<vertexNum;i++){
    
    
	for(int j=0;j<arcNum;j++){
    
    
		if(i != j && arc[i][j] == 0)
			arc[i][j] = 1000;	
	}
}

Prim算法
伪代码:
1、初始化辅助数组shortEdge[n];
2、循环,循环vertexNum-1次,以找到所有的边
3、调用最小边的函数,找到最小边的对应的邻接点
4、将该点的边的权值改为0,表示该点进入到集合当中
5、重新调整

int MinEdge(ShortEdge shortEdge1[],int n){
    
    
	int min = 100;
	int number;
	for(int i=1;i<n;i++){
    
    
		if(min>shortEdge1[i].lowcost && shortEdge1[i].lowcost != 0){
    
    
			min = shortEdge1[i].lowcost;
			number = i;
		}
	}
	return number;
}		 
void Prim(MGraph p){
    
    
	//1、初始化以Vo为起点的到所有邻接点的权值 
	ShortEdge se[p.vertexNum];
	for(int i=1;i<p.vertexNum;i++){
    
    
		se[i].adjvex = 0;
		se[i].lowcost = p.arc[0][i];
	}
	se[0].lowcost = 0;
	se[0].adjvex = 0;
	//2、循环vertexNum-1次,以找到所有的边 
	for(int i=1;i<p.vertexNum;i++){
    
    
		//3、找到权值最小的邻接点的下标 
		int k = MinEdge(se,p.vertexNum);
		cout<<"("<<p.vertex[se[k].adjvex]<<","<<p.vertex[k]<<")"<<se[k].lowcost<<endl;
		//4
		、将点k加入到集合U中 当权值等于0时,表面该点进入了集合U中 
		se[k].lowcost = 0;
		//5、重新调整 
		for(int j=1;j<p.vertexNum;j++){
    
    
			if(se[j].lowcost > p.arc[k][j]){
    
    
				se[j].lowcost = p.arc[k][j];
				se[j].adjvex = k;
			}
		}
	} 
}

3.1 Kruskal算法
设置一个结构体,存放每个边的起点和终点以及权值大小

struct EdgeType{
    
    
	int from;
	int to;
	int weight;
};

构造函数的算法

EdgeGraph::EdgeGraph(char a[],int n,int e){
    
    
	vertexNum = n;
	edgeNum = e;
	int from,to,weight;
	for(int i=0;i<vertexNum;i++)
		vertex[i] = a[i];
	for(int j=0;j<edgeNum;j++){
    
    
		cout<<"请输入每两棵树之间的序号以及权值: "<<endl;
		cin>>from>>to>>weight;
		edge[j].from = from;
		edge[j].to = to;
		edge[j].weight = weight;
	}
	InsertSort(edge,edgeNum);
}

在输入之后,为了方便操作,要对数组进行排序,按权值从小到大排序
这里我们使用插入排序:

void EdgeGraph::InsertSort(EdgeType edge[],int e){
    
    
	int j;
	for(int i=1;i<e;i++){
    
    
		int from = edge[i].from;
		int to = edge[i].to;
		int weight = edge[i].weight;
		for(j=i-1;j>=0;j--){
    
    
			if(weight<edge[j].weight){
    
    
				edge[j+1].from = edge[j].from;
				edge[j+1].to = edge[j].to;
				edge[j+1].weight = edge[j].weight;
			}
			else
				break;
		}
		edge[j+1].from = from;
		edge[j+1].to = to;
		edge[j+1].weight = weight;		
	}
}

kruskal算法
伪代码:
初始化辅助数组parent[]
循环依次考察每一条边

  • vex1获取所在生成树的根节点from
  • vex2获取所在生成树的根节点to
  • 如果vex1 != vex2,将vex1存入到vex2中,num++
  • 判断如果已满,算法结束
int FindRoot(int parent[],int v){
    
    
	while(parent[v]>-1){
    
    
		v = parent[v];
	}
	return v;
}
void Kruskal(EdgeGraph eg){
    
    
	int parent[eg.vertexNum];
	int num = 0;
	for(int i=0;i<eg.vertexNum;i++)
		parent[i] = -1;
	for(int i=0;i<eg.edgeNum;i++){
    
    
		int vex1 = FindRoot(parent,eg.edge[i].from);
		int vex2= FindRoot(parent,eg.edge[i].to);
		if(vex1 != vex2){
    
    
			cout<<" ( "<<eg.vertex[eg.edge[i].from]<<"->"<<eg.vertex[eg.edge[i].to]<<" ) "<<eg.edge[i].weight<<endl; 
			parent[vex2] = vex1;
			num++;
			if(num == eg.vertexNum-1)
				return;
		}
	}
}

四、图的应用之最短路径

猜你喜欢

转载自blog.csdn.net/Cristiano_san/article/details/108200475