数据结构:图的最短路径

目录

一、最短路径的基本概念

二、无权图单源最短路径

三、Dijkstra算法(正权图单源)

3.1、算法的基本步骤

3.2、算法的实现

3.3、习题思考

3.3.1、网络延迟时间

四、A*算法(正权图单源单目标点)

4.1、算法的基本概念

4.2、算法的重要证明

五、Floyd算法(带权每对顶点间的最短路)

5.1、算法的基本原理

5.2、算法的实现

5.3、扩展

六、Bellman-Ford算法(负权图单源最短路径问题)

6.1、算法的基本原理

6.2、算法的实现

七、SPFA(贝尔曼福特队列优化)

7.1、算法的基本思想

7.2、算法的实现


本篇所有证明都是博主自己想的,如有疑问,扔给GPT,他会帮你解答。

一、最短路径的基本概念

  • 无权图:路径包含的边的条数。
  • 带权图:路径包含的各边权值之和。
  • 长度最小的路径称为最短路径,最短路径的长度也称为最短距离。

二、无权图单源最短路径

        无权图单源最短路径使用BFS求出,时间复杂度为O(n+e)。该算法可以求出单源到所有顶点的最短路,并且可以通过静态链表的方式存储各顶点的最短路径信息。

        在BFS访问的过程中,当访问某个顶点时,就确定了该点与源点的最短距离。

#include<bits/stdc++.h>
using namespace std;
#define n 10
struct Edge{
    int VerName;
    Edge * next;
};
struct Vertex{
    int VerName;
    Edge * edge=nullptr;
};
vector<int> path(n);
Vertex Head[n];
vector<int> dist(n,0x3f3f3f3f);
void BFS(Vertex * Head,int s){
    dist[s]=0;path[s]=-1;
    queue<int> q;
    q.push(s);
    while(!q.empty()) {
        int pre=q.front();q.pop();
        for(Edge * edge=Head[pre].edge;edge!=nullptr;edge=edge->next) {
            int k=edge->VerName;
            if(dist[k]==0x3f3f3f3f) {//未被访问过
                dist[k]=dist[pre]+1;
                q.push(k);
                path[k]=pre;//记录该路径的前驱
            }
        }
    }
    return;
}
int main(void) {return 0;}

三、Dijkstra算法(正权图单源)

        虽然在关键路径中,我们使用过拓扑排序+动态规划求最长路径,时间复杂度是O(n+e)。但是这是在图存在拓扑排序时才能使用的。一般情况下的图更加复杂。一般情况下的正权图,可以使用迪杰斯特拉算法求出最短路径。迪杰斯特拉不能求带有负权图的最短路径。

        Dijkstra算法可以求出单源点到所有点的最短路径。该算法使用优先队列可以优化成O(nlogn+e)。

        Dijkstra算法不能求负权图的原因是,即使当前在优先队列中选定了当前距离源点最短的一个点,此时确定它的最短路径,但是由于负权边的存在,可能使得未来,它到源点的最短距离更短。比如求v0到各点的最短路,下图中的v1会比v2先确定,且认为v1的最短路就是5,实际上应该是先选择v2,求得v1最短路是-1。如果仅仅存在正权,路径的权值只能增加不能减少,当前最小就是最小(贪心的局部最优),因此不会出现这样的情况。

5ff46397a6cc4ef0a0420456ff148648.png

3.1、算法的基本步骤

①将图中的所有顶点分成两个集合,一个是集合S,一个是集合V-S,S中的顶点表示已经确定最短路的顶点,V-S中表示尚未确定最短路的顶点。

②在初始时S为空集,将源点压入优先队列,执行③

③在V-S中选择离源点最近的顶点(包括源点自身),将该顶点放入S,表示该顶点已经求出最短路径,执行④

