图论基础复习【并查集 | 图遍历 | 最小生成树 | 3种最小路径算法】

  博客主页:花果山~程序猿-CSDN博客

文章分栏:高阶数据结构_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

目录

一,并查集

查找

合并

应用题

二,图

图的基本概念

图的存储结构

1.邻接矩阵

优势

缺点

2.邻接表

优势

图的遍历(邻接矩阵)

1.广度优先遍历

2.深度优先遍历

三,最小生成树

Kruskal算法 

Prim算法

两种算法为什么能保证得到最小生成树?

前提

四,最短路径

1.Dijkstra算法

2.Bellman-Ford算法

3.floyd-warshall算法


嗨!收到一张超美的图,愿你每天都能顺心!

一,并查集

        并查集(Union-Find)是一种数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。它在算法中非常有用,尤其是在图论中处理连通性问题时。

并查集能够高效地支持两种操作:

  1. 查找(Find):确定元素属于哪一个子集。它可以用来确定两个元素是否位于同一个子集中。
  2. 合并(Union):将两个子集合并成一个单一的集合。

关于理解 树 与 树之间的合并:

1.当数据量比较少时,让一个树的根作为另一棵树的子树,并修改val值即可。

2.如果按照1方法进行树与树之间合并,当数据量比较大时,调用查找获取所在树时,需要经历多次"跳跃",因此在该情况下,应将子树拆散,直接作为孩子,提高效率(这也是路径压缩的算法思路)

查找

寻找到目标的根下标

size_t GetRoot(size_t order)
{
	    int root = order;
		while (_union_set[root] >= 0)
		{
			root = _union_set[root];
		}
		
		// 开始向上压缩---数据量打时采用压缩算法
		while (_union_set[order] >= 0)
		{
			int parent = _union_set[order];
			_union_set[order] = root;
			order = parent;
		}
		return root;
}

合并

    void _union(size_t a, size_t b)
	{
        a = GetRoot(a);
        b = GetRoot(b);
        if (a == b) return; // 相同树,不合并
		if (a > b)
			swap(a, b);

		_union_set[a] += _union_set[b];
		_union_set[b] = a;

	}

应用题

LCR 116. 省份数量 - 力扣(LeetCode)

990. 等式方程的可满足性 - 力扣(LeetCode)

二,图

图是一种非线性数据结构,它由顶点(Vertices)和边(Edges)组成,用于表示对象之间的多对多关系。图在许多领域都有广泛的应用,比如社交网络分析、路由算法、编译器优化等。

图的基本概念

  • 顶点(Vertex/Node):图中的基本单位,代表一个实体。
  • 边(Edge):连接两个顶点的线,表示这两个顶点之间存在某种关系。
  • 有向图(Directed Graph):边是有方向的,从一个顶点指向另一个顶点。

  • 无向图(Undirected Graph):边是没有方向的,表示两个顶点是相互连接的。

  • 加权图(Weighted Graph):每条边上都关联有一个权重值,通常用来表示成本或距离,这个可以灵活多变的。
  • 路径(Path):一系列相连的边,构成从一个顶点到另一个顶点的路线。
  • 连通图(Connected Graph):对于无向图,如果任意两个顶点之间都存在一条路径,则称该图为连通图
  • 对于有向图,若存在一条从任何顶点到其他所有顶点的路径,则称为强连通图
  • 环(Cycle):一条起始顶点和结束顶点相同的路径。
  • 度(Degree):与某个顶点相连的边的数量。在有向图中分为入度(进入顶点的边数)和出度(离开顶点的边数)。

图的存储结构

1.邻接矩阵

因为图中既有节点,又有边(节点与节点之间的关系),因此, 在图的存储中,只需要保存:节点和边关系即可。节点保存比较简单,只需要一段连续空间即可,那边关系该怎么保存呢?

注意:

如果边带有权值,并且两个节点之间是连通的,上图中的边的关系就用权值代替,如果 两个顶点不通,则使用 无穷大代替

