【数据结构】基础:图的最短路径问题(附C++源码)

news2024/11/24 11:09:39

【数据结构】基础:图的最短路径问题(附C++源码)

摘要:将会在数据结构专题中开展关于图论的内容介绍,其中包括四部分,分别为图的概念与实现、图的遍历、图的最小生成树以及图的最短路径问题。本文介绍图的最短路径问题,分别为Dijkstra算法、BellmanFord算法和FloydWarshall算法,从算法的概述内容出发,进行实例介绍,在进行代码的实现说明,最后对其进行测试。


文章目录

  • 【数据结构】基础:图的最短路径问题(附C++源码)
    • 前言
      • 0.1 图的实现方式
      • 0.2 松弛操作
    • 一、概述
    • 二、Dijkstra算法(迪杰斯特拉算法)
      • 2.1 概述
      • 2.2 具体实例
      • 2.3 代码实现
      • 2.4 测试用例
    • 三、BellmanFord算法(贝尔曼-福特算法)
      • 3.1 概述
      • 3.2 具体实例
      • 3.3 代码实现
      • 3.4 测试用例
    • 四、FloydWarshall(弗洛伊德算法)
      • 4.1 概述
      • 4.2 代码实现
      • 4.3 测试用例

前言

0.1 图的实现方式

本文中图的实现方法为邻接矩阵法,以下是对其类的基本描述,若需查看更加具体的内容,可以参考博客图的概念与基本实现。其重点可以概括为:

  • Direction:表示是否为有向图
  • _vertexs:记录了对应检索下的顶点元素
  • _vIndexMap:记录了检索与顶点的对应关系
  • _matrix:表示邻接矩阵

具体代码如下:

template<class V, class W, bool Direction = false, W MAX_WEIGHT = INT_MAX>
    class Graph {
        typedef Graph<V, W, Direction, MAX_WEIGHT> Self;

        private:
        vector<V> _vertexs; // 顶点集合
        map<V, int> _vIndexMap; // 顶点检索
        vector<vector<W>> _matrix; // 邻接矩阵

        public:
        Graph() = default;
        Graph(const V* vertexs,size_t vertexSize) {
            _vertexs.reserve(vertexSize);
            for (size_t i = 0; i < vertexSize; i++) {
                _vertexs.push_back(vertexs[i]);
                _vIndexMap[vertexs[i]] = i;
            }

            // 格式化
            _matrix.resize(vertexSize);
            for (auto& e : _matrix) {
                e.resize(vertexSize, MAX_WEIGHT);
            }
            //for (size_t i = 0; i < _matrix.size(); i++) {
            //	_matrix[i][i] = 0;
            //}
        }

        size_t GetVertexIndex(const V& v) {
            auto ret = _vIndexMap.find(v);
            if (ret != _vIndexMap.end()) {
                return ret->second;
            }
            else {
                throw invalid_argument("不存在的顶点");
                return -1;
            }
        }

        void AddEdge(const V& src, const V& dest, const W& weight) {
            size_t srcIndex = GetVertexIndex(src);
            size_t destIndex = GetVertexIndex(dest);
            AddEdgeByIndex(srcIndex, destIndex, weight);
        }

        void AddEdgeByIndex(size_t srcIndex, size_t destIndex, const W& weight){
            _matrix[srcIndex][destIndex] = weight;
            if (Direction == false) {
                _matrix[destIndex][srcIndex] = weight;
            }
        }
    }

0.2 松弛操作

松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。

image-20230215003032476

一、概述

最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

主要方法包括:

  • 单源最短路径算法:Dijkstra算法、BellmanFord算法
  • 多源最短路径算法:FloydWarshall算法

二、Dijkstra算法(迪杰斯特拉算法)

2.1 概述

迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

Dijkstra算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径。

对于Dijkstra算法的内容,同样将点的集合分为已确定最小路径和未确定最小路径的两个集合,在每次寻找路径的过程中,可以概括为选择离起点权值最小的边作为基准去更新其他未确定的边,并将以确定最小路径的点保存,不再修改。

