C++数据结构之:图Graph

摘要:

  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容器使用)

  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值