优势

  1. 邻接矩阵适合稠密图,即:相同成本下,边(关系越复杂)越多,矩阵利用率越高
  2. 矩阵能在O(1)内判断两 顶点的关系,并获取到 权值

缺点

无法快速获取一个顶点所连接的所有顶点——O(n),遍历一层。
框架图:如下

具体代码:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

2.邻接表

 邻接表:使用数组表示顶点的集合,使用 链表表示边的关系。 

优势

  1. 适合稀疏图(边少 |  关系简单)
  2. 适合查找一个顶点的所有关系 ——O(1)效率

框架:

具体代码:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

总结:两数据结构相辅相成,互为互补关系

图的遍历(邻接矩阵)

给定一个图 G 和其中任意一个顶点 v0 ,从 v0 出发,沿着图中各边访问图中的所有顶点,且每个顶 点仅被遍历一次 " 遍历 " 即对结点进行某种操作的意思

1.广度优先遍历

 难点:

1. 如何处理好一次存储层顶点?——采用队列保存层顶点,这里下标代表顶点。

2. 如何避免重复访问顶点? ——采用set思想,访问顶点就标记,避免形成环。

// 层序遍历————默认从0开始层序遍历
        void BFS(const V &v)
        {
            auto index = GetIndex(v);

            queue<int> q;                
            vector<bool> _set(_vertices.size(), false);

            q.push(index);
            cout << _vertices[index] << "->";
            _set[index] = true;
            while (q.empty() != true)
            {
                int tmp = q.front();
                q.pop();
                // 获取所有连接点,并判断载入队列
                int time = 0;
                while (time < _matrix[tmp].size())
                {
                    if (_matrix[tmp][time] != INT_MAX && _set[time] == false)
                    {
                        q.push(time);
                        cout << _vertices[time] << " ";
                        _set[time] = true;
                    }
                    time++;
                }
                cout << "->";
            }
            cout << endl;
        }

2.深度优先遍历

深度优先遍历较广度,逻辑难度复杂,代码简单。在效率方面,在深度较深时,效率下降,甚至会出现栈溢出现象

方法:递归 

void _dfs(int index, vector<bool> &_set)
{
    cout << _vertices[index] << "--";
    for (int i = 0; i < _matrix[index].size(); i++)
    {
       if (_set[i] != true && _matrix[index][i] != INT_MAX)
       {
            _set[i] = true;
            _dfs(i, _set);
       }
    }
}

        void DFS(const V &v)
        {
            auto index = GetIndex(v);
            vector<bool> _set(_vertices.size(), false);
            _set[index] = true;
            _dfs(index, _set);
            cout << endl;
        }

具体代码见代码仓库:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

三,最小生成树

连通图中,有n 个顶点,如果通过 n - 1将这n个顶点连接起来,那个形成的树就是生成树。

最小生成树的三个准则:

  • 只能使用图中的边来构造最小生成树
  • 只能使用恰好n-1条边来连接图中的n个顶点
  • 选用的n-1条边不能构成回路

Kruskal算法 

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边(贪心算法),加入到最小生成树的边集合(并查集中)里。

思路解析: 

  1. 把图中的所有边按权值从小到大排序(这里采用优先级队列 + 仿函数,也可以采用其他容器 + 排序); 
  2. 把图中的n个顶点看成独立的n棵树组成的森林; 
  3. 按权值从小到大选择边,所选的边连接的两个顶点v1,v2。 v1与 v2 应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。 
  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

代码:

W Kruskal(Graph<V, W>& minfree)
        {
            priority_queue<edge, vector<edge>, greater<edge>> greater_queue;
            // 队列载入边信息
            for (int i = 0; i < _matrix.size(); i++)
            {
                for (int j = 0; j < _matrix[i].size(); j++)
                {
                    if (i >= j && _matrix[i][j] != INT_MAX)
                        greater_queue.push(edge(i, j, _matrix[i][j]));
                }
            }

            size_t sum_edge = 0;
            W sum_w = W();
            // 初始化并查集
            UnionFindSet it(_vertices.size());

            int time = _vertices.size() - 1;
            // 只连接 n - 1次,判断该无向图是否有最小生成树
            while (sum_edge < time)
            {
                edge tmp = greater_queue.top();
                // 防止出现闭环,原理:并查集同树
                if (it.IsSameRoot(tmp._v1, tmp._v2))
                {
                    greater_queue.pop();
                    continue;
                }
                // cout << _vertices[tmp._v1] << "->" << _vertices[tmp._v2] << ": " << tmp._w << endl;
                //单独构建一个外部最小生成树
                minfree.size_t_addedge(tmp._v1, tmp._v2, tmp._w);
                it._union(tmp._v1, tmp._v2);
                greater_queue.pop();
                sum_w += tmp._w;
                sum_edge++;
            }

            // cout << "w : " << sum_w << endl;
            // cout << "sum_edge : " << sum_edge << endl;
            // cout << "_vertice : " << _vertices.size() << endl;

            // 经过n - 1次连接边,如果所有顶点都在树中,则有最小生成树;
            // 如何判断? 并查集,由于我们实现了压缩路径算法,所以所有孩子只有一个根。
            int root = it.GetRoot(0);
            for (int i = 1; i < it.size(); i++)
            {   
                // 如果根不相同,说明还有孤岛顶点,即不是最小生成树
                if (root != it.GetRoot(i))
                    return W();
            }
            return sum_w;
        }

具体代码 & 测试用例,见代码仓库:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

Prim算法

普里姆算法其实是在U和V-U两个阵营中不停的找一条最短的(代价最低的)可连通的边,然后将该边附着的在V-U阵营中的顶点加入U阵营中。

思路解析:

  1. 需要两个容器负责X, Y集合的快速插入 & 检测存在功能(这里我采用set<size_t>)
  2. 选择一个初始顶点加入到生成树中。
  3. 初始化一个优先队列(或最小堆),存储不在生成树中的顶点及其到当前生成树的最小边权。
  4. 从未加入生成树的顶点中选择一个与生成树相连的边权最小的顶点,加入到生成树中。
  5. 更新优先队列,反映新加入顶点带来的影响。
  6. 重复步骤 4 和 5 直到所有顶点都被加入到生成树中。直到优先级队列中没有边为止;如果记录的  边数量 == 顶点 - 1 (n - 1)则为最小生成树

下面采用另一位大佬的图解:

代码:

W Prim(Graph<V, W> &minfree, const V &start)
        {
            size_t ptr = GetIndex(start);
            set<size_t> X;
            set<size_t> Y;
            for (int i = 0; i < _vertices.size(); i++)
                Y.insert(i);

            priority_queue<edge, vector<edge>, greater<edge>> greater_queue;

            // 先载入开始顶点
            X.insert(ptr);
            Y.erase(ptr);
            // 队列载入与顶点相连边的顶点信息
            for (int i = 0; i < _matrix.size(); i++)
            {
                if (_matrix[ptr][i] != INT_MAX)
                    greater_queue.push(edge(ptr, i, _matrix[ptr][i]));
            }

            size_t edge_sum = 0;
            W w_sum = 0;

            while (greater_queue.empty() != true)
            {
                edge tmp = greater_queue.top();
                if (X.find(tmp._v1) != X.end() && X.find(tmp._v2) != X.end())
                {
                    greater_queue.pop();
                    continue;
                }
                // 不都在X中都载入
                size_t new_X;
                if (X.find(tmp._v1) == X.end())
                    new_X = tmp._v1;
                else
                    new_X = tmp._v2;
                // cout << _vertices[tmp._v1] << "->" << _vertices[tmp._v2] << ": " << tmp._w << endl;
                minfree.size_t_addedge(tmp._v1, tmp._v2, tmp._w);
                greater_queue.pop();
                w_sum += tmp._w;
                edge_sum++;
                X.insert(new_X);
                Y.erase(new_X);

                for (int i = 0; i < _matrix.size(); i++)
                {
                    if (_matrix[new_X][i] != INT_MAX)
                        greater_queue.push(edge(new_X, i, _matrix[new_X][i]));
                }
            }

            // cout << "w : " << w_sum << endl;
            // cout << "sum_edge : " << edge_sum << endl;
            // cout << "_vertice : " << _vertices.size() << endl;
            if (edge_sum != _vertices.size() - 1)
                return W();
            return w_sum;
        }