具体内容为:

  • 针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合
  • 每次从Q 中找出一个起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v 进行松弛操作。
  • 如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。
  • Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。

2.2 具体实例

摘抄自《算法导论》的实例进行展示与说明:

  • 以s为起点进行寻找最短路径,在最开始离s点最近只有s点,此时已确定为最短路径,权值为0。再以s为基准,访问其他的顶点,更新距离,对于t点和y点来说,可以更新,而x和z点不邻接,仍为无穷大
  • 选择最短的路径作为基准,因此选择y点,此时说明y到s的最短路径已经确定,并以y为基准访问其他顶点进行更新,对于t顶点、x顶点和z顶点,根据计算需要进行松弛操作
  • 选择最短的路径作为基准,因此选择z点,此时说明z到s的最短路径已经确定,并以z为基准访问其他顶点进行更新,对于x点需要进行松弛操作,而t点不需要
  • 选择最短的路径作为基准,因此选择t点,此时说明t到s的最短路径已经确定,并以t为基准访问其他顶点进行更新,对于x点需要进行松弛操作
  • 选择最短的路径作为基准,因此选择x点,此时说明x到s的最短路径已经确定,并且全部节点到s点的最短路径确定,为此不需要再更新,完成了最短路径的寻找

image-20230214222753625

2.3 代码实现

实现方法

  • 创建并初始化初始数据结构:距离容器(记录离起点的最短距离)、父节点容器(记录连接父节点的索引)和访问容器(表示是否为确定最小路径的节点)
  • 从起点开始,找出最小边,设置其为确定状态,添加更新松弛距离容器中起点到各个点的距离,当发生更新时,写入父节点容器,便于父节点查找,重复该过程n次,确定n个节点

具体代码

void Dijkstra(const V& src, vector<W>& dist, vector<int>& parentPath) {		
    size_t n = _vertexs.size();
    size_t srcIndex = GetVertexIndex(src);

    // 初始化距离容器和父节点容器
    dist.resize(n, MAX_WEIGHT);
    parentPath.resize(n, -1);

    // 设置起点访问自己权值为0
    dist[srcIndex] = W();
    parentPath[srcIndex] = srcIndex;

    // 记录是否访问过
    vector<bool> visitedV(n, false);

    // 统计完n个节点
    for (size_t num = 0; num < n; num++) {
        // 找出最小边
        int minEdgeIndex = 0;
        W minEdgeWeight = MAX_WEIGHT;
        for (size_t i = 0; i < n; i++) {
            if (visitedV[i] == false && dist[i] < minEdgeWeight) {
                minEdgeIndex = i;
                minEdgeWeight = dist[i];
            }
        }

        // 该边说明了 起点到该点的最小距离,因此要设置其为访问状态
        visitedV[minEdgeIndex] = true;

        // 添加更新dist中起点到各个点的距离
        // 找最小值:原来的最小值和途径新点的最小值
        for (size_t i = 0; i < n; i++) {
            if (visitedV[i] == false && _matrix[minEdgeIndex][i] != MAX_WEIGHT) {
                if (dist[minEdgeIndex] + _matrix[minEdgeIndex][i] < dist[i]) {
                    dist[i] = dist[minEdgeIndex] + _matrix[minEdgeIndex][i];
                    parentPath[i] = minEdgeIndex;
                }
            }
        }
    }
}

2.4 测试用例

测试样例与上文《算法导论》的图一致

void PrintShortestPath(const V& src, vector<W>& dist, vector<int>& parentPath) {
    size_t srcIndex = GetVertexIndex(src);
    size_t n = _vertexs.size();
    for (size_t i = 0; i < n; ++i) {
        // 找出i顶点的路径
        vector<int> path;
        size_t parentIndex = i;
        while (parentIndex != srcIndex) {
            path.push_back(parentIndex);
            parentIndex = parentPath[parentIndex];
        }
        path.push_back(srcIndex);
        reverse(path.begin(), path.end());
        cout << "[" << i << "]|";
        for (size_t j = 0; j < path.size() - 1; j++) {
            cout << _vertexs[path[j]] << " -> ";
        }
        cout << _vertexs[path[path.size() - 1]];
        cout << " 权值和:" << dist[i] << endl;
    }
}
void TestGraphDijkstra()
{
	const char* str = "syztx";
	Graph<char, int, true> g(str, strlen(str));
	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<int> dist;
	vector<int> parentPath;
	g.Dijkstra('s', dist, parentPath);
	g.PrintShortestPath('s', dist, parentPath);
}

