摘要:
it人员无论是使用哪种高级语言开发东东,想要更高效有层次的开发程序的话都躲不开三件套:数据结构,算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合,即带“结构”的数据元素的集合,“结构”就是指数据元素之间存在的关系,分为逻辑结构和存储结构。
此系列专注讲解数据结构数组、链表、队列、栈、树、哈希表、图,通过介绍概念以及提及一些可能适用的场景,并以C++代码简易实现,多方面认识数据结构,最后为避免重复造轮子会浅提对应的STL容器。本文介绍的是图Graph。
(开发环境:VScode,C++17)
关键词
: C++,数据结构,图,Graph
声明:
本文作者原创,转载请附上文章出处与本文链接。
(文章目录:)
正文:
介绍:
图是相对复杂的一种数据结构,由顶点和连接每对顶点的边所构成的图形就是图,图中的圆圈叫作“顶点”(Vertex,也叫“结点”),连接顶点的线叫作“边”(Edge)。
图按照顶点指向的方向可分为无向图和有向图,像上面的就叫无向图。 如果图中任意两个顶点之间的边都是无向边,则称该图为无向图,无向图相关概念:顶点、边;
如果图中任意两个顶点之间的边都是有向边,则称该图为有向图,有向图相关概念:顶点、弧(弧头、弧尾,出度、入度)。
图在存储数据上有着比较复杂和高效的算法,分别有邻接矩阵 、邻接表、十字链表、邻接多重表、边集数组等存储结构。常见的图遍历算法就是广度优先算法和深度优先算法。
特性:
- 多对多关系:图是一种数据结构,其中的数据元素(称为顶点)之间存在多对多关系。这意味着任意两个顶点之间都可能存在关联。
- 有向与无向:图可以是有向的或无向的。在有向图中,边具有方向,表示从一个顶点到另一个顶点的单向关系;而在无向图中,边没有方向,表示顶点之间的双向关系。
- 度数:顶点的度数是与该顶点相连的边的数量。在无向图中,度数等于该顶点的边数;在有向图中,度数分为入度和出度,分别表示指向该顶点的边数和从该顶点出发的边数。
- 权重:在某些图中,与边或弧相关的数(称为权重)可以表示从一个顶点到另一个顶点的距离、耗费或其他度量。
应用:
- 社交网络分析:图结构可以用来表示社交网络中的用户和他们之间的关系。通过分析社交网络图,可以揭示社区结构、影响力传播和关键节点等信息。
- 推荐系统:图结构可以用于构建推荐系统。通过分析用户之间的关系和物品之间的关系,可以为用户提供个性化的推荐。
- 交通网络优化:图结构可以用于优化交通网络。通过分析道路之间的连接和交通流量,可以提供更高效的交通规划和路线推荐。
代码实现:
#ifndef CGRAPH_H
#define CGRAPH_H
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
// 顶点
template<class T>
class Node
{
public:
Node(T data = 0)
{
m_cData = data;
m_bVisited = false;
}
Node<T>(const Node<T>& node)
{
if (this == &node)
return;
*this = node;
}
Node<T>& operator=(const Node<T>& node);
public:
T m_cData; // 数据
bool m_bVisited; // 是否访问
};
template <class T>
inline Node<T> &Node<T>::operator=(const Node<T> &node)
{
if (this == &node)
return *this;
this->m_cData = node.m_cData;
this->m_bVisited = node.m_bVisited;
return *this;
}
//边
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) {}
Edge(const Edge& edge)
{
if (this == &edge)
return;
*this = edge;
}
Edge& operator=(const Edge& edge);
public:
int m_iNodeIndexA; // 头顶点
int m_iNodeIndexB; // 尾顶点
int m_iWeightValue; // 权重
bool m_bSelected; // 是否被选中
};
Edge &Edge::operator=(const Edge &edge)
{
if (this == &edge)
return *this;
this->m_iNodeIndexA = edge.m_iNodeIndexA;
this->m_iNodeIndexB = edge.m_iNodeIndexB;
this->m_iWeightValue = edge.m_iWeightValue;
this->m_bSelected = edge.m_bSelected;
return *this;
}
// 图
template <class T>
class CMap
{
private:
int m_iCapacity; // 顶点总数
int m_iNodeCount; // 当前顶点数量
Node<T> *m_pNodeArray; // 顶点集合
int *m_pMatrix; // 邻接距阵
Edge *m_pEdgeArray; // 最小生成树边集合
private:
void breadthFirstTraverseImpl(vector<int> preVec); // 广度遍历具体实现
int getMinEdge(const vector<Edge>& edgeVec); // 取最小边
bool isInSet(const vector<int>& nodeSet, int target);
void mergeNodeSet(vector<int>& nodeSetA, const vector<int>& nodeSetB);
public:
CMap(int iCapacity);
~CMap();
// 添加顶点
void addNode(Node<T> *node);
// 将顶点访问设置默认
void resetNode();
// 设置权重-有向图
bool setValueToMatrixForDirectedGraph(int row, int col, int val = 1);
// 设置权重-无向图
bool setValueToMatrixForUndirectedGraph(int row, int col, int val = 1);
// 获取权重
bool getValueFromMatrix(int row, int col, int& val);
// 打印矩阵
void printMatrix();
void depthFirstTraverse(int index); // 深度遍历
void breadthFirstTraverse(int index); // 广度遍历
void primTree(int index); // 求最小生成树-普里斯算法
void kruskalTree(); // 最小生成树-克鲁斯卡尔算法
};
template <class T>
inline void CMap<T>::breadthFirstTraverseImpl(vector<int> preVec)
{
int val = 0;
vector<int> curVec;
for (int i = 0; i < (int)preVec.size(); i++) {
for (int j = 0; j < m_iCapacity; j++) {
getValueFromMatrix(preVec[i], j, val);
if (0 != val) {
if (m_pNodeArray[j].m_bVisited)
continue;
cout << m_pNodeArray[j].m_cData << " ";
m_pNodeArray[j].m_bVisited = true;
curVec.push_back(j);
}
else
continue;
}
}
if (curVec.empty())
return;
else
breadthFirstTraverseImpl(curVec);
}
template <class T>
inline int CMap<T>::getMinEdge(const vector<Edge> &edgeVec)
{
int min = 0, minEdge = 0;
for (int i = 0; i < (int)edgeVec.size(); i++) {
if (edgeVec[i].m_bSelected)
continue;
min = edgeVec[i].m_iWeightValue;
minEdge = i;
}
for (int i = 0; i < (int)edgeVec.size(); i++) {
if (edgeVec[i].m_bSelected)
continue;
if (min > edgeVec[i].m_iWeightValue) {
min = edgeVec[i].m_iWeightValue;
minEdge = i;
}
}
if (0 == min)
return -1;
return minEdge;
}
template <class T>
inline bool CMap<T>::isInSet(const vector<int> &nodeSet, int target)
{
for (int i = 0; i < (int)nodeSet.size(); i++) {
if (nodeSet[i] == target)
return true;
}
return false;
}
template <class T>
inline void CMap<T>::mergeNodeSet(vector<int> &nodeSetA, const vector<int> &nodeSetB)
{
for (size_t i = 0; i < (int)nodeSetB.size(); i++) {
nodeSetA.push_back(nodeSetB[i]);
}
}
template <class T>
inline CMap<T>::CMap(int iCapacity)
{
m_iCapacity = iCapacity;
m_iNodeCount = 0;
m_pNodeArray = new Node<T>[m_iCapacity];
m_pMatrix = new int[m_iCapacity*m_iCapacity];
memset(m_pMatrix, 0, m_iCapacity*m_iCapacity * sizeof(int));
m_pEdgeArray = new Edge[m_iCapacity - 1];
}
template <class T>
inline CMap<T>::~CMap()
{
delete[]m_pNodeArray;
delete[]m_pMatrix;
delete[]m_pEdgeArray;
}
template <class T>
inline void CMap<T>::addNode(Node<T> *node)
{
if(node == nullptr)
return;
m_pNodeArray[m_iNodeCount].m_cData = node->m_cData;
m_iNodeCount++;
}
template <class T>
inline void CMap<T>::resetNode()
{
for (int i = 0; i < m_iNodeCount; i++)
m_pNodeArray[i].m_bVisited = false;
}
template <class T>
inline bool CMap<T>::setValueToMatrixForDirectedGraph(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;
}
template <class T>
inline bool CMap<T>::setValueToMatrixForUndirectedGraph(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;
}
template <class T>
inline bool CMap<T>::getValueFromMatrix(int row, int col, int &val)
{
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;
}
template <class T>
inline void CMap<T>::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;
}
}
template <class T>
inline void CMap<T>::depthFirstTraverse(int index)
{
int val = 0;
cout << m_pNodeArray[index].m_cData << " ";
m_pNodeArray[index].m_bVisited = true;
for (int i = 0; i < m_iCapacity; i++) {
getValueFromMatrix(index, i, val);
if (0 != val) {
if (m_pNodeArray[i].m_bVisited)
continue;
depthFirstTraverse(i);
}
else
continue;
}
}
template <class T>
inline void CMap<T>::breadthFirstTraverse(int index)
{
cout << m_pNodeArray[index].m_cData << " ";
m_pNodeArray[index].m_bVisited = true;
vector<int> curVec;
curVec.push_back(index);
breadthFirstTraverseImpl(curVec);
}
template <class T>
inline void CMap<T>::primTree(int index)
{
int val = 0;
int iEdgeCount = 0;
vector<Edge> edgeVec;// 待选边集合
// 从传入点开始找
vector<int> nodeIndexVec;
nodeIndexVec.push_back(index);
// 结束条件:边数=顶点数-1
while (iEdgeCount < m_iCapacity - 1) {
// 查找传入点的符合要求(权重不为0且目的点没有被访问)边
int row = nodeIndexVec.back();
cout << m_pNodeArray[row].m_cData << endl;
m_pNodeArray[row].m_bVisited = true;
for (int i = 0; i < m_iCapacity; i++){
getValueFromMatrix(row, i, val);
if (0 == val)
continue;
if (m_pNodeArray[i].m_bVisited)
continue;
Edge edge(row, i, val);
edgeVec.push_back(edge);
}
// 取出最小边
int retIndex = getMinEdge(edgeVec);
if (-1 != retIndex) {
// 存储选中边
edgeVec[retIndex].m_bSelected = true;
m_pEdgeArray[iEdgeCount] = edgeVec[retIndex];
cout << m_pNodeArray[m_pEdgeArray[iEdgeCount].m_iNodeIndexA].m_cData << " - ";
cout << m_pNodeArray[m_pEdgeArray[iEdgeCount].m_iNodeIndexB].m_cData << " (";
cout << m_pEdgeArray[iEdgeCount].m_iWeightValue << ") " << endl;
iEdgeCount++;
int iNodeIndex = edgeVec[retIndex].m_iNodeIndexB;
// 设置点被访问
m_pNodeArray[iNodeIndex].m_bVisited = true;
// 存入目的点递归查找
nodeIndexVec.push_back(iNodeIndex);
}
}
}
template <class T>
inline void CMap<T>::kruskalTree()
{
int val = 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, val);
if (0 == val)
continue;
if (m_pNodeArray[i].m_bVisited)
continue;
Edge edge(i, j, val);
edgeVec.push_back(edge);
}
}
// 第二步、从所有边中取出组成最小生成树的边
// 1、算法结束条件:边数=顶点数-1
while (edgeCount < m_iCapacity - 1) {
// 2、从边集合中找出最小边
int retIndex = getMinEdge(edgeVec);
if (-1 != retIndex) {
edgeVec[retIndex].m_bSelected = true;
// 3、找出最小边连接点
int nodeAIndex = edgeVec[retIndex].m_iNodeIndexA;
int nodeBIndex = edgeVec[retIndex].m_iNodeIndexB;
// 4、找出点所在集合
bool nodeAInSet = false;
bool nodeBInSet = false;
int nodeAInSetLabel = -1;
int nodeBInSetLabel = -1;
for (int i = 0; i < (int)nodeSets.size(); i++) {
nodeAInSet = isInSet(nodeSets[i], nodeAIndex);
if (nodeAInSet)
nodeAInSetLabel = i;
}
for (int i = 0; i < (int)nodeSets.size(); i++) {
nodeBInSet = isInSet(nodeSets[i], nodeBIndex);
if (nodeBInSet)
nodeBInSetLabel = i;
}
// 5、根据点集合的不同做不同处理
if (nodeAInSetLabel == -1 && nodeBInSetLabel == -1) {
vector<int> vec;
vec.push_back(nodeAIndex);
vec.push_back(nodeBIndex);
nodeSets.push_back(vec);
}
else if (nodeAInSetLabel == -1 && nodeBInSetLabel != -1) {
nodeSets[nodeBInSetLabel].push_back(nodeAIndex);
}
else if (nodeAInSetLabel != -1 && nodeBInSetLabel == -1) {
nodeSets[nodeAInSetLabel].push_back(nodeBIndex);
}
else if (-1 != nodeAInSetLabel && -1 != nodeBInSetLabel && nodeAInSetLabel != nodeBInSetLabel) {
nodeSets[nodeAInSetLabel].insert(nodeSets[nodeAInSetLabel].end(),
nodeSets[nodeBInSetLabel].begin(),
nodeSets[nodeBInSetLabel].end());
for (int k = nodeBInSetLabel; k < (int)nodeSets.size() - 1; k++) {
nodeSets[k] = nodeSets[k + 1];
}
}
else if (nodeAInSetLabel != -1 && nodeBInSetLabel != -1 && nodeAInSetLabel == nodeBInSetLabel) {
continue;
}
m_pEdgeArray[edgeCount] = edgeVec[retIndex];
edgeCount++;
cout << m_pNodeArray[edgeVec[retIndex].m_iNodeIndexA].m_cData << " - ";
cout << m_pNodeArray[edgeVec[retIndex].m_iNodeIndexB].m_cData << " (";
cout << edgeVec[retIndex].m_iWeightValue << ") " << endl;
}
}
}
#endif // !CGRAPH_H
#include "cgraph.h"
#include <iostream>
using namespace std;
using type = int;
int main()
{
CMap<type> *pMap = new CMap<type>(6);
Node<type> *pNodeA = new Node<type>(1);
Node<type> *pNodeB = new Node<type>(2);
Node<type> *pNodeC = new Node<type>(3);
Node<type> *pNodeD = new Node<type>(4);
Node<type> *pNodeE = new Node<type>(5);
Node<type> *pNodeF = new Node<type>(6);
pMap->addNode(pNodeA);
pMap->addNode(pNodeB);
pMap->addNode(pNodeC);
pMap->addNode(pNodeD);
pMap->addNode(pNodeE);
pMap->addNode(pNodeF);
pMap->setValueToMatrixForUndirectedGraph(0, 1, 7);
pMap->setValueToMatrixForUndirectedGraph(0, 2, 1);
pMap->setValueToMatrixForUndirectedGraph(0, 3, 9);
pMap->setValueToMatrixForUndirectedGraph(1, 2, 2);
pMap->setValueToMatrixForUndirectedGraph(1, 4, 3);
pMap->setValueToMatrixForUndirectedGraph(2, 3, 11);
pMap->setValueToMatrixForUndirectedGraph(2, 4, 8);
pMap->setValueToMatrixForUndirectedGraph(2, 5, 4);
pMap->setValueToMatrixForUndirectedGraph(3, 5, 5);
pMap->setValueToMatrixForUndirectedGraph(4, 5, 15);
cout << "打印矩阵: " << endl;
pMap->printMatrix();
cout << endl;
pMap->resetNode();
cout << "深度优先遍历: " << endl;
pMap->depthFirstTraverse(0);
cout << endl;
pMap->resetNode();
cout << "广度优先遍历: " << endl;
pMap->breadthFirstTraverse(0);
cout << endl;
pMap->resetNode();
cout << "普里姆算法: " << endl;
pMap->primTree(0);
cout << endl;
pMap->resetNode();
cout << "克鲁斯卡尔算法: " << endl;
pMap->kruskalTree();
cout << endl;
pMap->resetNode();
return 0;
}
对应STL:
无,(反正木找到,有懂的老哥可评论 下下)
推荐阅读
C/C++专栏:https://blog.csdn.net/weixin_45068267/category_12268204.html
(内含其它数据结构及对应STL容器使用)