具体代码 & 测试用例,见代码仓库:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

两种算法为什么能保证得到最小生成树?

这两种算法都基于贪心策略,但在理论上已经被证明能够找到最小生成树。这是因为最小生成树问题满足贪心选择性质最优子结构性质。也就是说,在每一步中选择当前最优解(局部最优解),最终能得到全局最优解。

前提

只要图中的边权非负的,Kruskal 算法和 Prim 算法就能正确地找到最小生成树如果存在负权边,那么最小生成树的概念就不再适用,因为负权边可能会导致循环,从而使得没有明确的最小生成树。

四,最短路径

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

1.Dijkstra算法

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

时间复杂度:O(N^2)

算法原理可以查看该作者博客,通俗易懂

【看完必懂】Dijkstra算法(附案例详解) - 知乎

流程图: 

代码:

void Dijkstra(const V &v)
        {
            vector<W> dij_vec(_vertices.size(), INT_MAX);
            vector<bool> exist(_vertices.size(), true);

            int exist_sum = _vertices.size();
            auto start_index = GetIndex(v);

            dij_vec[start_index] = 0;
            exist[start_index] = false;
            exist_sum--;

            while (exist_sum > 0)
            {
                // 在未被标记顶点内寻找
                for (int i = 0; i < _matrix[start_index].size(); i++)
                {
                    if (exist[i] && _matrix[start_index][i] != INT_MAX)
                    {
                        dij_vec[i] = min(dij_vec[i], (_matrix[start_index][i] + dij_vec[start_index]));
                    }
                        
                }
                // 开始删除目前最小值
                //获取最小值下标
                int index = -1;
                size_t min_index = INT_MAX;
                for (int i = 0; i < dij_vec.size(); i++)
                {
                    if (exist[i] && dij_vec[i] < min_index)
                    {
                        min_index = dij_vec[i];
                        index = i;
                    }
                }
                // 删除已经确定的顶点
                if (index != -1)
                {
                //    cout << "delete: " << _vertices[index] << endl;
                    exist[index] = false;
                    start_index = index;
                    exist_sum--;
                }        
            }

            cout << "结果:" << endl;
            for (int i = 0; i < dij_vec.size(); i++)
            {
                cout << _vertices[i] << " min:" << dij_vec[i] << endl;
            }
        }

全部代码,见代码仓库:

Graph/Graph.hpp · 逆光/Cpp - 码云 - 开源中国

2.Bellman-Ford算法

bellman—ford算法可以解决负权图的单源最短路径问题 

特点: 

  • 适用于含有负权边的图(Dijkstra不适用)
  • 简单粗暴,但效率慢(N^3)
  • 如果对应路径存在负权回路则没有最短路径(可用于判断图中是否存在负权回路)

基本步骤 

  1. 初始化数据

    • 对于所有顶点 dist[v] = ∞。
    • dist[s] = 0,s是起始点。
  2. 松弛操作

    • 对图中的每条边(u, v),执行松弛操作。松弛操作检查是否满足dis[u]  + w < dis[v]。如果存在,则更新v的最短路径估计值。
    • 这个过程重复进行V-1次(V是顶点数)。
  3. 检查负权环

    • 再次对所有的边执行松弛操作。如果能够进一步减少某个顶点的距离正常情况不会超过V-1次,则说明存在一个从该顶点出发可以无限降低路径长度的环路,即存在一个权重之和为负的环路