④遍历该顶点的所有边,更新其他顶点的最短路径,重复③,直至所有顶点都已经求出最短路径。

        其主要思想是贪心。该算法实现时遍历所有顶点一次,遍历所有边一次,对于每一个顶点的所有边访问的顶点只要不在S中都压入优先队列中(延迟出栈,不修改队列中的元素,也可以直接自定义实现修改队列中的元素),可能重复压栈,因此时间复杂度为O(nloge+e)。由于e<=n^2 loge<=2logn,取最大阶,因此时间复杂度为O(nlogn+e)。

正确性证明:证明在S中的顶点的最短路一定被求出,在V-S中新选择的顶点其最短路一定已经被求出。

数学归纳法:令S中的元素个数为n

①当n=0时,选择源点,源点到源点必然是最短路。

②当n=1时,即源点更新最短路,在V-S中选择一步到源点的最短边对应的顶点v,也一定是v的最短路,因为从源点走其他一步的长度都比源点到v的路径更长,因此直接走到v必然是最短的,将其加入到S中,此时S中的元素都已经被求出最短路。

③当n=k-1时(k>=1),反证法:假设在V-S当前到源点的最短的距离顶点为v,假如目前该最短距离还不是源点到v的最短路,则必然有一条源点到v的路径经过一点u,使得源点到v的最短路距离更短(且u不在S中,不然u就已经更新过v了,就不存在这个u的说法了),由于最短路的性质,此时源点到u的最短路会更短,且u不在S中,所以应该选择的是u而不是v,因此与选择v矛盾,则目前该最短距离是v到源点的最短路,将其加入到S中,此时S中的元素都已经被求出最短路,证毕。

3.2、算法的实现

        这里采用STL的优先队列的方法,优先队列采用延迟出栈,出栈的顶点若不在S中,则选择它,如果出栈的顶点在S中,则忽略该顶点,直至找到真正不在S中的顶点,多个副本在优先队列中是没问题的,因为多个副本距离最短的优先出队。出队防止队列为空!

这里直接通过距离来判断是否在S中:同样的距离仍然会被更新,但是不影响。

int n=100;
vector<int> dist(n,0x3f3f3f3f);
vector<int> path(n);
void Dijkstra(Vertex * Head,int s){
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;//默认是大根堆,定义小根堆! {dist,k} 直接使用pair的比对函数,先比较dist,dist小者优先
    q.push({0,s});
    dist[s]=0;path[s]=-1;
    while(!q.empty()){//优先队列不为空继续遍历,如果已经遍历了所有顶点,循环内部也会解决该问题
        int pre=q.top().second;
        int dis=q.top().first;q.pop();
        if(dist[pre]<dis) continue;//优先队列中弹出的pre的最小值,应当是当前pre存储的最小值,才算是最小的,不是最小的出队。不能保证每个点只更新一次,但这样的多次更新对结果没影响
        for(Edge * edge=Head[pre].edge;edge!=nullptr;edge=edge->next) {
            int k=edge->VerName;
            if(dist[k]<dist[pre]+edge->cost){//小时更新,等于时也不更新了。
                path[k]=pre;//更新前驱
                q.push({dist[k],k});//只有被更新了才导入,没被更新不是最优解就不导了
                
            }
        }
    }
    return;
}

使用vector实现的,并使用“集合”的思想:

        vector<int> S(n,0);
        while(!q.empty()){
            int pre=q.top().second;q.pop();
            if(S[pre]==1) continue;//pre在S中,即pre已经更新过则继续
            S[pre]=1;//标记pre放入S并更新,保证每个点只更新一次了
            for(auto i : Head[pre]) {
                int k=i.first;
                if(dist[pre]+i.second<dist[k]){//只有距离更短才放入,当然随着更新,可能同一个点多次放入,但是我们只在优先队列中选择一次,所以之后用S[pre]可以判断。
                    dist[k]=dist[pre]+i.second;
                    q.push({dist[k],k});
                }
            }
        }

3.3、习题思考

3.3.1、网络延迟时间

