算法与数据结构基础-图

图是由 顶点集合 以及 顶点间的关系集合 组成的一种数据结构

有向图和无向图
无向图:边(x,y)与(y,x)相同,一条连线相当于有向图顶点间两条
有向图:用箭头标明边的方向为有向图,<x,y>,与<y,x>不同,<x,y>表示从顶点x发向顶点y的边,x为始点,y为终点。有向边称为弧,x为弧尾,y为弧头

完全图
图中顶点与任意顶点都有连线
无向图:有n个顶点,n(n-1)/2条边
有向图:n个顶点,n(n-1)条边

稠密图和稀疏图
当一个图接近完全图,称为稠密图
一个图含有较少的边或弧,称为稀疏图


与边或弧有关的数据信息称为权,用于表示某种含义(顶点关系等),提供算法依据
带权图称为网络,分有向,无向网络

子图
G1图是G2图的子集,那么G1是G2的子图(根据方式,不唯一)

度,出度和入度
无向图中依附顶点的边的数目为顶点的度
有向图中,指向顶点的弧的数目为入度,从顶点发出的弧的数目为出度

邻接点(无向图)
一条边连接的两个顶点,互为邻接点

连通图(无向图)
无向图中每个顶点都有通向任意顶点的连线(可直接或间接)
连通分量:无向图的极大联通子图,即不连通的部分

路径,回路
一个顶点到另一个顶点方式称为路径,路径上边的数目称为路径长度
简单路径:路径上除第一个和最后一个顶点外,其他顶点不重复
复杂路径:路径上有第一个或最后一个顶点重复
回路:起点和终点相同的路径,称为回路或环,分有向或无向,简单和复杂