代码:
void Bellman_Ford(const V &v)
        {
            vector<W> bell_vec(_vertices.size(), INT_MAX);
            vector<size_t> last_index(_vertices.size(), -1);
            auto start_index = GetIndex(v);

            bell_vec[start_index] = 0;
            last_index[start_index] = 0;
            int n = _vertices.size();

            for (int z = 0; z < n; z++)
            {
                for (int i = 0; i < n; i++)
                {
                    for (int j = 0; j < n; j++)
                    {
                        if (_matrix[i][j] != INT_MAX && bell_vec[i] + _matrix[i][j] < bell_vec[j])
                        {
                            bell_vec[j] = bell_vec[i] + _matrix[i][j];
                            last_index[j] = i;
                        }
                    }
                }
            }

           for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (_matrix[i][j] != INT_MAX && bell_vec[i] + _matrix[i][j] < bell_vec[j])
                    {
                        return false;
                    }
                }
            }

           return true;
        }

3.floyd-warshall算法

loyd-Warshall算法是一种解决全网最短路径问题(All-Pairs Shortest Path, APSP)的动态规划算法。它可以在有向或无向图中找到任意两点之间的最短路径,即使图中包含负权重的边也可以处理。

特点

  • 适用于密集图或当需要知道所有顶点对之间的最短路径时使用(在稀疏图非负权图上,不如多次调用dijkstra速度快)
  • 代码简单,逻辑暴力,时间复杂度——O(n^3)
  • 无法解决负权回路问题

以下是Floyd-Warshall算法的主要步骤:

  1. 初始化距离矩阵

    • 对于所有顶点对(i, j),如果(i, j)直接相连,则d[i][j]设置为边的权重。
    • 如果i = j,则d[i][j] = 0
    • 否则,如果不存在直接连接,则d[i][j]设置为无穷大(+∞)。
  2. 动态规划

    • 逐步考虑每一个顶点k作为中间顶点,尝试通过k来改进i到j之间的最短路径。
    • 使用以下更新规则:对于每一对顶点(i, j),如果d[i][k] + d[k][j] < d[i][j],则更新d[i][j]d[i][k] + d[k][j]
    • 以上更新操作对所有可能的k(从1到n,n为顶点数)进行迭代。

代码:

void Floyd_warshall()
        {
            vector<vector<W>> dis;
            vector<vector<int>> parentpath;
            int n = _vertices.size();

            dis.resize(n);
            parentpath.resize(n);
            // 初始化权值图
            // 初始化父路径图
            for (int i = 0; i < n; i++)
            {
                dis[i].resize(n, INT_MAX);
                parentpath[i].resize(n, -1);
            }

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (_matrix[i][j] != INT_MAX)
                    {
                        dis[i][j] = _matrix[i][j];
                        // 默认i->j就是最短路径,所以j的上一级就是i
                        parentpath[i][j] = i;
                    }

                    if (i == j)
                        dis[i][j] = 0;
                }   
            }

            // 用k作为中转点,试图获取i->k->j的最短路径
            for (int k = 0; k < n; k++)
            {
                for (int i = 0; i < n; i++)
                {
                    for (int j = 0; j < n; j++)
                    {
                        if (dis[i][k] != INT_MAX && dis[k][j] != INT_MAX && dis[i][k] + dis[k][j] < dis[i][j])
                        {
                            dis[i][j] = dis[i][k] + dis[k][j];
                            parentpath[i][j] = parentpath[k][j];
                        }
                    }
                }
            }

        }

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源泉。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVA Maven 的安装与配置

一、下载地址 官方网站&#xff1a;Maven – Download Apache Maven 我这里是3.8.6版本 二、安装步骤 maven安装之前要先安装jdk&#xff0c;请确保你的系统已经安装了jdk环境。 1.将下载好的 Maven 进行解压 apache-maven-3.6.8-bin.zip 2.配置本地仓库:修改 conf/settin…

