图
图是由 顶点集合 以及 顶点间的关系集合 组成的一种数据结构
有向图和无向图
无向图:边(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]);
}
}