8e2397634023469c8d012904bd42160d.png

实际上由于STL的引入,存储边并不需要使用邻接表了,使用vector就能解决存储边的问题!以后我们都用vector存储边。

class Solution {
public:
    const int INF=0x3f3f3f3f;
    int Dijkstra(vector<vector<int>>& times, int n, int s) {
        vector<vector<pair<int,int> > > Head(n);
        for(auto &i:times){
            Head[i[0]-1].emplace_back(i[1]-1,i[2]);
        }
        vector<int> dist(n,0x3f3f3f3f);
        priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q;
        q.push({0,s});
        dist[s]=0;
        while(!q.empty()){
            int pre=q.top().second;
            int dis=q.top().first;q.pop();
            if(dist[pre]<dis) continue;//
            for(auto i : Head[pre]) {
                int k=i.first;
                if(dist[pre]+i.second<dist[k]){
                    dist[k]=dist[pre]+i.second;
                    q.push({dist[k],k});//只有被更新了才导入,没被更新不是最优解就不导了
                }
            }
        }
        int max=-1;
        for(auto i:dist) if(max<i) max=i;
        if(max!=INF) return max;
        return -1;
    }
        int networkDelayTime(vector<vector<int>>& times, int n, int k) {
            return Dijkstra(times,n,k-1);
        }
};

四、A*算法(正权图单源单目标点)

4.1、算法的基本概念

【路径规划】全局路径规划算法——A*算法(含python实现 | c++实现)

  • 在游戏中,如《新倩女幽魂》里的自动寻路功能,通常采用路径规划算法来计算从当前位置到目标位置的最佳路径。这种路径规划可能会涉及到各种算法,如A*(A-Star)、Dijkstra等,这些算法在寻找最短路径的同时,也会考虑其他因素,如路径上的障碍、地形的不同代价以及可能的动态变化等。

  • A*算法是一种启发式搜索,它每次选择估价函数最小的顶点进行下一步移动。

  • A*算法的执行步骤和Dijkstra算法很相似,区别就是A*算法每次选择的不是 没被选择过的里面距离源点最短的顶点更新,而是选择没被选择过的里面 估价函数最低的顶点更新。

  • 估价函数f(v) 包含了dijkstra里面距离源点的最短路径g(v),还多出来了h(v)。并且我们更新的最短路实际上也是更新的g(v),只是在选择更新时选择了f(v)而已,选择f(v)可以让算法更快抵达目标点。

4.2、算法的重要证明

  • 当保证h(v)<=h*(v)时,我们根据之前的阐述可以发现,最短路的顶点是可能被选到的,因为h(v)==h*(v)时,最短路径上的顶点f(v)总是最小的,当h(v)不超过实际代价时,最短路径上的顶点f(v)也大概率会比其他顶点小,因此会优先被更新。并且对于一些顶点到源点的距离g(v)就可能已经超过了最短路径上的f(v),因此不会再被更新。

  • 证明启发函数小时能找到最短路:A* 算法的一个核心停止条件是当目标节点被移动到关闭列表(已经被更新过)时,此时算法认为已经找到了一条路径,并且基于启发式函数的设定,认为这是最优路径。如果h(v)>h*(v),那么最优路径的顶点可能在优先队列中靠后,导致更新不及时,最终到达终点,找到近似解,当然也可能找到最优解。而如果h(v)<=h*(v),则在终点更新扩展时,终点的估价函数等于f0,而最短路径上的估价函数等于f(v)=g(v)+h(v)<=g(v)+h*(v)<=f0,而若终点未被最短路径上的顶点更新,那么其f必然是大于f0的(因为g(v)更大,h(v)=0),因此最短路径上的顶点会比终点先扩展,g(v)是唯一确定的,扩展后终点的g(v)也会被确定为最短。这意味着,对于最短路径上的任一节点 v,f(v)≤f0​,确保了在目标节点的 f0​ 值被确定之前,最短路径上的所有节点都已被考虑。这是因为 A* 算法总是优先扩展 f 值最小的节点。

  • 780a8824ddcc40e492928b574ed5ce02.png

  • 273bd35b079449a7a787e499fdfd3057.png

  • 535c5b51c1f94b58ace4f117abbd5c25.png

  • 794f44fc4067480eb40cbf136f5e98fc.png