C++设计模式创建型模式———单例模式

文章目录 一、引言二、懒汉模式三、饿汉模式四、C11 的线程安全单例五、与其他模式的关系六、总结 一、引言 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 在一个项目中&#xff0c;全局范围内&a…

HTML+CSS实现超酷超炫的3D立方体相册

效果演示 HTML和CSS实现一个简单的3D立方体加载动画的相册。它使用了HTML来构建立方体的结构&#xff0c;并通过CSS来添加样式和动画效果。 HTML <div class"loader3d"><div class"cube"><div class"face"><img src&qu…

如何对pdf文件进行加密?pdf文件加密全攻略与深度解析(5个方法)

如何对pdf文件进行加密&#xff1f; 只见&#xff0c;在深夜的情报局里&#xff0c;特工小李将一份绝密PDF文件放在保险箱内&#xff0c;以为这样就天衣无缝了。 细细推敲&#xff0c;漏洞百出&#xff1a; 如果钥匙被盗呢&#xff1f;如果被神匠破解出密码呢&#xff1f;如果…

java 提示 避免用Apache Beanutils进行属性的copy。

避免用Apache Beanutils进行属性的copy。 Inspection info: 避免用Apache Beanutils进行属性的copy。 说明&#xff1a;Apache BeanUtils性能较差&#xff0c;可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。 TestObject a new TestObject(); TestObject b new Te…

linux指令笔记

bash命令行讲解 lyt &#xff1a;是用户名 iZbp1i65rwtrfbmjetete2b2Z :这个是主机名 ~ &#xff1a;这个是当前目录 $ &#xff1a;这个是命令行提示符 每个指令都有不同的功能&#xff0c;大部分指令都可以带上选项来实现不同的效果。 一般指令和选项的格式&#xff1a;…

【openEuler/Centos】yum安装软件报Error: GPG check FAILED【分析根因弄明白,亲测有效不浪费时间】

yum安装软件报Error: GPG check FAILED 环境信息&#xff1a;cat /etc/openEuler-release openEuler release 22.03 (LTS-SP1) 报错信息 The downloaded packages were saved in cache until the next successful transaction. You can remove cached packages by executin…

当我们在微服务中使用API网关时,它是否会成为系统的瓶颈?这种潜在的瓶颈如何评估和解决?如何在微服务架构中保证高效请求流量?|API网关|微服务|异步处理

目录 1. API网关在微服务中的角色与重要性 2. API网关瓶颈的评估 2.1 请求延迟分析 2.2 并发请求量监控 2.3 内存和CPU使用情况 2.4 限流和熔断机制评估 2.5 日志分析 3. API网关瓶颈的解决方案 3.1 缓存机制优化 3.2 负载均衡优化 3.3 异步处理与消息队列 3.4 限流…

记录如何在RK3588板子上跑通paddle的OCR模型

