算法学习(五)图算法学习总结

图算法

1、图的表示

1.1、邻接矩阵(有向图、无向图、带权图、代码实现)

1、无向图的邻接矩阵
在这里插入图片描述
2、有向图的邻接矩阵
在这里插入图片描述
3、带权值的图
在这里插入图片描述

有了上述的理解,我们可以设计数据结构,并实现了。C++实现如下:

#include<iostream>
#include <iomanip>//setw格式化函数头文件
using namespace std;
template<typename T1,typename T2>
class graph{
	private:
		const static int MAXVEX = 100;
		const static int MaxValue = 65535; //要加static
		bool  flag;//标记是否创建无向图 ,默认是 
		int numVertexes,numEdges;//顶点个数、边数;
		T2 arc[MAXVEX][MAXVEX]; //邻接矩阵
		T1 vexs[MAXVEX];//顶点数组
	public:
		graph(int numVertexes,int numEdges,bool flag);//构造函数 ,这里无需写默认参数 
	public: 
		void createMGraph();//构建图	
		void displayEdge();//展示邻接矩阵
		
};
template<typename T1,typename T2>
graph<T1,T2>::graph(int numVertexes,int numEdges,bool flag=true){ //默认参数必须放最右边 
	this->numEdges = numEdges;
	this->numVertexes = numVertexes;//初始化值
	this->flag = flag;
	createMGraph();	 	
}
template<typename T1,typename T2>
void graph<T1,T2>::createMGraph(){
	cout<<"开始创建图,顶点个数为"<<this->numVertexes
			<<" ;边数为"<<this->numEdges<<":"<<endl;
	cout<<"输入顶点编号:";
	for(int i=0;i<this->numVertexes;i++){//顶点表初始化 
		cin>>vexs[i];
	}
	for(int i=0;i<this->numVertexes;i++){
		for(int j=0;j<this->numVertexes;j++)
			this->arc[i][j] = this->MaxValue;//邻接矩阵权值初始化 
	}
	cout<<"输入边(vi,vj)的下标i,j和权值w:"<<endl;
	int i,j,w;
	for(int k=0;k<this->numEdges;k++){
		cin>>i>>j>>w;
		this->arc[i][j]=w;
		if(this->flag==true) //是否构建无向图 
			this->arc[j][i]=w;//对称矩阵 
	}
	cout<<"创建完成!"<<endl; 
	return;
	
}
template<typename T1,typename T2>
void graph<T1,T2>::displayEdge(){
	for(int i=0;i<this->numVertexes;i++){
		for(int j=0;j<this->numVertexes;j++)
			cout<<setw(5)<<this->arc[i][j]<<"\t";//5位对齐 
		cout<<endl;
	}
	return;
}
int main(){
	graph<int,int> mygraph(4,5,false); 
	mygraph.displayEdge();
	return 0;
}

在这里插入图片描述

在这里插入图片描述

1.2、 邻接表

1、邻接表的提出
在这里插入图片描述
2、无向图的邻接表
在这里插入图片描述
在这里插入图片描述
3、有向图的邻接表(分出边表、入边表)
在这里插入图片描述
4、带权图的处理
在这里插入图片描述

有了上面的邻接表的理解,我们可以实现代码(java):

package dataStruct;

import java.util.Scanner;

/**
 * 邻接表_无向图、有向图、带权图实现实现
 */
public class GraphAdjList {
    private static final int MAXSIZE = 100;//默认最大顶点个数为100
    private int numVertexes;//顶点个数
    private int numEdges;//边数
    public boolean flag;//标记是否为无向图,java类里边默认初始化为true

    public VertexNode[] adjList;