解释:

95378134a47b43acb8cec080f08361e7.png

五、Floyd算法(带权每对顶点间的最短路)

带权每对顶点间的最短路:

①如果全是正权,可以让每个顶点都做一次源点,执行n次Dijkstra算法,时间复杂度为O(n²logn)

②带有负权,也可以使用Floyd算法,时间复杂度为O(n³)

5.1、算法的基本原理

        Floyd算法实际上是一个动态规划算法,它定义dp[k][i][j],表示能经过顶点0~k时,i到j的最短路。

        状态转移方程:dp[k][i][j]=min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j]),即经过顶点0~k时,i到j的最短路 等于 {经过顶点0~k-1时,i到j的最短路,可以经过顶点0~k-1时,i先经过k,再从k到j的距离}的最小值

        降维优化,由于只用到之前的距离,因此可以直接将三维数组改写成二维dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j])。//我们不需要担心i==k||j==k时,更新它是会影响之后的更新情况,不用担心,因为k能经过k 和k能经过0~k-1是没区别的。

         不允许存在负环(环上所有边的权值和为负数,理论上不存在最短路,因为可以无限小,Floyd此时求不出最短路)。Floyd算法可以判负环,初始时D[i][i]=0,算法执行过程中若D[i][i]<0,表示顶点i到自己的最短距离为负值,即存在负环。(可以这样考虑为什么不允许存在负环,初始化时,你将dp[i][i]初始化为0,表示自己到自己应该是0距离,而实际上存在负环,可以是负无穷,因此即使算法没逻辑问题,但是初始化就导致算法结果出现问题)

5.2、算法的实现

int A[n][n];
vector<vector<int> >dp(n,vector<int>(n,0));
vector<vector<int> >path(n,vector<int>(n,0));
void Floyd(int n){
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j){
            dp[i][j]=A[i][j];
            if(i!=j)
                path[i][j]=i;//表示i到j的最短路 j 的前驱
            else path[i][j]=-1;
        }
    for(int k=0;k<n;++k)
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                if(dp[i][j]>dp[i][k]+dp[k][j]){
                    dp[i][j]=dp[i][k]+dp[k][j];
                    path[i][j]=path[k][j];//i到j的最短路前驱变成了k到j的最短路前驱。
                }
    return;
}

5.3、扩展

不经过 v1,v2,v3,v4··的最短路(不存在v1,v2,v3,v4··为中间顶点时),我们直接让k不能等于它们即可。

    for(int k=0;k<n;++k)
        if(k!=v1&&k!=v2&&k!=v3&&k!=v4)//使得k不能作为中间结点~
        for(int i=0;i<n;++i)
            for(int j=0;j<n;++j)
                if(dp[i][j]>dp[i][k]+dp[k][j]){
                    dp[i][j]=dp[i][k]+dp[k][j];
                    path[i][j]=path[k][j];//i到j的最短路前驱变成了k到j的最短路前驱。
                }

六、Bellman-Ford算法(负权图单源最短路径问题)

6.1、算法的基本原理

先看一下是什么:

b2756ff5dfaf4b2cbde3809d364d22dc.png

afd40d32abd7475aa3543261548ed7b9.png