官网文档地址 rknn_zoo RKNPU2_SDK RKNN Model Zoo 一、PC电脑是Ubuntu22.04系统中完成环境搭建(板子是20.04&#xff09; 安装模型转换环境 ​conda create -n rknn2 python3.10 conda activate rknn2 安装Ubuntu依赖包 su…

STM32 第18章 SysTick--系统定时器

时间:2024.10.26-10.27 参考资料: 《零死角玩转STM32》“SysTick--系统定时器”章节 一、学习内容 1.SysTick简介 1.1 SysTick: 系统定时器,24位,只能递减,存在于内核,嵌套在NVIC中,所有的Cortex-M内核的单片机都具有这个定时器。 官方参考手册里的介绍: 系统嘀…

riscv uboot 启动流程分析 - SPL启动流程

分析uboot 启动流程硬件&#xff1a;启明智显推出M4核心板 &#xff08;https://gitee.com/qiming-zhixian/m4-openwrt&#xff09; 1.U-boot和SPL概述 U-Boot 分为 uboot-spl 和 uboot 两个组成部分。SPL 是 Secondary Program Loader 的简称&#xff0c;第二阶段程序加载器。…

重塑在线软件开发新纪元:集成高效安全特性,深度解析与评估会员与促销管理系统的系统架构设计

案例 阅读以下关于软件架构设计与评估的叙述&#xff0c;回答问题1和问题2。 【题目】 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管…

简单的udp程序

文章目录 1. 预备知识1.1 源IP地址和目的IP地址1.2 端口号1.3 套接字初识1.4 tcp协议和udp协议简单认识1.5 网络字节序 2. udp程序2.1 创建套接字&#xff08;socket&#xff09;的系统调用2.2 bind()2.2.1 初始化一个sockaddr_in结构体2.2.2 inet_addr函数2.2.3 0.0.0.02.2.4 …

深入解析东芝TB62261FTG,步进电机驱动方案

TB62261FTG是一款由东芝推出的两相双极步进电机驱动器&#xff0c;采用了BiCD工艺&#xff0c;能够提供高效的电机控制。这款芯片具有多种优秀的功能&#xff0c;包括PWM斩波、内置电流调节、低导通电阻的MOSFET以及多种步进操作模式&#xff0c;使其非常适合用于需要精确运动控…

一步一步从微信小程序获取asp.net Core API的数据

前面我们说过&#xff0c;如何使用微信小程序获取asp.net的数据&#xff0c;这里我们继续介绍如何获取asp.net core api的数据。两者之间还是有一些差别的。本篇博文旨在详细介绍如何一步一步从微信小程序获取asp.net Core API的数据。 文章目录 一、建立并了解asp.net core we…

RabbitMQ集群搭建及使用

1. 概述 前提条件&#xff1a;linux服务器下已经安装好了docker服务。 本文档将搭建一个三台RabbitMQ的集群&#xff0c;包括三个RabbitMQ容器安装在同一服务器和三台不同的服务器。 2. 集群搭建 在一台服务器上创建三个RabbitMQ容器。 2.1.1. 创建容器 执行以下命令创建三…

合理利用IPIDEA代理IP,优化数据采集效率!

一、前言 在全球化与信息化交织的当代社会&#xff0c;数据已成为驱动商业智慧与技术革新的核心引擎。网络&#xff0c;作为信息汇聚与交流的枢纽&#xff0c;不仅是人们获取知识的窗口&#xff0c;更是商业活动与技术创新的广阔舞台。在这个信息繁荣的时代&#xff0c;Python…

Docker 实践与应用举例教程:从入门到精通

Docker 实践与应用举例教程&#xff1a;从入门到精通 引言 在现代软件开发中&#xff0c;Docker 已成为一种不可或缺的工具。它通过容器化技术简化了应用的部署、管理和扩展&#xff0c;极大地提高了开发和运维的效率。本文将详细介绍 Docker 的基本概念、安装步骤、常用命令…

开放式耳机哪个品牌音质好?音质最好的开放式耳机推荐!

如今&#xff0c;开放式耳机市场日益繁荣&#xff0c;成为了众多音乐爱好者和追求舒适佩戴体验者的新宠。然而&#xff0c;面对琳琅满目的品牌和产品&#xff0c;消费者往往陷入选择的困境。音质&#xff0c;作为衡量一款耳机优劣的关键因素&#xff0c;更是备受关注。究竟哪个…

反编译华为-研究功耗联网监控日志

摘要 待机功耗中联网目前已知的盲点&#xff1a;App自己都不知道的push类型的被动联网、app下载场景所需时长、组播联网、路由器打醒AP。 竞品 策略 华为 灭屏使用handler定时检测&#xff08;若灭屏30分钟内则周期1分钟&#xff0c;否则为2分钟&#xff09;&#xff0c;检…