目录
1.数据结构--并查集
2.数据结构--图
1.图的基础概念
2.图的简单实现
2.1.邻接矩阵的图实现
2.2.邻接表的图实现
2.3.图的DFS和BFS
2.4.最小生成树
2.4.1.Kruskal(克鲁斯卡尔算法)
2.4.2.Prim(普里姆算法)
2.5.最短路径
2.5.1.Dijkstra(迪杰斯特拉算法)
2.5.2.Bellman-Ford(贝尔曼-福特算法)
2.5.3.Floyd-Warshall(弗洛伊德算法)
概念:将n个不同的元素划分成一些不相交的集合。
概念图:
实现:Count:遍历数组元素有多少个负数,就有几个集合
#pragma once
#include
#include
#include
using namespace std;
template
class UnionFindSet {
public:
int Find(T val)
{
int index = _find_index[val];
while (_v[index] >= 0)
index = _v[index];
return index;
}
bool Union(T x, T y)
{
int xi = Find(x);
int yi = Find(y);
//是否已经联合了
if (xi == yi)
return false;
_v[xi] += _v[yi];
_v[yi] = xi;
return true;
}
//有多少个集合
int Count()
{
int count = 0;
for (auto e : _v)
{
if (e < 0)
count++;
}
return count;
}
public:
UnionFindSet(const vector& tmp)
{
for (int i = 0; i < tmp.size(); i++)
_find_index[tmp[i]] = i;
_v.resize(tmp.size(), -1);
}
~UnionFindSet()
{}
private:
//
vector _v;
//哈希数组的数据和下标
unordered_map _find_index;
};
测试:
#include
#include"UnionFindSet.h"
#include
using namespace std;
int main()
{
vector v{ 1,23,432,5345,8,712,44,534,645,73,862,3 };
UnionFindSet ufs(v);
ufs.Union(1, 534);
ufs.Union(1, 534);
ufs.Union(73, 534);
ufs.Union(1, 4);
ufs.Union(23, 8);
ufs.Union(862, 73);
ufs.Union(3,3);
cout << ufs.Count() << endl;
return 0;
}
可以试试这个题:使用并查集做
Leetcode:省份数量
class UnionFindSet{
public:
int Find(int x)
{
//找到下标
int index = _find_index[x];
while(_v[index] >= 0)
{
index = _v[index] ;
}
return index;
}
bool Union(int x, int y)
{
int xi = Find(x);
int yi = Find(y);
//已经联合
if(xi == yi)
return false;
_v[xi] += _v[yi];
_v[yi] = xi;
return true;
}
int Count()
{
int count = 0;
for(auto e : _v)
{
if(e < 0)
count++;
}
return count;
}
public:
UnionFindSet(const vector& v)
{
_v.resize(v.size(), -1);
for(int i = 0; i _v;
unordered_map _find_index;
};
class Solution {
public:
int findCircleNum(vector>& isConnected) {
int n = isConnected.size();
vector v;
for(int i =0; i
图(graph):由顶点(vertex)集合和顶点之间的关系(edge)构成,即G = (V, E);
无向图和有向图
完全图:在有n个顶点的无向图中,若有n * (n-1)/2条边,即任意两个顶点之间有且仅有一条边, 则称此图为无向完全图,比如上图G1;在n个顶点的有向图中,若有n * (n-1)条边,即任意两个 顶点之间有且仅有方向相反的边,则称此图为有向完全图,比如上图G4。
顶点的度:顶点v的度是指与它相关联的边的条数,记作deg(v);。在有向图中,顶点的度等于该顶点的入度与出度之和,其中顶点v的入度是以v为终点的有向边的条数,记作indev(v);顶点v的出度 是以v为起始点的有向边的条数,记作outdev(v)。因此:dev(v) = indev(v) + outdev(v)。注意:对于无向图,顶点的度等于该顶点的入度和出度,即dev(v) = indev(v) = outdev(v)。
简单路径与回路:若路径上各顶点v1,v2,v3,…,vm均不重复(不构成回路的路径),则称这样的路径为简单路径。若路径上第一个顶点v1和最后一个顶点vm重合,则称这样的路径为回路或环。
生成树:一个连通图的最小连通子图(整个图连通 ,任一顶点都可以找到另一顶点)称作该图的生成树。有n个顶点的连通图的生成树有n个顶点 和n-1条边。
优势:使用邻接矩阵可以快速查找(时间复杂度O(1))两个顶点是否有边且边的权值;劣势:查找顶点的度时(时间复杂度为O(N));
设计理念:有n个顶点,创建一个n*n的二维矩阵来放置两个顶点边的权值,为空可以使用一个权值的最大值或者最小值;
#pragma once
#include
#include
#include
#include
#include
#include
#include"UnionFindSet.h"
using namespace std;
//V顶点类型,W权值类型
namespace Matrix {
template
class Graph {
public:
typedef Graph Self;
int Find(const V& v)
{
auto it = _find_index.find(v);
if (it == _find_index.end())
return -1;
return it->second;
}
bool AddEdge(const V& src, const V& des, const W& weight)
{
int si = Find(src);
int di = Find(des);
//错误顶点
if (si == -1 || di == -1)
return false;
_matrix[si][di] = weight;
if (Direction == false)
_matrix[di][si] = weight;
return true;
}
void Print()
{
for (int i = 0; i < _matrix.size(); i++)
{
for (int j = 0; j < _matrix.size(); j++)
{
if (_matrix[i][j] == W_MAX)
printf("%5c", '*');
else
printf("%5d", _matrix[i][j]);
}
cout << endl;
}
}
public:
Graph(const vector& v)
{
int n = v.size();
_vertexs.resize(n);
//初始化顶点集合和 顶点和下标的映射
for (int i = 0; i < n; i++)
{
_vertexs[i] = v[i];
_find_index[v[i]] = i;
}
//初始邻接矩阵
_matrix.resize(n, vector(n, W_MAX));
}
Graph() = default;
~Graph()
{}
private:
//顶点集合
vector _vertexs;
//查找顶点下标
unordered_map _find_index;
vector> _matrix;
};
}
void test()
{
vector a{ "张三", "李四", "王五", "赵六", "周七" };
Matrix::Graph g1(a);
g1.AddEdge("张三", "李四", 100);
g1.AddEdge("张三", "王五", 200);
g1.AddEdge("王五", "赵六", 30);
g1.AddEdge("王五", "周七", 30);
g1.Print();
g1.Print();
}
int main()
{
test();
return 0 ;
}
优势:查找顶点的度时(时间复杂度为O(1),添加一个计数),更节省空间;劣势:使用邻接表查找两个顶点是否有边且边的权值(最差情况:时间复杂度O(N)),插入效率低:需要遍历链表,看是否已存在
设计理念:有n个顶点,创建有n个元素的指针矩阵,插入一个新边 遍历链表看是否存在;
namespace List {
template
class Node {
public:
int _des;
//权值
W _weight;
Node* _next;
Node(int des, W w):_des(des),_weight(w),_next(nullptr)
{}
};
template
class Graph{
public:
int Find(const V& v)
{
auto it = _find_index.find(v);
if (it == _find_index.end())
return -1;
return it->second;
}
bool AddEdge(const V& src, const V& des, const W& weight)
{
//获取下标
int si = Find(src);
int di = Find(des);
//不存在元素,fail;
if (si == -1 || di == -1)
return false;
//邻接表添加边
//数组指针是否为nullptr
if (_table[si] == nullptr){
//创建对象
Node* new_node = new Node(di, weight);
new_node->_next = _table[si];
_table[si] = new_node;
//无向图
if (Direction == false) {
Node* new_node = new Node(si, weight);
new_node->_next = _table[di];
_table[di] = new_node;
}
}
else {//需要和邻接表内元素比较,是否已添加
Node* nd = _table[si];
while (nd)
{
if (nd->_des == di)
return false;
else
nd = nd->_next;
}
//还未添加过
Node* new_node = new Node(di, weight);
new_node->_next = _table[si];
_table[si] = new_node;
//无向图
if (Direction == false) {
Node* new_node = new Node(si, weight);
new_node->_next = _table[di];
_table[di] = new_node;
}
}
return true;
}
void Print()
{
for (int i = 0; i < _table.size(); i++)
{
cout << _vertexs[i] << ": ";
Node* nd = _table[i];
while (nd)
{
cout << _vertexs[nd->_des] << " " << nd->_weight << " ";
nd = nd->_next;
}
cout << endl;
}
}
public:
Graph(const vector& v)
{
int n = v.size();
_vertexs.reserve(n);
for (int i = 0; i < n; i++)
{
_vertexs.push_back(v[i]);
_find_index[v[i]] = i;
}
_table.resize(n, nullptr);
}
private:
//顶点集合
vector _vertexs;
//顶点和下标的哈希
unordered_map _find_index;
//邻接表
vector*> _table;
};
}
void test()
{
vector a{ "张三", "李四", "王五", "赵六", "周七" };
List::Graph g1(a);
g1.AddEdge("张三", "李四", 100);
g1.AddEdge("张三", "王五", 200);
g1.AddEdge("王五", "赵六", 30);
g1.AddEdge("王五", "周七", 30);
g1.Print();
g1.Print();
}
int main()
{
test();
return 0 ;
}
我使用的是邻接表的图,来实现的,邻接矩阵也差不多,把代码插入List::graph类中就可以用了;
void _DFS(int pos, vector& visited)
{
visited[pos] = true;
Node* nd = _table[pos];
while (nd)
{
cout << _vertexs[pos] << _vertexs[nd->_des] << " ";
if (visited[nd->_des] == false)
_DFS(nd->_des, visited);
nd = nd->_next;
}
}
void DFS(const V& v)
{
int pos = _find_index[v];
vector visited(_table.size(), false);
_DFS(pos, visited);
}
void BFS(const V& v)
{
int pos = _find_index[v];
vector visited(_table.size(), false);
queue q;
q.push(pos);
while (!q.empty())
{
int size = q.size();
while (size--)
{
pos = q.front();
visited[pos] = true;
q.pop();
Node* nd = _table[pos];
while (nd)
{
cout << _vertexs[pos] << _vertexs[nd->_des] << " ";
if (visited[nd->_des] == false)
q.push(nd->_des);
nd = nd->_next;
}
}
}
}
后续5种算法都使用Matrix::Graph类
最小生成树通常使用的都是无向图;
贪心思想:
并查集代码:在最上面
//贪心思想,选权值最小的边,使用并查集判断是否构成回路
W Kruskal(Self& minTree)
{
//初始化生成树
int n = _vertexs.size();
minTree._vertexs = _vertexs;
minTree._find_index = _find_index;
minTree._matrix.resize(n, vector(n, W_MAX) );
//优先级队列保存边
priority_queue, vector>, greater>> pq;
//添加所有边进优先级队列
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if (_matrix[i][j] != W_MAX)
pq.push(Edge (i, j, _matrix[i][j]) );
}
}
//有n-1条边
int count = 0;
UnionFindSet ufs(_vertexs);
int totalW = 0;
while (!pq.empty())
{
Edge eg= pq.top();
pq.pop();
bool ret = ufs.Union(_vertexs[eg._src], _vertexs[eg._des]);
if (ret == true){//不构成回路
cout << _vertexs[eg._src] << ' ' << _vertexs[eg._des] << ' ' << eg._weight << endl;
minTree.AddEdge(_vertexs[eg._src], _vertexs[eg._des], eg._weight);
count++;
totalW += eg._weight;
}
else {//
cout << "构成环 " << _vertexs[eg._src] << ' ' << _vertexs[eg._des] << ' ' << eg._weight << endl;
}
//构成的总权值
}
if (count == n - 1)//能构成生成树
return totalW;
else//不能构成
return W();
}
测试代码:
void TestGraphMinTree()
{
const char* ch = "abcdefghi";
vector v;
for (int i = 0; i < strlen(ch); i++)
{
v.push_back(ch[i]);
}
Matrix::Graph g(v);
g.AddEdge('a', 'b', 4);
g.AddEdge('a', 'h', 8);
g.AddEdge('b', 'c', 8);
g.AddEdge('b', 'h', 11);
g.AddEdge('c', 'i', 2);
g.AddEdge('c', 'f', 4);
g.AddEdge('c', 'd', 7);
g.AddEdge('d', 'f', 14);
g.AddEdge('d', 'e', 9);
g.AddEdge('e', 'f', 10);
g.AddEdge('f', 'g', 2);
g.AddEdge('g', 'h', 1);
g.AddEdge('g', 'i', 6);
g.AddEdge('h', 'i', 7);
Matrix::Graph kminTree;
cout << "Kruskal:" << g.Kruskal(kminTree) << endl;
kminTree.Print();
}
贪心思想:
//贪心思想,按已选择的点延伸,使用set保存已使用,不使用使用过的就一定不会构环
//不使用并查集是因为已使用节点都是连通的
W Prim(Self &minTree, const V& val)
{
int n = _vertexs.size();
//初始化mintree
minTree._vertexs = _vertexs;
minTree._find_index = _find_index;
minTree._matrix.resize(n, vector(n, W_MAX));
int pos = _find_index[val];
//保存已使用
unordered_set visited;
visited.insert(pos);
priority_queue, vector>, greater> > pq;//
for (int j = 0; j < n; j++) {
if(_matrix[pos][j] != W_MAX)
pq.push(Edge(pos, j, _matrix[pos][j]));
}
int count = 0;//边数量
W TotalW = 0;//权值大小
while (!pq.empty())
{
//获取当前最小
Edge dg = pq.top();
pq.pop();
int des = dg._des;
if(visited.count(des))//存在,不可用
cout << "构成环 " << _vertexs[dg._src] << ' ' << _vertexs[dg._des] << ' ' << dg._weight << endl;
else {
for (int j = 0; j < n; j++)//添加新边
{
if(_matrix[des][j] != W_MAX)
pq.push(Edge(des, j, _matrix[des][j]));
}
visited.insert(des);//已使用
minTree._matrix[dg._src][dg._des] = dg._weight;//生成树添边
TotalW += dg._weight;
count++;
cout << _vertexs[dg._src] << ' ' << _vertexs[dg._des] << ' ' << dg._weight << endl;
}
}
if (count == n - 1)
return TotalW;
else
return W();
}
测试代码:
void TestGraphMinTree()
{
const char* ch = "abcdefghi";
vector v;
for (int i = 0; i < strlen(ch); i++)
{
v.push_back(ch[i]);
}
Matrix::Graph g(v);
g.AddEdge('a', 'b', 4);
g.AddEdge('a', 'h', 8);
g.AddEdge('b', 'c', 8);
g.AddEdge('b', 'h', 11);
g.AddEdge('c', 'i', 2);
g.AddEdge('c', 'f', 4);
g.AddEdge('c', 'd', 7);
g.AddEdge('d', 'f', 14);
g.AddEdge('d', 'e', 9);
g.AddEdge('e', 'f', 10);
g.AddEdge('f', 'g', 2);
g.AddEdge('g', 'h', 1);
g.AddEdge('g', 'i', 6);
g.AddEdge('h', 'i', 7);
Matrix::Graph kminTree;
cout << "Prim:" << g.Prim(kminTree, 'a') << endl;
kminTree.Print();
}
最短路径通常使用的都是有向图;
贪心思想:此算法的缺点:只能用做不带负权的图(负权就是小于0),因为不带负权已确定的路径值不可能更小(一个数加正数不可能更小);优势:性能最好(时间:O(N^2),邻接矩阵)
void Dijkstra(const V& v, vector& dest, vector& parentPath )
{
int n = _vertexs.size();
//起始点下标
int src = _find_index[v];
//和初始点的最短路径
dest.resize(n, W_MAX);
//顶点父亲下标
parentPath.resize(n, -1);
dest[src] = W();//起始点置为0
vector visited(n, false);
//执行n次
for (int i = 0; i < n; i++)
{
int minW = W_MAX, minI = src;
//找到当前最小顶点且未使用
for (int j = 0; j < n; j++)
{
if (visited[j] == false && dest[j] < minW)
{
minI = j;
minW = dest[j];
}
}
//设为已使用的值,不可能更小
visited[minI] = true;
//延伸是否能找到更小顶点权值
for (int j = 0; j < n; j++)
{
//满足未使用,两个顶点右边
if (visited[j] == false && _matrix[minI][j] != W_MAX)
{
//新路径值更小
if (dest[minI] + _matrix[minI][j] < dest[j])
{
//设置路径值和父顶点下标
dest[j] = dest[minI] + _matrix[minI][j];
parentPath[j] = minI;
}
}
}
}
}
测试代码:
void TestGraphDijkstra()
{
const char* ch = "syztx";
vector v;
for (int i = 0; i < strlen(ch); i++)
{
v.push_back(ch[i]);
}
Matrix::Graph g(v);
g.AddEdge('s', 't', 10);
g.AddEdge('s', 'y', 5);
g.AddEdge('y', 't', 3);
g.AddEdge('y', 'x', 9);
g.AddEdge('y', 'z', 2);
g.AddEdge('z', 's', 7);
g.AddEdge('z', 'x', 6);
g.AddEdge('t', 'y', 2);
g.AddEdge('t', 'x', 1);
g.AddEdge('x', 'z', 4);
vector dest;
vector parentPath;
g.Dijkstra('s', dest, parentPath);
}
暴力思想:将邻接矩阵遍历n-1次;时间复杂度:O(N^3),优势:解决带负权的图问题
bool BellmanFord(const V& v, vector& dest, vector& parentPath)
{
int n = _vertexs.size();
int src = _find_index[v];//初始位置
dest.resize(n, W_MAX);
parentPath.resize(n, -1);
//初始化初始位置
dest[src] = 0;
//为什么是n-1次:任意顶点到其他所有顶点最多n-1次,再多说明构成负权回路
for (int k = 0; k < n - 1; k++)
{
bool quit = true;
//遍历权值邻接矩阵
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
//有边且本身不为初始值
if (_matrix[i][j] != W_MAX && dest[i] != W_MAX && dest[i] + _matrix[i][j] < dest[j])
{
//设置更小的路径值和父顶点下标
dest[j] = dest[i] + _matrix[i][j];
parentPath[j] = i;
quit = false;
}
}
}
if (quit)
break;
}
//判断是否构成负权回路
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (_matrix[i][j] != W_MAX && dest[i] != W_MAX && dest[i] + _matrix[i][j] < dest[j])
return false;
}
}
return true;
}
测试代码:
void TestGraphBellmanFord()
{
const char* ch = "syztx";
vector v;
for (int i = 0; i < strlen(ch); i++)
{
v.push_back(ch[i]);
}
Matrix::Graph g(v);
g.AddEdge('s', 't', 6);
g.AddEdge('s', 'y', 7);
g.AddEdge('y', 'z', 9);
g.AddEdge('y', 'x', -3);
g.AddEdge('z', 's', 2);
g.AddEdge('z', 'x', 7);
g.AddEdge('t', 'x', 5);
g.AddEdge('t', 'y', 8);
g.AddEdge('t', 'z', -4);
g.AddEdge('x', 't', -2);
vector dist;
vector parentPath;
if (g.BellmanFord('s', dist, parentPath))
{
}
else
{
cout << "存在负权回路" << endl;
}
}
动态规划思想:时间复杂度:O(N^3);可以算出所有节点为起始点的最短路径;
void FloydWarShall(vector>& vvDest, vector>& vvParentPath)
{
int n = _vertexs.size();
vvDest.resize(n);
vvParentPath.resize(n);
//初始化
for (int i = 0; i < n; i++)
{
vvDest[i].resize(n, W_MAX);
vvParentPath[i].resize(n, -1);
}
//直接相连的顶点添加入二维数组
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (_matrix[i][j] != W_MAX)
{
vvDest[i][j] = _matrix[i][j];
vvParentPath[i][j] = i;
}
else
{
vvParentPath[i][j] = -1;
}
if (i == j)
{
vvParentPath[i][j] = -1;
vvDest[i][j] = 0;
}
}
}
//动规思想:i 到 j之间存在一个k,并且i到k + k到j < i 到 j
for (int k = 0; k < n; k++)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (vvDest[i][k] != W_MAX && vvDest[k][j] != W_MAX && vvDest[i][k] + vvDest[k][j] < vvDest[i][j])
{
vvDest[i][j] = vvDest[i][k] + vvDest[k][j];
vvParentPath[i][j] = vvParentPath[k][j];
}
}
}
}
}
测试代码:
void TestFloydWarShall()
{
const char* ch = "12345";
vector v;
for (int i = 0; i < strlen(ch); i++)
{
v.push_back(ch[i]);
}
Matrix::Graph g(v);
g.AddEdge('1', '2', 3);
g.AddEdge('1', '3', 8);
g.AddEdge('1', '5', -4);
g.AddEdge('2', '4', 1);
g.AddEdge('2', '5', 7);
g.AddEdge('3', '2', 4);
g.AddEdge('4', '1', 2);
g.AddEdge('4', '3', -5);
g.AddEdge('5', '4', 6);
vector> vvDist;
vector> vvParentPath;
g.FloydWarShall(vvDist, vvParentPath);
}