image-20230214225047974

三、BellmanFord算法(贝尔曼-福特算法)

3.1 概述

贝尔曼-福特算法(Bellman-Ford)是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解单源最短路径问题的一种算法。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪科斯彻算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。但算法可以进行若干种优化,提高了效率。

BellmanFord算法缺点在于时间复杂度 O(N*E) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的,本文使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),可以看出来Bellman-Ford就是一种暴力求解更新。

不仅如此,该算法当负环存在时也是无法进行计算出最短路径的。原因在于每进行一次松弛,负环中的点的最短路径距离就会减少,进入循环过程中也会持续减小,没有终止。

3.2 具体实例

摘抄自《算法导论》的实例进行展示与说明:从点s开始对每个结点进行松弛操作

image-20230214234857738

3.3 代码实现

算法理解:可以从每次松弛过程进行理解,对于松弛的结点来说,松弛过程中并不是此刻的距离容器中记录的就是最短距离。而每轮松弛过程,最多可能添加一个中转结点,而对于n个结点来说,最多增加n-1个中间结点。由于每个结点都有可能成为中间结点,因此进行n次遍历松弛过程。

实现方法:对图进行n次松弛操作,得到所有可能的最短路径

  • 创建并初始化距离容器和父节点容器
  • 松弛n轮:因为每次松弛可能让一个结点的路径影响,更新后就规避了这种影响,最多可能更改n个结点的次数的影响
  • 设置目标位:当不需要更新时停止松弛
  • 在每次松弛需要更新时,写入父节点容器,便于父节点查找
  • 为防止出现负环的问题,在更新了n次后,判断是否还更新的,当存在负值环,进行错误返回

具体代码

bool BellmanFord(const V& src, vector<W>& dist, vector<int>& parentPath) {
    size_t n = _vertexs.size();
    size_t srcIndex = GetVertexIndex(src);

    // 初始化距离容器和父节点容器
    dist.resize(n, MAX_WEIGHT);
    parentPath.resize(n, -1);

    // 设置起点访问自己权值为0
    dist[srcIndex] = W();
    parentPath[srcIndex] = srcIndex;

    // 松弛n次:因为每次松弛可能让一个结点的路径影响,更新后就规避了这种影响,最多可能更改n个结点的次数的影响
    for (size_t num = 0; num < n; num++) {
        bool flag = false;
        cout << "更新次数:" << num << endl;
        // 每条边进行松弛
        for (size_t i = 0; i < n; i++) {
            for (size_t j = 0; j < n; j++) {
                if (_matrix[i][j] != MAX_WEIGHT && dist[i] + _matrix[i][j] < dist[j]) {
                    flag = true;
                    dist[j] = dist[i] + _matrix[i][j];
                    parentPath[j] = i;
                    cout << _vertexs[i] << "->" << _vertexs[j] << " : " << _matrix[i][j] << endl;
                }
            }
        }
        // 没有更新边说明不需要松弛了
        if (flag == false) {
            cout << "没有更新边"<<endl;
            break;
        }
    }
    // 更新了n次,还有要更新的,说明存在负值环,当存在该环时,会导致src到src不断减小
    for (size_t i = 0; i < n; i++) {
        for (size_t j = 0; j < n; j++) {
            if (_matrix[i][j] != MAX_WEIGHT && dist[i] + _matrix[i][j] < dist[j]) {
                cout << "存在负值环";
                return false;
            }
        }
    }
    return true;
}

3.4 测试用例

测试样例与上文《算法导论》的图一致,打印函数PrintShortestPath也在上文测试用例中