    /**
     * 构造器
     * @param numVertexes 顶点个数
     * @param numEdges 边数
     */
    public GraphAdjList(int numVertexes,int numEdges,boolean flag) {
        this.numVertexes = numVertexes;
        this.numEdges = numEdges;
        this.flag = flag;
        adjList = new VertexNode[MAXSIZE];//为了后面的插入
    }
    public void CreateALGraph(){
        Scanner input = new Scanner(System.in);
        String info = "无向图";
        if(!flag)
            info = "有向图";
        System.out.println("创建"+info+",顶点个数:"+this.numVertexes+"边数:"+this.numEdges);
        System.out.println("开始输入顶点信息:");
        for(int i=0;i<this.numVertexes;i++){
            int data = input.nextInt();
            this.adjList[i] = new VertexNode(data,null);
        }
        System.out.println("开始输入边(vi,vj)的顶点序号和边上的权值w:");
        for(int k=0;k<this.numEdges;k++){
            int i=input.nextInt();
            int j=input.nextInt();//1 3 2,1 2 4
            int weight = input.nextInt();

            EdgeNode e = new EdgeNode(j, weight);
            e.next = this.adjList[i].firstEdge;//头插法,e节点指向原来头结点指向的地址
            this.adjList[i].firstEdge = e;//链接!

            if(flag){//如果是无向图
                e = new EdgeNode(i,weight);
                e.next = this.adjList[j].firstEdge;
                this.adjList[j].firstEdge = e;
            }
        }
        System.out.println("创建完成!");
    }
    public void displayALGraph(){
        for(int i=0;i<this.numVertexes;i++){
            if(adjList[i].firstEdge!=null){
                EdgeNode e = adjList[i].firstEdge;
                while(e!=null){
                    System.out.print("顶点:"+this.adjList[i].data+"邻接顶点:");//打印顶点信息
                    System.out.println(e.adjvex+"权值:"+e.weight);//打印该顶点其邻接顶点信息
                    e = e.next;
                }
            }
        }
    }
    static class VertexNode{//顶点结构
        int data;//顶点信息
        EdgeNode firstEdge;//边表头指针
        public VertexNode(int data,EdgeNode firstEdge){
            this.data = data;
            this.firstEdge = firstEdge;
        }
    }
    static class EdgeNode{//边表
        public int adjvex;//邻接顶点的下标
        public int weight;//权重,可以考虑初始化为无穷大代表不存在。
        public EdgeNode next;//链域,指向下一个邻接顶点
        public EdgeNode(int adjvex,int weight){
            this.adjvex = adjvex;
            this.weight = weight;
        }
    }
    public static void main(String[]args){
        GraphAdjList myGraph = new GraphAdjList(5,6,false);//创建5顶点6边的有向图
        myGraph.CreateALGraph();
        myGraph.displayALGraph();
    }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.3、 十字链表与邻接多重表

1、十字链表——解决有向图邻接表结构缺点
在这里插入图片描述
在这里插入图片描述

2、邻接多重表——解决无向图邻接表结构,边的删除麻烦问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4、边集数组

在这里插入图片描述

2、图的遍历

2.1、DFS(深度优先搜索、递归算法)

在这里插入图片描述

在这里插入图片描述
基于邻接矩阵的DFS

template<typename T1,typename T2>
void graph<T1,T2>::DFS(int i){
	this->visited[i]=true;//标记当前顶点已经遍历
	cout<<this->vexs[i];//打印顶点
	for(int j=0;j<this->numVertexes;j++){
		if(this->arc[i][j]!=this->MaxValue && !this->visited[j])//未遍历过
			DFS(j); 
	} 	
}
template<typename T1,typename T2>
void graph<T1,T2>::DFSTraverse(){
	cout<<endl<<"遍历结果:";
	for(int i=0;i<this->numVertexes;i++){
		this->visited[i]=false;//所有点都标记未访问过 
	}
	for(int i=0;i<this->numVertexes;i++){//开始遍历
		if(!this->visited[i])
			DFS(i);
	}
	 
} 

由于是邻接矩阵存储结构,算法时间复杂度O(n2)

基于邻接表的DFS

template<typename T1,typename T2>
void graph<T1,T2>::DFS(int i){//邻接表的话
	this->visited[i]=true;//标记当前顶点已经遍历
	EdgeNode *p;//临时变量 
	cout<<this->adjList[i].data<<" ";打印顶点值
	p = this->adjList[i].firstedge;//获取邻接顶点
	while(p){
		if(!visited[p->adjvex])//该顶点没有遍历过
			DFS(p->adjvex);
		p = p->next;//遍历下一个邻接顶点
	}
}
template<typename T1,typename T2>
void graph<T1,T2>::DFSTraverse(){//基本不变
	cout<<endl<<"遍历结果:";
	for(int i=0;i<this->numVertexes;i++){
		this->visited[i]=false;//所有点都标记未访问过 
	}
	for(int i=0;i<this->numVertexes;i++){//开始遍历
		if(!this->visited[i])
			DFS(i);
	}
	 
}

邻接表使得算法复杂度为O(n+e),n为顶点个数,e为边数。

2.2、BFS(宽度优先搜索、优先队列)

在这里插入图片描述
在这里插入图片描述
对边搜索、不断延展。
邻接矩阵的BFS

void BFSTraverse(MGraph G){
	int i,j;
	Queue Q;
	for(i=0;i<G.numVertexes;i++)
		visited[i] = false;
	InitQueue(&Q);//初始化队列
	for(i=0;i<G.numVertexes;i++){
		if(!visited[i]){
			visited[i] = true;//标记已经访问过了
			cout<<G.vexs[i];//打印顶点
			EnQueue(&Q,i);//入队
			while(!QueueEmpty(Q))//队列不空
			{
				DeQueue(&Q,&i);//队头元素出队
				for(j=0;j<G.numVertexes;j++){
					if(G.arc[i][j] !=this.MaxValue && !visited[j])//当前边的顶点未访问过
					{		visited[j] = true;
							cout<<G.vexs[j];//打印顶点
							EnQueue(&Q,j);//该顶点入队
					}
				}
			}
		}
	}
}

邻接表的BFS

void BFSTraverse(GraphAdjList G){
	int i,j;
	Queue Q;
	EdgeNode *p;//临时变量
	for(i=0;i<G.numVertexes;i++)
		visited[i] = false;
	InitQueue(&Q);//初始化队列
	for(i=0;i<G.numVertexes;i++){
		if(!visited[i]){
			visited[i] = true;//标记已经访问过了
			cout<<G.vexs[i];//打印顶点
			EnQueue(&Q,i);//入队
			while(!QueueEmpty(Q))//队列不空
			{
				DeQueue(&Q,&i);//队头元素出队
				p = G->adjList[i].firstedge;//当前顶点的邻接表头指针
				while(p){
					if(!visited[p->adjvex])//当前边的顶点未访问过
					{		visited[p->adjvex] = true;
							cout<<G->adjList[p->adjvex].data;//打印顶点
							EnQueue(&Q,p->adjvex);//该顶点入队
					}
					p = p->next;//指向下一个邻接顶点
				}
			}
		}
	}
}

2.3、小结

在这里插入图片描述

3、寻找最小生成树(两个贪心算法)

实际问题:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.1、Prim算法(分割法、贪心策略:寻找集合中的顶点所连接边中最小权值边)

在这里插入图片描述
贪心策略、算法分析
在这里插入图片描述

template<typename T1,typename T2>
void graph<T1,T2>::MinSpendTree_Pirm(){
	int min,i,j,k;
	int adjvex[this->MAXVEX];//用来保存顶点的下标
	int lowcost[this->MAXVEX];//保存相关顶点之间的权值
	lowcost[0]=0;//表示v0加入生成树,只要lowcost=0表示对于的顶点加入了生成树
	adjvex[0]=0;//第一个顶点为0
	for(i=1;i<this->numVertexes;i++){
		lowcost[i] = this->arc[0][i];//与V0顶点有边的点权值存入
		adjvex[i] = 0;//初始化为V0下标 
	} 
	for(i=1;i<this->numVertexes;i++){
		min = this->MaxValue;//初始化最小值权值,一般为无穷大
		j=1;k=0;
		while(j<this->numVertexes){//寻找当前顶点中最小的权值,存到k 
			if(lowcost[j]!=0 && lowcost[j]<min){
				min = lowcost[j];
				k=j;
			}
			j++;
		}
		//找到最小权值的顶点,加入最小生成树
		cout<<"当前顶点边中最小权值的是:"<<adjvex[k]<<"-->"<<k; 
		lowcost[k]=0;//将其加入最小生成树
		for(j=1;j<this->numVertexes;j++){
			if(lowcost[j]!=0 && this->arc[k][j]<lowcost[j]){
				lowcost[j]= this->arc[k][j];
				adjvex[j]=k; 
			}
		} 
	} 	
}

在这里插入图片描述

3.2、 Kruskal算法(边集数组、每次从剩余边选择最小权值边)

在这里插入图片描述
图解分析:
在这里插入图片描述
基于分析我们可以写出代码:

template<typename T1,typename T2>
void graph<T1,T2>::MinSpanTree_Krusal(){
	if(!this->flag){
		cout<<"该方法暂时仅限无向图使用";
		return;
	}		
	int parent[this->MAXVEX]={0};//创建一个数组,下标i对于边的起点start,存放的值对应end 
	int i,n,m,count=0;//count记录最小权值 
	//将无向图的邻接矩阵转化为边集数组,注意权值排序。
	typedef struct{
		int begin;
		int end;
		int weight;
	}Edge;
	Edge edges[this->numEdges];//定义边集数组
	int edgenum = this->numEdges,e=0;
	for(i=0;i<this->numVertexes;i++){
		for(int j=0;j<this->numVertexes && e<edgenum;j++){
				if(this->arc[i][j]!=this->MaxValue &&i<j)//边存在,i<j是无向图里边只计算一次 
				{
				 	edges[e].begin=i;
					edges[e].end = j;
					edges[e].weight = this->arc[i][j];
					e++;
				} 
		}
		
	} 
	//将边集数组按照权值排序
	for(int i=1;i<edgenum;i++){//冒泡排序
		for(int j=0;j<edgenum-i;j++){
			if(edges[j].weight>edges[j+1].weight){
				int temp = edges[j].begin;
				edges[j].begin = edges[j+1].begin;
				edges[j+1].begin = temp;
				temp = edges[j].end;
				edges[j].end = edges[j+1].end;
				edges[j+1].end = temp;
				temp = edges[j].weight;
				edges[j].weight = edges[j+1].weight;
				edges[j+1].weight = temp;
			}
		}
	} 
	for(int i=0;i<this->numEdges;i++){
		n = Find(parent,edges[i].begin);
		m = Find(parent,edges[i].end);
		if(n!=m)//不形成闭环
		{
			parent[n] = m;//将其加入生成树
			cout<<" "<<edges[i].begin<<"-->"<<edges[i].end ;
			count+=edges[i].weight;
		} 
	}
	cout<<endl<<"(kruskal)最小生成树代价:"<<count; 
	 	 
}

运行结果:
在这里插入图片描述

4、最短路径问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.1、单源最短路径问题(给定一个点,求到其余各个点的距离)

4.1.1、迪杰特斯拉(Dijkstra)算法(贪心算法)

很像prim算法,利用集合,寻找最短路径。但是有区别。而且用途也不同。这是一个贪心算法。
在这里插入图片描述

基于理解上的算法实现 :

template<typename T1,typename T2>
void graph<T1,T2>::MinPath_Dijkstra(int v0){
	int D[this->numVertexes];//最短路径数组,D[v]表示源点v0到V的最短路径
	bool flag[this->numVertexes] ;//标记集合,flag[v]=true表示点V0到V的最短路径已经求得 
	int p[this->numVertexes];//记录到V的最短路径中倒数第二个顶点
	int k;
	 
	//初始化
	for(int V=0;V<this->numVertexes;V++){
		D[V] = this->arc[v0][V];//默认初始化为其边的权值
		flag[V]=false;
		p[V]=0; 
		
	} 
	D[v0]=0;
	flag[v0]=true;//源点到源点路径不需要记录
	p[v0]=0;
	int min;
	//开始查找最短路径
	for(int V=1;V<this->numVertexes;V++){//控制选择到V的最短路径 
		min = this->MaxValue-1;
		for(int w=0;w<this->numVertexes;w++){//找到当前离点v0最近的点,且其最短路径还没找到 
			if(!flag[w]&&D[w]<min){
				min=D[w];
				k=w;
			}
		}
		flag[k]=true;//加入集合里边 
		//调整D[W],即每找到一个点加入路径,就调整一次源点到其余未加入最短路径的顶点的D[W] 
		for(int w=0;w<this->numVertexes;w++){
			if(!flag[w]&& (min+this->arc[k][w]<D[w]))
			{
					D[w]=min+this->arc[k][w];//记录下来 
					p[w]=k;
			} 
			
		}//找到最短路径、倒数第二个顶点k	
	}
	//打印最短路径 
	for(int i=0;i<this->numVertexes;i++){
		k=i;
		cout<<""<<v0<<"--->"<<i<<"的最短路径为:";
	 
		while(k!=v0){
			cout<<k<<"<--";
			k=p[k];	
		}
		cout<<v0<<" 长度为"<<D[i]<<endl;
	
		
	}
	return ; 
}

在这里插入图片描述
未优化的算法复杂度很高
在这里插入图片描述

4.1.2、Floyd算法(动态规划)

在这里插入图片描述
在这里插入图片描述

4.1.3、Bellman-Ford 算法

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值