先看 定义邻接表
//边
struct Edge {
int AdjVertex;//邻接顶点
int Weight;//权值
Edge* next;//下一条边
};
using VertexValue = use define
//顶点
struct Vertex {
VertexValue value; // 顶点数据
Edge* First;//获取第一条边
};
//邻接顶点
using AdjVertex = Vertex*;
//邻接表
using AdjList = AdjVertex*;
//图
struct Graph {
//邻接列表
AdjList List;
//顶点数
int VertexSize;
//边数
int EdgeSize;
};
看的清清楚楚 !
using AdjVertex = Vertex*;
你也可以不用指针 用 了也没啥事我的就是方法方式不一样
为了更好的构建图结构 画图是避免不了的事情
请看现露出只因(鸡)脚,本作者 带来的精彩图
首先来看圆圆的带着字 ! 蔡徐坤与其他人关系 是没有任何的关系
邻接表:包含数据 并且有一个 Edge* First; 获取第一条边 当然现在也显示不出来
来看看邻接表如何组合成图结构的吧
//图
struct Graph {
//邻接列表
AdjList List;
//顶点数
int VertexSize;
//边数
int EdgeSize;
};
//邻接列表
AdjList List; //本质就是 一个指针数组 List指向蔡徐坤这个虚拟结点 当然你也可以认为是一个结点指向蔡徐坤这个结点
算法实现
算法声明
//图初始化
void GraphInit(Graph& graph);
//添加顶点
void AddVertex(Graph& graph, VertexValue value);
//添加边
void AddSingleEdge(Graph& graph, VertexValue VertexValueFirst, VertexValue VertexValueSecond);
//添加边的权值
void SetEdgeOFWeight(Graph& graph, VertexValue VertexValueFirst, VertexValue VertexValueSecond, Ikun Weight);
//深度优先遍历
void DepthFirstSearch(const Graph& graph);
void DepthFirstSearch(const Graph& graph, int VertexIndex);
//广度优先遍历
void BreadthFirstSearch(const Graph& graph);
初始化图结构
你需要多少个顶点 ?
看你的需求 定义常量
所以添加顶点的的时候 动态? 还是一次性分配好
当然 效率来说当然是还是一次性分配好 难道你想一个个扩展? 产生时间成本 有的时候一切的安排或许有效率 但也要学会变动态,比如说:突然忘记带笔记本电脑上班,赶紧回去找笔记本电脑 没有发生什么事情 .比如说 :遇见好久不见的朋友 聊一下什么的 我们需要理性的动态 而不是一边一次性安排计划 这个点我要干嘛 ;只会感觉没有意义 完全是计划绑定死了的人
所以一切看你的需求来 我的需求是 如果顶点数大于1024 那么不需要添加
//顶点添加最大值
const int MaxSize = 1024;
//邻接表初始化
static void AdjListInit(AdjList& List) {
//遍历邻接表数组
for (size_t i = 0; i < MaxSize; i++) {
//初始化所有元素为null
List[i] = nullptr;
}
}
//图初始化
void GraphInit(Graph& graph){
//动态分配邻接表数组
graph.List = new AdjVertex[MaxSize];
//调用邻接表初始化函数
AdjListInit(graph.List);
//初始化顶点数量和边数量
graph.VertexSize = 0;
graph.EdgeSize = 0;
}
添加顶点
static AdjVertex CreateAdjVertex(VertexValue& value) {
//创建新顶点
Vertex *vertex = new Vertex;
//设置顶点的值
vertex->value = value;
//设置第一条边为null
vertex->First = nullptr;
//返回邻接顶点,实际上就是新顶点的指针
return vertex;
}
void AddVertex(Graph& graph, VertexValue value){
//获取顶点数量的引用
int& VertexSize = graph.VertexSize;
//如果顶点数量达到上限,不再添加
if (VertexSize >= MaxSize) {
return;
}
//获取邻接表的引用
AdjList& List = graph.List;
//调用CreateAdjVertex创建新顶点
AdjVertex adjVertex = CreateAdjVertex(value);
//将新顶点添加到邻接表中
List[VertexSize] = adjVertex;
//更新顶点数量
++VertexSize;
}
添加顶点非常像顺序表添加一样简单
真正的man,不需要绽放光芒也能闪亮
添加完毕后的 图结构:
感觉有点枯燥起来,唉 也不知道为啥总觉得自己提高不上来,可能过于自信了吧 不管怎么样实力永远都是比比不了学历 这我知道,但尝试过去休闲日子,因为自己太失败了,一个不太正常的人,当然我说的是我本人 但有的时候有点怪 怎么说呢看看这个链接我的故事
唉 避免不了的事情 就是这件事情 我虽然放弃了我的学业 当然学习的机会是自己争取,当然这怎么可能一个人呢 当然资源
竞争往往都是给别人,但我敢于投资策略 ,总之一切靠的是人脉网络 我其实写这个博客就是需要这些人脉网络扩大我的就业机遇这才是目的,谢谢你们关注 ,我会继续更新所学到的内容 以及分享一些有趣的话题
添加边
//寻找对应的顶点的索引
static int Location(Graph& graph, VertexValue & value){
const int VertexSize = graph.VertexSize;
for (size_t i = 0; i < VertexSize; i++){
//检查当前顶点的值是否与value相等
const bool result = graph.List[i] ? graph.List[i]->value == value : false;
if (result){
return i;
}
}
return VertexSize;
}
//创建一条新的边
static Edge* CreateEdge(int &AdjVertex,Edge *& First) {
Edge* Newedge = new Edge;
//设置新边的终点顶点索引
Newedge->AdjVertex = AdjVertex;
//将新边插入到第一条边之前
Newedge->next = First;
return Newedge;
}
//将新边插入到邻接表中
static void EdgeLink(Edge* &edge, Edge*& newEdge) {
edge= newEdge;
}
//向图中添加一条边 (- graph: 图的结构体,包含顶点数、边数、邻接表等信息 - VertexValueFirst: 个顶点的值- VertexValueSecond: 第二个顶点的值)
void AddSingleEdge(Graph& graph, VertexValue VertexValueFirst, VertexValue VertexValueSecond) {
const int VertexSize = graph.VertexSize;
int& EdgeSize = graph.EdgeSize;
//查找第一个顶点的索引
int VertexValueFirstIndex = Location(graph, VertexValueFirst);
//查找第二个顶点的索引
int VertexValueSecondIndex = Location(graph, VertexValueSecond);
//如果两个顶点都存在
if (VertexValueFirstIndex != VertexSize && VertexValueSecondIndex != VertexSize) {
//获得第一个顶点
Vertex* VertexFirst = graph.List[VertexValueFirstIndex];
//获得第一个顶点的首条边
Edge* ¤tEdge = VertexFirst->First;
//创建一条新边
Edge* NewEdge = CreateEdge(VertexValueSecondIndex, currentEdge);
//将新边插入到邻接表中
EdgeLink(currentEdge, NewEdge);
++EdgeSize;
}
}
由于边是单向链表 所以前插入法最高效,若你是看不懂需要 复习图的技术点-里的邻接表部分
int AdjVertex;//邻接顶点 代表 邻接表的索引
看深度优先遍历就知道
依次添加后
自定义函数依次添加边 这是演示 一般开发不使用中文命名函数 你们也知道
void 四大ikun连接蔡徐坤(Graph &Graph, VertexValue value[]) {
for (size_t i = 1; i <= 4; i++) {
AddSingleEdge(Graph, value[i], value[0]);
}
}
void 四大ikun互相连接(Graph &Graph, VertexValue value[]) {
AddSingleEdge(Graph, value[1], value[2]);
AddSingleEdge(Graph, value[1], value[3]);
AddSingleEdge(Graph, value[1], value[4]);
AddSingleEdge(Graph, value[2], value[1]);
AddSingleEdge(Graph, value[2], value[3]);
AddSingleEdge(Graph, value[2], value[4]);
AddSingleEdge(Graph, value[3], value[1]);
AddSingleEdge(Graph, value[3], value[2]);
AddSingleEdge(Graph, value[3], value[4]);
AddSingleEdge(Graph, value[4], value[1]);
AddSingleEdge(Graph, value[4], value[2]);
AddSingleEdge(Graph, value[4], value[3]);
}
可能链接可能乱七八糟了 我们不需要理会
接下来说一下深度优先遍历可能需要有段时间 准备! 就这样吧
深度优先遍历
struct DepthFirstSearchFLag {
int index; //当前访问顶点的索引
bool visited[MaxSize]; //标记数组,MaxSize为图的最大顶点数
};
//深度优先搜索遍历图
static void DepthFirstSearch(const Graph& graph, DepthFirstSearchFLag &flag) {
//当前访问的顶点索引
int& index = flag.index;
//如果当前顶点已被访问,返回
if (flag.visited[index]) {
return;
}
//输出当前顶点的值和权重
cout << graph.List[index]->value << " "<< " is " << (graph.List[index]->First->Weight == Ikun::TrueIKun ? " Ikun : " : " kun : ") << graph.List[index]->First->Weight << endl;
//标记当前顶点为已访问
flag.visited[index] = true;
//获得当前顶点的首条边
Edge* current = graph.List[index]->First;
//下一条边的终点顶点索引
int nextIndex;
//遍历邻接表
while (current) {
//获得下一条边的终点顶点索引
nextIndex = current->AdjVertex;
//移到下一条边
current = current->next;
//如果下一顶点未被访问
if (!flag.visited[nextIndex]) {
//访问下一顶点
index = nextIndex;
//递归调用
DepthFirstSearch(graph, flag);
}
}
}
//深度优先搜索图,使用递归实现
void DepthFirstSearch(const Graph& graph) {
//定义结构体,保存搜索状态
//index:当前访问顶点索引
//visited[]:标记所有顶点是否被访问,初始化为false
DepthFirstSearchFLag Flag = {};
//获取图的顶点数
const int VertexSize = graph.VertexSize;
//遍历图的所有顶点,i为顶点索引
for (size_t i = 0; i < VertexSize; i++) {
//如果当前顶点i未被访问
if (!Flag.visited[i]) {
//1. 设置当前顶点索引为i
Flag.index = i;
//2. 递归调用,访问当前顶点i
//在递归调用中,会完成如下操作:
//2.1 输出当前顶点i信息
//2.2 标记当前顶点i为已访问
//2.3 遍历当前顶点i的邻接表
//2.4 如果发现未访问顶点,递归调用访问之
//2.5 返回上一层调用
DepthFirstSearch(graph, Flag);
}
}
//遍历结束,输出一个换行
cout << endl;
}
深度优先遍历图
进入第一次递归:
返回上一层:
进入第二次递归
返回上一层:
进入第三次递归
返回上一层:
进入第四次递归
返回上一层:
循环结束
深度优先遍历结果
- 检查当前顶点是否被访问,如果是则返回。否则打印顶点信息。
if (flag.visited[index]) {
return;
}
cout << graph.list[index]->value << " ";- 将当前顶点标记为已访问。
flag.visited[index] = true;- 指向当前顶点的第一个相邻顶点。如果未访问,则将其索引赋值给index,递归调用深度优先搜索算法。
Edge* current = graph.list[index]->first;
while (current) {
int nextIndex = current->adjVertex;
if (!flag.visited[nextIndex]) {
index = nextIndex;
depthFirstSearch(graph, flag);
}
current = current->next;
}- 直到所有相邻顶点都被访问,返回。重复此过程直到图被完全遍历。
广度优先遍历
static void BreadthFirstSearch(const Graph& graph, BreadthFirstSearchFLag& flag) {
queue<int> vertexIndexQueue;
int& Index = flag.index;
vertexIndexQueue.push(Index);
bool(&visited)[MaxSize] = flag.visited;
while (!vertexIndexQueue.empty()) {
Index = vertexIndexQueue.front();
Edge* current = graph.List[Index]->First;
vertexIndexQueue.pop();
if (!visited[Index]) {
cout << graph.List[Index]->value << " ";
if (current) {
cout << " is First Edge :" << current->AdjVertex << " Weight: " << (current->Weight == Ikun::TrueIKun ? " Ikun : " : " kun : ") << current->Weight << endl;
}
visited[Index] = true;
}
int NextIndex;
while (current) {
NextIndex = current->AdjVertex;
current = current->next;
if (!visited[NextIndex]) {
vertexIndexQueue.push(NextIndex);
}
}
}
}
void BreadthFirstSearch(const Graph& graph){
BreadthFirstSearchFLag Flag = {};
const int VertexSize = graph.VertexSize;
for (size_t i = 0; i < VertexSize; i++) {
if (!Flag.visited[i]) {
Flag.index = i;
BreadthFirstSearch(graph, Flag);
}
}
cout << endl;
}
广度优先遍历图
- 从起点顶点开始,其索引push入队列q,visited数组对应位置置true。
q.push(start);
visited[start] = true;- 队首元素出队,此时index指向当前访问顶点。
int index = q.front();
q.pop();
- 访问此顶点,打印信息。
cout << graph.list[index]->value << " ";
// 打印相邻顶点信息- 当前顶点的相邻顶点索引入队,对应visited位置置true。循环直到相邻顶点为空。
Edge* current = graph.list[index]->first;
while (current) {
int adj = current->adjVertex;
if (!visited[adj]) {
q.push(adj);
visited[adj] = true;
}
current = current->next;
}- 重复步骤2到4,直到队列为空。算法结束。