void TestGraphBellmanFord()
{
	const char* str = "syztx";
	Graph<char, int,true> g(str, strlen(str));
	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<int> dist;
	vector<int> parentPath;
	g.BellmanFord('s', dist, parentPath);
	g.PrintShortestPath('s', dist, parentPath);
}

image-20230215004345410

四、FloydWarshall(弗洛伊德算法)

4.1 概述

Floyd-Warshall算法是解决任意两点间的最短路径的一种算法。Floyd算法考虑的是一条最短路径的中间节点,即简单路径p={v1,v2,…,vn}上除v1和vn的任意节点。
算法内容:设k是p的一个中间节点,那么从i到j的最短路径p就被分成i到k和k到j的两段最短路径p1,p2。p1是从i到k且中间节点属于{1,2,…,k-1}取得的一条最短路径。p2是从k到j且中间节点属于{1,2,…,k-1}取得的一条最短路径。

以下是来自《算法导论》的摘抄:

image-20230215005317567

4.2 代码实现

Floyd算法本质是三维动态规划,Dijk表示从点i到点j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路。

可以设Di,j,k为i到j的只以(1…k)集合中的节点为中间节点的最短路径长度:

  1. 若最短路径经过点k,则Di,j,k = Di,k,k-1 + Dk,j,k-1
  2. 若最短路径不经过点k,则Di,j,k = Di,j,k-1

因此 Di,j,k = min(Di,k,k-1 + Dk,j,k-1 , Di,j,k-1)

实现方法

  • 初始化权值矩阵:与邻接矩阵一致,但对于达到自身的距离设置为0
  • 初始化路径矩阵:每个节点的初始路径为自己
  • 依次用顶点k作为中转点更新最短路径,即进行k次动态规划的过程,要筛选中间的n-2个节点,但每个节点都需要检测,因此进行n次更新检查
void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvParentPath) {
    size_t n = _vertexs.size();
    // 初始化权值和路径矩阵
    vvDist.resize(n);
    vvParentPath.resize(n);
    for (size_t i = 0; i < n; ++i){
        vvDist[i].resize(n, MAX_WEIGHT);
        vvParentPath[i].resize(n, -1);
    }
    for (size_t i = 0; i < n; ++i){
        for (size_t j = 0; j < n; ++j){
            if (_matrix[i][j] != MAX_WEIGHT){
                vvDist[i][j] = _matrix[i][j];
                vvParentPath[i][j] = i;
            }
        }
        vvDist[i][i] = W();
    }
    // 依次用顶点k作为中转点更新最短路径
    for (size_t k = 0; k < n; k++) {
        for (size_t i = 0; i < n; i++) {
            for (size_t j = 0; j < n; j++) {
                // i->k + k->j 比 i->j前面更新的距离更短,则更新
                if (vvDist[i][k] != MAX_WEIGHT && vvDist[k][j] != MAX_WEIGHT
                    && vvDist[i][k] + vvDist[k][j] < vvDist[i][j]) {
                    vvDist[i][j] = vvDist[i][k] + vvDist[k][j];
                    vvParentPath[i][j] = vvParentPath[k][j];
                }
            }
        }
        // 打印矩阵与路径矩阵
        for (size_t i = 0; i < n; i++) {
            for (size_t j = 0; j < n; j++) {
                if (vvDist[i][j] == MAX_WEIGHT) {
                    printf("%3c", '*');
                }
                else {
                    printf("%3d", vvDist[i][j]);
                }
            }
            cout << " | ";
            for (size_t j = 0; j < n; j++) {
                if (vvParentPath[i][j] == -1) {
                    printf("%3d", vvParentPath[i][j]);
                }
                else {
                    printf("%3c", _vertexs[vvParentPath[i][j]]);
                }
            }
            cout << endl;
        }
        cout << endl;
        cout << "=================================" << endl;
        cout << endl;
    }
}

4.3 测试用例

使用算法导论的实例进行说明:
在这里插入图片描述

void TestFloydWarShall()
{
    const char* str = "12345";
    Graph<char, int, true> g(str, strlen(str));
    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<vector<int>> vvDist;
    vector<vector<int>> vvParentPath;
    g.FloydWarshall(vvDist, vvParentPath);

    // 打印任意两点之间的最短路径
    for (size_t i = 0; i < strlen(str); ++i)
    {
        g.PrintShortestPath(str[i], vvDist[i], vvParentPath[i]);
        cout << endl;
    }
}

