图结构
什么是图
- 图结构是一种与树结构有些相似的树结构
- 图论是数学的一个分支,并且,在数学的概念上,树是图的一种
- 它以图为研究对象,研究顶点和边组成的图形的数学理论和方法
- 主要研究的目的是事物之间的关系,顶点代表事物,边代表两个事物间的关系
图的特点
- 一组顶点:通常用E(Vertex)表示顶点的集合
- 一组边:通常用E(Edge)表示边的集合
- 边可以是无向的,也可以是有向的
- 比如A — B,通常表示无向,A –> B,通常表示有向。
一笔图的充要条件
- 奇点的数目不是0个就是2个
- 连到一点的边的数目如是奇数条,就称为奇点
- 如果是偶数条就成为偶点
- 要想一笔画成,必须中间点均是偶点
- 也就是有来路必有另一条去路,奇点只能在两端,因此任何图能一笔画成,奇点要么没有要么在两端。
图的术语
-
顶点:表示图中的一个结点。
-
边:表示顶点和顶点之间的连线.
- 注意: 这里的边不要叫做路径, 路径有其他的概念。
-
相邻顶点:由一条边连接在一起的顶点称为相邻顶点.
-
度:一个顶点的度是相邻顶点的数量.
-
路径:路径是顶点到另一个顶点
- 简单路径:简单路径要求不包含重复的顶点,比如 0 1 5 9 是一条简单路径
- 回路:第一个顶点和最后一个顶点相同的路径称为回路
-
无向图,有向图
- 无向图:任何边都没有方向
- 有向图:边有方向
-
无权图和带权图
- 无权图:边是没有任何意义的。
- 带权图:表示边有一定的权重,可以是任意你希望表示的数据。
图的表示
邻接矩阵
- 邻接矩阵让每个节点和一个整数向关联, 该整数作为数组的下标值。
- 通常用一个二维数组来表示顶点之间的连接.
- 缺点:
- 如果是一个无向图, 邻接矩阵展示出来的二维数组, 其实是一个对称图,这种情况下会造成空间的浪费。
- 如果图是一个稀疏图,那么矩阵中将存在大量的0, 这意味着我们浪费了计算机存储空间来表示根本不存在的边.而且即使只有一个边, 我们也必须遍历一行来找出这个边, 也浪费很多时间.
图片来源(zhyjc6.github.io/posts/2020/…)
邻接表
- 邻接表由图中每个顶点以及和顶点相邻的顶点列表组成.
- 这个列表有很多中方式来存储: 数组/链表/字典(哈希表)都可以.
- 缺点
- 邻接表计算"出度"是比较简单的(出度: 指向别人的数量, 入度: 指向自己的数量)
- 邻接表如果需要计算有向图的"入度", 那么是一件非常麻烦的事情.
图片来源(zhyjc6.github.io/posts/2020/…)
图的遍历
遍历方式
- 图的遍历思想和树的遍历思想是一样的
- 图的遍历意味着需要将图中每个顶点访问一边,并且不能有重复
- 遍历的两种算法
- 广度优先搜索(Breadth-First Search,简称BFS)
- 深度优先搜索(Dep-First Search,简称DFS)
- 两种遍历算法,都需要明确指定第一个被访问的顶点
- BFS:基于队列,入队列的顶点先被探索,从指定的第一个顶点开始遍历图, 先访问其所有的相邻点, 就像一次访问图的一层.换句话说, 就是先宽后深的访问顶点
图片来源博客:www.cnblogs.com/skywang1234…
- DFS:基于栈或者使用递归,通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问,接着原路回退并探索下一条路径
图片来源博客:www.cnblogs.com/skywang1234… 为了记录顶点是否被访问过,我们使用三种颜色来反应它们的状态
- 白色:该顶点未被访问
- 灰色:表示该顶点被访问过,但未被探索
- 黑色:表示该顶点被访问且被完全探索过
图的封装
包括加入顶点,加入边,toString,广度优先搜索和深度优先搜索,使用字典和队列封装
class Dictionary {
constructor() {
this.items = {};
}
//字典添加键值对
set(key, value) {
this.items[key] = value;
}
//判断字典中是否有某个key
has(key) {
return this.items.hasOwnProperty(key);
}
//从字典中移除元素
remove(key) {
if (!this.has(key)) return false;
delete this.items[key];
return true;
}
//根据key获取value
get(key) {
return this.has(key) ? this.items[key] : undefined;
}
//获取所有的keys
keys() {
return Object.keys(this.items);
}
//
}
class Queue {
constructor() {
this.item = [];
}
//1.向队尾添加一个或多个项
enqueue(element) {
this.item.push(element);
}
//2.从队列中删除前端的元素
dequeue() {
return this.item.shift();
}
//3.查看前端的元素
front() {
return this.item[0];
}
//4.查看队列是否为空
isEmpty() {
return this.item.length == 0;
}
//5.查看队列中的个数u
size() {
return this.item.length;
}
//6.toString方法
toString() {
let resultString = '';
for (let i = 0; i < this.item.length; i++) {
resultString += this.item[i] + ' ';
}
return resultString;
}
}
class Graph {
constructor() {
this.vertexes = [];
this.edges = new Dictionary();
}
//添加顶点
addVertex(v) {
this.vertexes.push(v);
this.edges.set(v, []);
}
//添加边
addEdge(v1, v2) {
this.edges.get(v1).push(v2);
this.edges.get(v2).push(v1);
}
toString() {
let resultString = '';
for (let i of this.vertexes) {
resultString += i + '->';
let vEdges = this.edges.get(i);
for (let j of vEdges) {
resultString += j + ' ';
}
resultString += '\n';
}
return resultString;
}
initializeColor() {
let colors = [];
for (let i = 0; i < this.vertexes.length; i++) {
colors[this.vertexes[i]] = 'white';
}
return colors;
}
//广度优先搜索
bfs(vInit, handler) {
//初始化图中各顶点的颜色
let colors = this.initializeColor(this.vertexes);
//创建队列
let queue = new Queue();
//将初始值加入到队列中
queue.enqueue(vInit);
//遍历初始节点指向的顶点
while (!queue.isEmpty()) {
//删除表中第一个元素
let v = queue.dequeue();
//将删除元素v的颜色改为灰色
colors[v] = 'gray';
//声明v顶点指向的顶点
let vList = this.edges.get(v);
//循环遍历vList
for (let i of vList) {
//判断被指顶点的颜色
if (colors[i] === 'white') {
colors[i] = 'gray';
queue.enqueue(i);
}
}
//对v顶点进行处理
handler(v);
//将v顶点的颜色改为黑色
colors[v] = 'black';
}
}
dfs(vInit, handler) {
//初始化颜色
let colors = this.initializeColor(this.vertexes);
//递归搜索
this.dfsVisit(vInit, colors, handler);
}
dfsVisit(v, colors, handler) {
//改变顶点的颜色
colors[v] = 'gray';
//处理顶点
handler(v);
//获取顶点指向的订点
let vList = this.edges.get(v);
//遍历
for (let i of vList) {
if (colors[i] === 'white') {
this.dfsVisit(i, colors, handler);
}
}
//将顶点的颜色变为黑色
colors[v] = 'black';
}
}
let graph = new Graph();
let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
myVertexes.forEach(item => {
graph.addVertex(item);
});
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
// console.log(graph.toString());
let resultBfs = '';
graph.bfs(graph.vertexes[0], function (v) {
resultBfs += v + '';
});
console.log(resultBfs);
let resultDfs = '';
graph.dfs(graph.vertexes[0], function (v) {
resultDfs += v + '';
});
console.log(resultDfs);
根据coderwhy老师的课程总结记录