强连通图
有向图中,对于两个顶点间,都存在从起点到终点和从终点回顶点的路径,称为强连通图(返回路径与出发不同)
强连通分量:有向图的极大强连通子图(强连通图只有其本身,非强有多个

生成树(无向图)
无向图的极小连通子图,包含n个顶点,n-1条不构成回路的边(不唯一)

图的存储结构

邻接矩阵表示法:用一个一维数组存放顶点本身信息,再用一个n×n的矩阵(二维数组)表示各个顶点的邻接关系

有向图中一个顶点可以到达另一个顶点用1表示,不可到达用0表示(自身都用0表示)
无向图沿主对角线对称,故记录时可只记录上(下)三角

邻接表表示法
顺序存储与链式存储结合的方式
顶点表示:拥有顶点索引,出弧链表头指针(用顶点找到连接它的弧),顶点数据
弧的表示:弧头顶点索引,下一条弧指针,弧的数据
一个顶点找到它的所有出弧,出弧找到下一顶点(弧头顶点),顶点再找到弧
在这里插入图片描述
在这里插入图片描述
V1有三个指向,V2无指向,V3指向索引为3的V4,v4指向索引为0的V1

图的遍历

深度优先搜索
类似前序遍历,先一端搜索到最深处,再返回,直到形成回路,再从另一支搜索,下图搜索过程为A B C E F D G H
在这里插入图片描述

广度优先搜索
即广泛的搜索,按层搜索,没有回溯过程
下图搜索过程为A B D C F G H E
在这里插入图片描述

图的生成树和最小生成树

最小生成树,当图的路径有权且很复杂时,按最小的权得到的遍历全图的路径即为最小生成树(类比城市间修路,因地形不同价格不同,最低价的方式)

最小生成树算法

普里姆算法
点,边集合:已经选入的点和边
待选边集合:每次到一个点,比对与点集合连接的所有边的权,通过最小权的边找下一个点
克鲁斯卡尔算法
先把所有边都纳入待选边集合,从权最小的边开始选,得到两个点,然后选次小边得到点,判断次小边是否与已选边形成回路,形成回路则删除当前的边。当所有点都涉及,所有点集合已经合并,则得到最小生成树

图的邻接矩阵编码实现

顶点实现

class Node
{
public:
	Node(char data = 0){
	m_cData = data;
	m_bIsVisited = false;
	}
	char m_cData;		//数据
	bool m_bIsVisited;	//是否被访问过
};

边的实现(实现边的权,用于最小生成树算法)

class Edge
{
public:
	Edge(int nodeIndexA=0,int nodeIndexB=0,int weightValue=0){
	m_iNodeIndexA = nodeIndexA;
	m_iNodeIndexB = nodeIndexB;
	m_iWeightValue = weightValue;
	m_bSelected = false;		//初始都为未被选中
	}
	int m_iNodeIndexA;			//与边相连的两个节点A,B
	int m_iNodeIndexB;			
	int m_iWeightValue;			//权值
	bool m_bSelected;			//是否被选中
};

图的实现

class CMap
{
public:
	CMap(int capacity);
	~CMap();
	bool addNode(Node *pNode);		//向图中添加顶点
	void resetNode();				//重置顶点(全标记为未访问)
	bool setValueToMatrixForDirectGraph(int row, int col, int val = 1);		//为有向图设置邻接矩阵
	bool setValueToMatrixForUndirectGraph(int row, int col, int val = 1);	//为无向图设置邻接矩阵
	void printMatrix();		//打印邻接矩阵
	void depthFirstTraverse(int nodeIndex);		//深度优先遍历
	void breathFirstTraverse(int nodeIndex);	//广度优先遍历

	void primTree(int nodeIndex);				//普里姆生成树
	void kruskalTree();							//克鲁斯卡尔生成树
private:
	bool getValueFromMatrix(int row, int col, int &val);	//从矩阵中获取信息(是否相连或权值)
	void breathFirstTraverseImpl(vector<int> preVec);		//广度优先遍历实现函数
	int getMinEdge(vector<Edge> edgeVec);							//获取最小权值的边
	bool isInSet(vector<int>nodeSet, int target);					//是否在集合中存在
	void mergeNodeSet(vector<int> &nodeSetA, vector<int> &nodeSetB);//合并集合
private:
	int m_iCapacity;		//最多容纳的顶点数
	int m_iNodeCount;		//已添加的顶点个数
	Node *m_pNodeArray;		//存放顶点数组
	int *m_pMatrix;			//存放邻接矩阵

	Edge *m_pEdge;			//存放最小边的指针
};
CMap::CMap(int capacity){
	m_iCapacity = capacity;
	m_iNodeCount = 0;
	m_pNodeArray = new Node[m_iCapacity];			//存放顶点的数组
	m_pMatrix = new int[m_iCapacity*m_iCapacity];	//邻接矩阵
	//memset(m_pMatrix, 0, m_iCapacity*m_iCapacity*sizeof(int));	//memset将邻接矩阵初始化为0
	for (int i = 0; i < m_iCapacity*m_iCapacity; i++){
		m_pMatrix[i] = 0;
	}
	
	m_pEdge = new Edge[m_iCapacity - 1];		//最小生成树的边的个数为点的个数减一
}

CMap::~CMap(){
	delete[]m_pNodeArray;
	delete[]m_pMatrix;
}

bool CMap::addNode(Node *pNode){
	if (pNode == NULL){
		return false;
	}
	m_pNodeArray[m_iNodeCount].m_cData = pNode->m_cData;	//传入数据
	m_iNodeCount++;	
	return true;
}

void CMap::resetNode(){
	for (int i = 0; i < m_iNodeCount; i++){
		m_pNodeArray[i].m_bIsVisited = false;
	}
}

bool CMap::setValueToMatrixForDirectGraph(int row, int col, int val){
	if (row < 0 || row >= m_iCapacity){
		return false;
	}
	if (col < 0 || col >= m_iCapacity){
		return false;
	}
	m_pMatrix[row*m_iCapacity + col] = val;		
	return true;
}

bool CMap::setValueToMatrixForUndirectGraph(int row, int col, int val){
	if (row < 0 || row >= m_iCapacity){
		return false;
	}
	if (col < 0 || col >= m_iCapacity){
		return false;
	}
	m_pMatrix[row*m_iCapacity + col] = val;
	m_pMatrix[col*m_iCapacity + row] = val;//主对角线对称位置
	return true;
}


bool CMap::getValueFromMatrix(int row, int col, int &val){	//val=0不相连,1相连
	if (row < 0 || row >= m_iCapacity){
		return false;
	}
	if (col < 0 || col >= m_iCapacity){
		return false;
	}
	val = m_pMatrix[row*m_iCapacity + col];
	return true;
}

void CMap::printMatrix(){		//打印邻接矩阵
	for (int i = 0; i < m_iCapacity; i++){			
		for (int j = 0; j < m_iCapacity; j++){			
			cout << m_pMatrix[i*m_iCapacity + j] << " ";
		}
		cout << endl;		
	}
}

//深度优先遍历
void CMap::depthFirstTraverse(int nodeIndex){
	int value = 0;										//存储弧信息
	cout << m_pNodeArray[nodeIndex].m_cData << " ";		//访问当前节点
	m_pNodeArray[nodeIndex].m_bIsVisited = true;		//将当前节点标识为已访问
	for (int i = 0; i < m_iCapacity; i++){				//遍历与当前节点所连接的节点	
		getValueFromMatrix(nodeIndex, i, value);		//有无弧与当前点相连
		if (value == 1){								//节点与当前节点相连		
			if (m_pNodeArray[i].m_bIsVisited == true){	//节点是否访问过			
				continue;								
			}
			else{
				depthFirstTraverse(i);					//对当前找到的节点进行深度优先遍历(递归)
			}
		}
		else{
			continue;									//与当前节点不相连,寻找下一个节点
		}
	}
}

//广度优先遍历
void CMap::breathFirstTraverse(int nodeIndex){
	cout << m_pNodeArray[nodeIndex].m_cData << " ";		//访问当前节点
	m_pNodeArray[nodeIndex].m_bIsVisited = true;		//标识置为已访问

	vector<int>curVec;									//定义数组用于存储节点索引
	curVec.push_back(nodeIndex);						//将当前节点加入到数组中
	breathFirstTraverseImpl(curVec);					//实现递归
}

void CMap::breathFirstTraverseImpl(vector<int> preVec){		//preVec上一次保存的节点
	int value=0;											//存储弧信息
	vector<int>curVec;										//数组存储当前层的节点
	for (int j = 0; j < (int)preVec.size(); j++){			//遍历前一层所有的节点	
		for (int i = 0; i < m_iCapacity;i++){				//找与当前节点相连的节点		
			getValueFromMatrix(preVec[j], i, value);		//获取弧连接信息
			if (value != 0){								//与当前节点相连			
				if (m_pNodeArray[i].m_bIsVisited == true){  //判断是否访问过				
					continue;
				}
				else{
					cout << m_pNodeArray[i].m_cData << " "; //访问节点的数据
					m_pNodeArray[i].m_bIsVisited = true;	//将节点置为已访问
					curVec.push_back(i);					//将节点添加到储存层节点的数组中
				}
			}
		}
	}
	if (curVec.size() == 0){								//数组中是否还有元素	
		return;												
	}
	else{
		breathFirstTraverseImpl(curVec);					//有元素,下一层
	}
}

普利姆算法

void CMap::primTree(int nodeIndex){
	int value = 0;				
	int edgeCount=0;			//已选择的边数
	vector<int> nodeVec;		//点的集合
	vector<Edge> edgeVec;		//边的集合

	cout << m_pNodeArray[nodeIndex].m_cData << endl;	//输出当前节点数据
	nodeVec.push_back(nodeIndex);						//将当前节点存入点集合
	m_pNodeArray[nodeIndex].m_bIsVisited = true;		//将当前节点置为已访问
	//获取所有的待选边
	while (edgeCount < m_iCapacity - 1){
		int temp = nodeVec.back();						//取出尾部节点
		for (int i = 0; i < m_iCapacity; i++){
			getValueFromMatrix(temp, i, value);			//得到弧信息
			if (value != 0){							//有相连的边
				if (m_pNodeArray[i].m_bIsVisited){		//节点 i 是否已访问过				
					continue;							
				}
				else{
					Edge edge(temp, i, value);			//定义边
					edgeVec.push_back(edge);			//将边添加到待选边中
				}
			}
		}
		//从可选边集合中找出最小边
		int edgeIndex=getMinEdge(edgeVec);				//找出权最小边的索引
		edgeVec[edgeIndex].m_bSelected = true;			//将边置为以选择

		cout << edgeVec[edgeIndex].m_iNodeIndexA << "----" << edgeVec[edgeIndex].m_iNodeIndexB << " ";		//输出节点和边
		cout << edgeVec[edgeIndex].m_iWeightValue << endl;

		m_pEdge[edgeCount] = edgeVec[edgeIndex];		//将边存入最小生成树的边集合中
		edgeCount++;									//最小生成树的边集合中边的数目加1

		int nextNodeIndex = edgeVec[edgeIndex].m_iNodeIndexB;	//获取最小边中的另一个节点的索引
		nodeVec.push_back(nextNodeIndex);						//将点的索引加入到点集合中
		m_pNodeArray[nextNodeIndex].m_bIsVisited = true;		//将点置为已访问
		cout << m_pNodeArray[nextNodeIndex].m_cData << endl;
	}
}

int CMap::getMinEdge(vector<Edge> edgeVec){
	int minWeight = 0;			//最小权值
	int edgeIndex = 0;			//边的索引
	int i=0;
	for (; i < edgeVec.size(); i++){			//找到第一条未被选择的边
		if (!edgeVec[i].m_bSelected){
			minWeight = edgeVec[i].m_iWeightValue;		//获取权值
			edgeIndex = i;								//获取边的索引
			break;
		}
	}
	if (minWeight == 0){
		return -1;
	}
	for (; i < edgeVec.size(); i++){			//找到最小边的索引
		if (edgeVec[i].m_bSelected){
			continue;
		}
		else{
			if (minWeight > edgeVec[i].m_iWeightValue){
				minWeight = edgeVec[i].m_iWeightValue;
				edgeIndex = i;
			}
		}
	}
	return edgeIndex;									//最小边的索引
}

克鲁斯卡尔算法

void CMap::kruskalTree(){
	int value = 0;			
	int edgeCount = 0;							//存放生成树中边的个数	
	vector<vector<int>> nodeSets;				//定义存放节点集合的数组,已选顶点不一定在同一个集合
	vector<Edge> edgeVec;						//取出所有边
	for (int i = 0; i < m_iCapacity; i++){		//对称矩阵,取出所有右上方的边	
		for (int j = i + 1; j < m_iCapacity; j++){
			getValueFromMatrix(i, j, value);
			if (value != 0){
				Edge edge(i, j, value);
				edgeVec.push_back(edge);
			}
		}
	}	
	while (edgeCount < m_iCapacity - 1){			//从所有边中取出组成最小生成树的边	
		int minEdgeIndex=getMinEdge(edgeVec);		//获得最小边
		edgeVec[minEdgeIndex].m_bSelected = true;	
		int nodeAIndex = edgeVec[minEdgeIndex].m_iNodeIndexA;		//获取最小边的两个顶点
		int nodeBIndex = edgeVec[minEdgeIndex].m_iNodeIndexB;	
		bool nodeAIsInSet = false;				//点A,B是否在点集合中的标志
		bool nodeBIsInSet = false;	
		int nodeAInSetLabel = -1;				//点A,B所在的集合的索引
		int nodeBInSetLabel = -1;				

		
		for (int i = 0; i < (int)nodeSets.size(); i++){		//找出点所在的点集合
			nodeAIsInSet=isInSet(nodeSets[i], nodeAIndex);	//A是否在点集合中
			if (nodeAIsInSet){
				nodeAInSetLabel = i;						//获取A所在的点集合的索引
			}
		}
		for (int i = 0; i < (int)nodeSets.size(); i++){		//B点同上
			nodeBIsInSet = isInSet(nodeSets[i], nodeBIndex);
			if (nodeBIsInSet){
				nodeBInSetLabel = i;						
			}
		}
		if (nodeAInSetLabel == -1 && nodeBInSetLabel == -1){//A与B都不在点集合中	
			vector<int> vec;
			vec.push_back(nodeAIndex);						//将A和B都存在到点集合中
			vec.push_back(nodeBIndex);
			nodeSets.push_back(vec);						//点集合存到点数组中
		}
		else if (nodeAInSetLabel == -1 && nodeBInSetLabel != -1){	//只有A不在点集合中
			nodeSets[nodeBInSetLabel].push_back(nodeAIndex);		//将A存到B所在的点集合中
		}
		else if (nodeAInSetLabel != -1 && nodeBInSetLabel == -1){	//B点同上
			nodeSets[nodeAInSetLabel].push_back(nodeBIndex);		
		}
		else if (nodeAInSetLabel != -1 && nodeBInSetLabel != -1 && nodeAInSetLabel != nodeBInSetLabel){		//A,B在点不同点集合中
			mergeNodeSet(nodeSets[nodeAInSetLabel], nodeSets[nodeBInSetLabel]);			//将B与A所在的集合合并
			for (int k = nodeBInSetLabel; k < (int)nodeSets.size() - 1; k++){			//删除B所在的点集合
				nodeSets[k] = nodeSets[k + 1];
			}
		}
		else if (nodeAInSetLabel != -1 && nodeBInSetLabel != -1 && nodeAInSetLabel == nodeBInSetLabel){    //A和B在同一个点集合中
			continue;					//构成回路,放弃当前边
		}
		m_pEdge[edgeCount] = edgeVec[minEdgeIndex];			//将最小边添加到生成树中
		edgeCount++;

		cout << edgeVec[minEdgeIndex].m_iNodeIndexA << "--" << edgeVec[minEdgeIndex].m_iNodeIndexB << " ";		//输出边和权值
		cout << edgeVec[minEdgeIndex].m_iWeightValue << endl;
	}
}

bool CMap::isInSet(vector<int>nodeSet, int target){		//点是否在点集合中
	for (int i = 0; i <(int)nodeSet.size(); i++){
		if (nodeSet[i] == target){
			return true;
		}
	}
	return false;
}

void CMap::mergeNodeSet(vector<int> &nodeSetA, vector<int> &nodeSetB){	//合并点集合
	for (int i = 0; i <(int)nodeSetB.size(); i++){
		nodeSetA.push_back(nodeSetB[i]);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jason 20

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

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

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

打赏作者

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

抵扣说明:

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

余额充值