image-20230215011230400


补充:

  1. 代码将会放到:C++/C/数据结构代码链接 ,欢迎查看!
  2. 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/346596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode 105. 从前序与中序遍历序列构造二叉树 -- 数据结构基础

从前序与中序遍历序列构造二叉树 中等 1.9K 相关企业 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], i…

基于MATLAB的MIMO信道估计(附完整代码与分析)

目录 一. 介绍 二. MATLAB代码 三. 运行结果与分析 一. 介绍 本篇将在MATLAB的仿真环境中对比MIMO几种常见的信道估计方法的性能。 有关MIMO的介绍可看转至此篇博客&#xff1a; MIMO系统模型构建_唠嗑&#xff01;的博客-CSDN博客 在所有无线通信中&#xff0c;信号通过…

05- 线性回归算法 (LinearRegression) (算法)

线性回归算法(LinearRegression)就是假定一个数据集合预测值与实际值存在一定的误差, 然后假定所有的这些误差值符合正太分布, 通过方程求这个正太分布的最小均值和方差来还原原数据集合的斜率和截距。当误差值无限接近于0时, 预测值与实际值一致, 就变成了求误差的极小值。 fr…

【Calcite源码学习】ImmutableBitSet介绍

Calcite中实现了一个ImmutableBitSet类&#xff0c;用于保存bit集合。在很多优化规则和物化视图相关的类中都使用了ImmutableBitSet来保存group by字段或者聚合函数参数字段对应的index&#xff0c;例如&#xff1a; //MaterializedViewAggregateRule#compensateViewPartial()…

浏览器渲染原理JavaScript V8引擎

浏览器渲染原理 前言 在我们面试过程中&#xff0c;面试官经常会问到这么一个问题&#xff0c;那就是从在浏览器地址栏中输入URL到页面显示&#xff0c;浏览器到底发生了什么&#xff1f; 浏览器内有哪些进程&#xff0c;这些进程都有些什么作用&#xff1b;浏览器地址输入U…

【CentOS】有关时间的设置

目录环境信息date语法信息查看时间设置时间设置日期tzselecttimedatectl语法显示当前及所有时区修改时区hwclock语法读取硬件时钟使用硬件时钟设置系统时间使用系统时间设置硬件时钟如何理解硬件时钟和系统时钟环境信息 CentOS 7 date 语法信息 date --help用法&#xff1a…

Android - dimen适配

一、分辨率对应DPIDPI名称范围值分辨率名称屏幕分辨率density密度&#xff08;1dp显示多少px&#xff09;ldpi120QVGA240*3200.75&#xff08;120dpi/1600.75px&#xff09;mdpi160&#xff08;基线&#xff09;HVGA320*4801&#xff08;160dpi/1601px&#xff09;hdpi240WVGA4…

小白系列Vite-Vue3-TypeScript:011-登录界面搭建及动态路由配置

前面几篇文章我们介绍的都是ViteVue3TypeScript项目中环境相关的配置&#xff0c;接下来我们开始进入系统搭建部分。本篇我们来介绍登录界面搭建及动态路由配置&#xff0c;大家一起撸起来......搭建登录界面登陆接口api项目登陆接口是通过mockjs前端来模拟的模拟服务接口Login…

OpenStack手动分布式部署环境准备【Queens版】

目录 1.基础环境准备&#xff08;两个节点都需要部署&#xff09; 1.1关闭防火墙 1.2关闭selinux 1.3修改主机名 1.4安装ntp时间服务器 1.5修改域名解析 1.6添加yum源 2.数据库安装配置 2.1安装数据库 2.2修改数据库 2.3重启数据库 2.4初始化数据库 3.安装RabbitMq…

html网页加载ppt文件非ifram加载