再来说说为什么:

        我们可以将源点到所有顶点的所有可能路径展开(类似一个散射图),不管重不重复出现,将所有可能的路径都向外展开就行。这样,我们将所有的可能路径都分开了,每一条单独而不影响!

        我们再来看算法,当算法第1次迭代时,必定只有离原点只有一条边的顶点的距离被更新,第2次迭代时才可以更新离源点有两条边的顶点,依次类推。那么,我们是如何找到最短路的?对于同一个顶点v,可能出现在我们刚刚展开的图中两条(或多条)路径中,我们来证明一下v最短路径那条路径(包含e条边)一定会在e次迭代后被成功更新,即第e次一定会迭代出v的最短路径长度。假设v的最短路径包含e条边,且前驱是u,根据最短路的性质,源点到u也应该是最短路,若不然,u的最短路在另外一条路径中的长度应该更长(至少包含e条边,因为本条路径对于u来说到源点一共e-1条边),因为u还未被更新为最短路,一旦被更新为最短路,则u的最短值不可能被更改,所以u如果在v的该路中不是最短路,则u应该会更长,这与v的前驱是u,且最短路包含e条边矛盾,因为u可以更短且包含更多的边,则v的前驱有u,则v也可以包含更多的边而不是e,与之前矛盾,所以u确实是v的最短路。证毕。

再来看看更容易的证明:

06731f05cca8476eaf653ded1c30f0ee.png

最优子结构的存在,对于v的最短路来说,源点到v之前的所有顶点都必然是自己的最短路,这样的话,v的最短路e条边,实际上迭代e次必然就出现了~(因为迭代e次相当于只能往外走e次,并且一旦被更新为最短路,则最短值不可能被更改)。

最优子结构是因为,源点到v的最短路经过x,那么源点到x也必然是最短路,因为v的最短路=<源点,u,x>+<x,,v),如果<源点,u,x> 不是x的最短路,那么换一个最短的,v还能短,所以v最短必然x也最短~()

d1f5bad9fb1c41909daebe4881f206f5.png

15f802435edc4466b585e56b2cb1b383.png

6.2、算法的实现

不存储边:时间复杂度O(n²+ne)<=O(n³),注意,虽然最坏也是n³,但是Floyd可以求两两之间的最短路~

vector<int> path(n,-1);
vector<int> dist(n,0x3f3f3f3f);
bool Bellman_Ford(vector<vector<pair<int,int> > > & g,int n,int s){
    dist[s]=0;
    for(int k=1;k<n;++k)//只需要迭代n-1次 就行~
        for(int i=0;i<g.size();++i)
            for(int j=0;j<g[i].size();++j)
                if(dist[i]+g[i][j].second<dist[j]){
                    dist[j]=dist[i]+g[i][j].second;
                    path[j]=i;
                }
    
    for(int i=0;i<g.size();++i)//检测负环
        for(int j=0;j<g[i].size();++j)
            if(dist[i]+g[i][j].second<dist[j]){//还能更新,存在负环!
                return false;
            }
    return true;
}

存储边:时间复杂度O(n+ne)<=O(n³)

vector<int> path(n,-1);
vector<int> dist(n,INT_MAX);
bool Bellman_Ford(vector<vector<pair<int,int> > > & g,int n,int s){
    vector<pair<int,pair<int,int> > > edge;
    for(int i=0;i<g.size();++i)
        for(auto &j:g[i])
            edge.push_back({i,j});
    dist[s]=0;
    for(int k=1;k<n;++k)//只需要迭代n-1次 就行~
        for(auto & i:edge)
            if(dist[i.first]+i.second.second<dist[i.second.first]){
                dist[i.second.first]=dist[i.first]+i.second.second;
                path[i.second.first]=i.first;
            }
    
    for(auto & i:edge)//检测负环
       if(dist[i.first]+i.second.second<dist[i.second.first]){
          return false;
       }
    return true;
}

七、SPFA(贝尔曼福特队列优化)

SPFA实际上是贝尔曼福特算法的队列优化版本。

SPFA存在卡时,如网格图可以将SPFA卡成O(ne)。

7.1、算法的基本思想

        SPFA和Bellman-Ford算法的区别在于,SPFA每次迭代并不遍历所有边,因为可能有些边的遍历是多余的,比如第k-1次遍历,没有更新dist[u],且第k-1次遍历会遍历尝试更新u邻接的所有顶点的dist,则第k次遍历 用上一次一样的dist[u]继续更新是没有意义的。

        因此我们使用的方法的核心思想仍然是迭代次数 遍历边,但是我们只遍历这次迭代被更新顶点的边:我们使用一个队列保存需要用来更新的顶点,用该顶点更新其邻居,当其邻居的dist被更新了,则将其邻居入队,相当于下一次迭代我们只需要遍历这些被更新的邻居即可,由于队列是先进先出的,我们直接不断取出不断弹出直到队列为空即可。这里我们还可以优化一部分,就是如果在队列中的顶点就不需要继续入队,因为一个点v 更新其邻接顶点 使用越接近最短距离的越好;可能在“本次迭代”更新的时候,压入了一个顶点u,然后之后的某一次这个顶点u还没出队,又更新了这个顶点u,这个顶点u如果在队列中我们就不需要压入了,原因是即使是不压入,u也确实被更新了,它仍然会带着这个被更新的值 继续用最好的dist去更新。(这和dijkstra不一样, SPFA队列中是点 不是值 值在外部)

        

        入队次数等于n说明个什么问题? 

        由于SPFA相当于Bellman-Ford算法的优化,实际上也是一个迭代过程,如果我们保证每次迭代队列中的元素只入队一次的话。也就是说可以想象成队列中的元素是按迭代次序摆放的且分开的,下一次迭代的元素会在本次被压入队列中,因此近似地入队次数可以被认为是一个顶点的迭代次数。准确的说,入队次数是≤Bellman-ford的迭代次数的,因为第k次迭代更新的 顶点的结果 应该在k+1次迭代中使用并进行更新其邻接顶点,但是可能在第k次迭代被更新时,此时还没出队,即它也将进行第k次迭代,则它不会再被入队,因此也不会参与第k+1次迭代了,即第k+1次不会更新该顶点的邻居,所以入队次数少于等于实际的迭代次数的,如果此时入队次数达到了n则至少在Bellman-Ford算法中的迭代次数达到了n,负权环的存在可以使得更新产生无数次,入队次数也可以无数次。

        如果入队次数等于n说明一个顶点在不同的路径中存在着n个不同的位置,否则根据Bellman-Ford算法的思想,它不能被迭代n次!而不存在负环的图,n个顶点,任何一个顶点最多出现在一条路的n-1个不同的位置,因此如果有n个位置就说明存在负环,路径可以无限长。

7.2、算法的实现

bool SPFA(vector<vector<pair<int,int>>> &g,int n,int s){
    vector<int> path(n,-1);
    vector<int> times(n,0);//入队次数
    queue<int> q;
    vector<int> dist(n,INT_MAX);
    vector<int> Inqueue(n,0);
    dist[s]=0;
    q.push(s);times[s]+=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        Inqueue[u]=0;
        for(auto &i:g[u]){
            int v=i.first;int weight=i.second;
            if(dist[u]+weight<dist[v]){
                dist[v]=dist[u]+weight;
                path[v]=u;
                if(Inqueue[v]==0){
                    Inqueue[v]=1;
                    q.push(v);
                    if(++times[v]==n) return false;//一次迭代只算一次入队哦
                }
            }
        }

    }
    return true;
}

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

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

相关文章

web自动化--元素定位之xpath和css

元素定位 xpath绝对路径相对路径案例xpath策略&#xff08;路径&#xff09;案例xpath策略&#xff08;层级、扩展&#xff09;属性层级与属性层级与属性拓展层级与属性综合 csscss选择器&#xff08;id、类、标签、属性&#xff09;id选择器类选择器标签选择器属性选择器案例-…

黄衣老太怒骂,难阻年轻人的热情,苹果CEO库克笑开怀