今天有一个客户需求是加载一个ppt文件还要有翻页的效果&#xff0c;我搜索了很久也只有一个ifram加载。 所以我果断用了chtgpt然后发现了一个宝藏效果 代码如下&#xff1a; <!DOCTYPE html> <html> <head><title>PPT预览</title> </head>…

Seata-Server分布式事务原理加源码 (六) - Seata的AT模式

Seata-AT模式 概念&#xff1a;AT模式是一种无侵入的分布式事务解决方案&#xff0c;在 AT 模式下&#xff0c;用户只需关注自己的“业务 SQL”&#xff0c;用户的 “业务 SQL” 作为一阶段&#xff0c;Seata 框架会自动生成事务的二阶段提交和回滚操作。 整体机制 两阶段提…

Linux——线程同步(条件变量、POSIX信号量)和线程池

一.线程同步&#xff08;一&#xff09;.概念线程同步是一种多线程关系&#xff0c;指的是线程之间按照特定顺序访问临界资源&#xff0c;进而能够避免线程饥饿问题。所谓线程饥饿指的是某个线程长期“霸占”临界资源&#xff0c;导致其他线程无法访问该资源。而通过线程同步机…

【FPGA】Verilog:组合电路设计 | 三输入 | 多数表决器

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载的示例&#xff1a;表决器&#xff08;三人表决器&#xff09;。 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部…

你是真的“C”——【经典面试知识点】数据在内存中的大小端存储方式

你是真的“C”——【经典面试知识点】数据在内存中的大小端存储方式&#x1f60e;前言&#x1f64c;大小端介绍&#x1f64c;什么大端小端呢&#xff1f;&#xff1a;大小端存储的标准定义&#xff1a;大端和小端存在的意义经典的面试题目&#x1f64c;总结撒花&#x1f49e;&a…

ICLR 2022—你不应该错过的 10 篇论文(上)

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 ICLR 2023已经放榜&#xff0c;但是今天我们先来回顾一下去年的ICLR 2022&#xff01; ICLR 2022将于2022年 4 月 25 日星期一至 4 月 29 日星期五在线举行&#xff08;连续第三年&#xff01;&#xf…

1.8配置OSPF特殊区域

1.4.3实验8:配置OSPF特殊区域 实验目的实现OSPF Stub区域的配置实现OSPF NSSA区域的配置描述Type-7 LSA的内容描述Type-7 LSA与Type-5 LSA之间的转换过程实验拓扑配置OSPF特殊区域实验拓扑如图1-18的所示:[1] 图1-18 配置OSPF特殊区域 实验步骤 配置I…

有趣的HTML实例(十一) 烟花特效(css+js)

为什么今天不做炒土豆丝呢&#xff0c;为什么呢为什么呢为什么呢为什么呢&#xff0c;坚持问上一个时辰&#xff0c;一般来说&#xff0c;第二天我们的饭桌上就会出现炒土豆丝。这件事告诉了我们求知欲的重要性&#xff0c;知之才幸福&#xff0c;不知不幸福。 ——《华胥引》 …

ch4_1存储器

1. 存储器的类型 1.1 按照存储介质来分类 半导体存储器&#xff1a; TTL&#xff0c; MOS 易失性 磁表面存储器&#xff1a; 磁头&#xff0c; 载磁体&#xff1b; 磁芯存储器&#xff1a; 硬磁材料&#xff0c; 环状元件 光盘存储器: 激光&#xff0c; 磁光材料; 1.2 按…

【SSL/TLS】准备工作:证书格式

证书格式1. 格式说明1.1 文件编码格式1.2 文件后缀格式2. xca导出格式1. 格式说明 1.1 文件编码格式 1. PEM格式: 使用Base 64 ASCII进行编码的纯文本格式。后缀为“.pem”, ".cer", ".crt", ".key" 2. DER格式 二进制编码格式&#xff0c;文件…

Day889.MySQL高可用 -MySQL实战

MySQL高可用 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于MySQL高可用的内容。 正常情况下&#xff0c;只要主库执行更新生成的所有 binlog&#xff0c;都可以传到备库并被正确地执行&#xff0c;备库就能达到跟主库一致的状态&#xff0c;这就是最终一致性。但是…