苹果在中国最大的专卖店在上海静安开业&#xff0c;壮观的排队场景--特别的是排队的大多都是年轻人&#xff0c;凸显出国内消费者对苹果的热情&#xff0c;苹果CEO库克亲自在专门店门口迎客&#xff0c;还与消费者热情拥抱。 不过在苹果静安店开业的当天出现了一个插曲&#xf…

E3S独立出版 | 2024年第二届绿色建筑国际会议(ICoGB 2024)

会议简介 Brief Introduction 2024年第二届绿色建筑国际会议(ICoGB 2024) 会议时间&#xff1a;2024年5月22日-24日 召开地点&#xff1a;意大利米兰 大会官网&#xff1a;www.icogb.org ICoGB 2024由意大利米兰理工大学主办&#xff0c;西安交通大学&#xff0c;葡萄牙米尼奥大…

ubuntu20.04安装 ffmpeg 开发环境

参考&#xff1a;参考1 一些相关软件包&#xff0c;已打包整理好&#xff0c;如下 源码包 1、安装步骤 创建安装目录 sudo mkdir -p /usr/local/ffmpeg/lib 解压源码 tar -jxf ffmpeg-4.3.2.tar.bz2 到指定ffmpeg目录进行配置 cd ffmpeg-4.3.2/ 配置&#xff1a;会报错很多…

对BOM的理解,常见的BOM对象有哪些?(非常详细)

文章目录 一、是什么二、window三、location四、navigator五、screen六、history 一、是什么 BOM (Browser Object Model)&#xff0c;浏览器对象模型&#xff0c;提供了独立于内容与浏览器窗口进行交互的对象 其作用就是跟浏览器做一些交互效果,比如如何进行页面的后退&…

C++ Thread 源码 观后 自我感悟 整理

Thread的主要数据成员为_Thr 里面存储的是线程句柄和线程ID 先看看赋值运算符的移动构造 最开始判断线程的ID是否不为0 _STD就是使用std的域 如果线程ID不为0&#xff0c;那么就抛出异常 这里_New_val使用了完美转发&#xff0c;交换_Val和_New_val的值 _Thr _STD exchange(_…

使用 chezmoi vscode, 管理你的 dotfiles

什么是 dotfiles In Unix-like operating systems, any file or folder that starts with a dot character (for example, /home/user/.config), commonly called a dot file or dotfile. 任何以 . 开头去命名的文件或者目录都可以称为 dotfile, 在 Unix-like 系统一般用的比较…

【生成式AI導論 2024】第5講:訓練不了人工智慧?你可以訓練你自己 (下) — 讓語言彼此合作,把一個人活成一個團隊 (開頭有芙莉蓮雷,慎入)

文章目录 视频简介 视频内容 视频简介 from: https://www.油管.com/watch?vinebiWdQW-4 1,849次观看 2024年3月24日 「把一個人活成一個團隊」是從羅振宇老師 2024 跨年演講聽來的&#xff1a; • AI会不会取代人&#xff1f;看看疾病诊断、挖掘机、美容院、solopreneur的案…

小目标检测篇 | YOLOv8改进之增加小目标检测层(四头检测机制)

前言:Hello大家好,我是小哥谈。小目标检测是计算机视觉领域中的一个研究方向,旨在从图像或视频中准确地检测和定位尺寸较小的目标物体。相比于常规目标检测任务,小目标检测更具挑战性,因为小目标通常具有低分辨率、低对比度和模糊等特点,容易被背景干扰或遮挡。为了解决小…

【IoT新星导航】物联网技术人的发展方向

目录 物联网的概念 下面是我对物联网两个方向的认识&#xff1a; 物联网硬件方向&#xff1a; 一般路线&#xff1a; C语言&#xff1a; 单片机&#xff1a; 嵌入式RTOS&#xff1a; 嵌入式Linux&#xff1a; 物联网软件方向&#xff1a; 一般路线&#xff1a; 编程语言的选…

【Linux】进程的进一步认识

目录 进程的创建 fork函数初步认识 fork函数的返回值 写时拷贝 操作系统怎么知道什么时候要写时拷贝的呢&#xff1f; fork的常规用法 fork调用失败的原因 进程终止 进程的退出场景 进程常见退出方法 正常终止&#xff08;可以通过 echo $? 查看进程退出码&#xff…

Linux 常用命令 1

Tips&#xff1a;终端热键ctrl shift 放大终端窗口的字体 ctrl - 缩小终端窗口的字体 注意区分大小写 查阅命令帮助信息&#xff1a; 1&#xff09;--help command –help(两个减号) 显示command命令的帮助信息 2&#xff09;man man command 查阅command命令的使…

【动手学深度学习】深入浅出深度学习之PyTorch基础

目录 一、实验目的 二、实验准备 三、实验内容 1. 数据操作 2. 数据预处理 3. 线性代数 4. 微积分 5. 自动微分 四、实验心得 一、实验目的 &#xff08;1&#xff09;正确理解深度学习所需的数学知识&#xff1b; &#xff08;2&#xff09;学习一些关于数据的实用…

逆向爬虫技术的进阶应用与实战技巧

前言 在互联网的海洋中&#xff0c;数据是无价的财富。爬虫技术作为获取这些数据的重要手段&#xff0c;一直备受关注。然而&#xff0c;随着网站反爬虫机制的日益完善&#xff0c;简单的爬虫程序已经很难满足我们的需求。因此&#xff0c;掌握爬虫逆向技术&#xff0c;突破反爬…

智慧农业引领未来:数字乡村推动农业现代化与智能化

随着信息技术的飞速发展&#xff0c;数字乡村已成为推动农业现代化与智能化的重要力量。智慧农业作为数字乡村的核心组成部分&#xff0c;正以其独特的优势引领未来农业的发展方向。本文将从智慧农业的内涵、发展现状、面临的挑战以及未来展望等方面&#xff0c;探讨数字乡村如…

初始Java篇(JavaSE基础语法)(2)(逻辑控制)

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 逻辑控制 顺序结构 分支结构 if语句 switch 语句 循环结构 while 循环 for 循环 do while 循环 输入输出 输出到控制台 从键盘输入 …

登录与注册功能(简单版)(4)注册时使用Session校验图片验证码

目录 1、需求及实现流程分析 2、实现 1&#xff09;新建register.jsp 2&#xff09;导入CheckCodeUtil工具类 3&#xff09;新建CheckCodeServlet 4&#xff09;修改RegisterServlet 5&#xff09;启动访问 1、需求及实现流程分析 验证码的作用&#xff1a;防止机器自动…

【Leetcode】2549. 统计桌面上的不同数字

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个正整数 n n n &#xff0c;开始时&#xff0c;它放在桌面上。在 1 0 9 10^9 109 天内&#xff0c;每天都要执行下述步骤&#xff1a; 对于出现在桌面上的每个数字 x &am…

The plain HTTP request was sent to HTTPS port

异常信息 原因 错误信息 “The plain HTTP request was sent to HTTPS port” 表明客户端尝试使用未加密的HTTP协议发送请求到一个配置为使用加密的HTTPS协议的端口。 解决方案 要解决这个问题&#xff0c;需要确保使用正确的协议和端口号进行请求。应该使用的HTTPS前缀。例如…

Naive UI:一个 Vue 3 组件库,比较完整,主题可调,使用 TypeScript,快有点意思。

在当今的前端开发领域&#xff0c;Vue 3已成为中后台应用的首选框架。为了满足开发者的需求&#xff0c;各种组件库如雨后春笋般涌现。其中&#xff0c;Naive UI以其独特的优势&#xff0c;成为了Vue 3开发者的得力助手。本文将深入探讨Naive UI的特性、优势以及如何使用它来提…