算法学习笔记Day8——回溯算法

news2024/11/20 20:20:04

本文解决几个问题:

回溯算法是什么?解决回溯算法相关的问题有什么技巧?回溯算法代码是否有规律可循?

一、介绍

1.回溯算法是什么?

回溯算法就是个多叉树的遍历问题,关键在于在前序和后序时间点做一些操作,本质是一种暴力枚举算法,它和DFS非常相似,区别在于,回溯算法关注点在于树的树枝,DFS关注点在于树的节点。

2.回溯算法的技巧

站在一棵决策树的节点,需要考虑三个问题:

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件。

3. 回溯算法的框架(规律)

result = []
def backtrack(路径,选择列表){
    if(满足结束条件){
        result.add(路径)
        return;
    }
    
    for(选择 : 选择列表){
        //做选择
        将该选择从选择列表移除
        路径.add(选择)
        backtrack(路径, 选择列表)
        //撤销选择
        路径.remove(选择)
        将该选择再加入选择列表
    }

}

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,其实就是在维护每个节点的路径和选择列表信息,

抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案。

我们定义的 backtrack 函数在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层叶子节点,其「路径」就是一个答案。

4. 回溯算法和DFS的关系

回溯法 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

i)找到一个可能存在的正确的答案;
ii)在尝试了所有可能的分步方法后宣告该问题没有答案。

深度优先搜索 是一种用于遍历或搜索树或图的算法。这个算法会 尽可能深 的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。

5. 回溯算法的拓展

由于回溯算法的时间复杂度很高,因此在遍历的时候,如果能够提前知道这一条分支不能搜索到满意的结果,就可以提前结束,这一步操作称为 剪枝。剪枝是一种技巧,通常需要根据不同问题场景采用不同的剪枝策略,需要在做题的过程中不断总结。

二、排列、组合、子集相关问题

无论是排列、组合还是子集问题,简单说无非就是让你从序列 nums 中以给定规则取若干元素,主要有以下几种变体:

形式一、元素无重不可复选

形式二、元素可重不可复选

形式三、元素无重可复选

但无论形式怎么变化,其本质就是穷举所有解,而这些解呈现树形结构,所以合理使用回溯算法框架,稍改代码框架即可把这些问题一网打尽

为什么只要记住这两种树形结构就能解决所有相关问题呢?

首先,组合问题和子集问题其实是等价的,这个后面会讲;至于之前说的三种变化形式,无非是在这两棵树上剪掉或者增加一些树枝罢了

无重不可复选

核心:通过保证元素之间的相对顺序不变来防止出现重复的子集。

具体方法:使用 start 参数控制树枝的生长避免产生重复的子集,用 track 记录根节点到每个节点的路径的值,同时在前序位置把每个节点的路径值收集起来,完成回溯树的遍历就收集了所有子集。

例题1:子集

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> track;
    void traceback(vector<int>& nums, int start){
        // 每个节点的值都是一个子集
        ans.emplace_back(track);
        for(int i = start; i < nums.size(); i++){
            track.emplace_back(nums[i]);
            traceback(nums, i+1);
            track.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        traceback(nums, 0);
        return ans;
    }
};

例题2:组合 

分析

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> track;
    void backtrack(int n, int k, int start){
        if(track.size() == k){
            ans.emplace_back(track);
        }
        for(int i = start; i<= n; i++){
            track.emplace_back(i);
            backtrack(n, k, i+1);
            track.pop_back();
        }
    }
    vector<vector<int>> combine(int n, int k) {
        backtrack(n, k, 1);
        return ans;
    }
};

例题3:全排列

分析

选择列表就是遍历整个数组,如果没有用过某个数字,就选择它,然后把used数组对应的值设为true,然后进入节点(递归调用backtrack()),出来后撤销选择,方式是对路径弹出元素,然后更新used数组。

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<vector<int>> permute(vector<int>& nums) {
        vector<int> track;
        vector<bool> used(nums.size(), false);
        backtrack(nums, track, used);
        return ans;
    }
    void backtrack(vector<int>&nums, vector<int>&track, vector<bool>& used){
        if(track.size() == nums.size()){
            ans.push_back(track);
            return;
        }
        for(int i  =0; i < nums.size(); i++){
            if(used[i] == true){
                continue;
            }
            track.push_back(nums[i]);
            used[i] = true;
            backtrack(nums, track, used);
            track.pop_back();
            used[i] = false;
        }
    }
};

但如果题目不让你算全排列,而是让你算元素个数为 k 的排列,怎么算?

也很简单,改下 backtrack 函数的 base case,仅收集第 k 层的节点值即可

可重不可复选

例题4:子集 II

分析

元素可重的通用解决方法:排序 + used数组,连续相同的元素只能按顺序取,不能前面的没取取后面的。

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> track;
    void backtrack(vector<int>& nums, int start, vector<bool> used){
        ans.emplace_back(track);
        for(int i = start; i<nums.size(); i++){
             // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
            if(i>0 && nums[i] == nums[i-1] && used[i-1] == false){
                continue;
            }
            track.emplace_back(nums[i]);
            used[i] = true;
            backtrack(nums, i+1, used);
            used[i] = false;
            track.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<bool> used(nums.size(), false);
        backtrack(nums, 0, used);
        return ans;
    }
};

例题5:组合总和 II

代码

class Solution {
public:
    vector<int> track;
    vector<vector<int>> ans;
    void traceback(vector<int>& candidates, int target, vector<bool>& used, int start){
        int sum = accumulate(track.begin(), track.end(), 0);
        if(sum == target){
            ans.emplace_back(track);
            return;
        }
        if(sum > target){
            return;
        }
        for(int i  = start ; i< candidates.size(); i++){
            if(used[i] || ( i>0 && candidates[i-1] == candidates[i] && used[i-1] == false)){
                continue;
            }
            used[i] = true;
            track.emplace_back(candidates[i]);
            traceback(candidates, target, used, i+1);
            track.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<bool> used(candidates.size(), false);
        traceback(candidates, target, used, 0);
        return ans;
    }
};

例题6:全排列 II

分析

所有相同的数字,他的排列组合只有一个,记住这一点,然后用nums[i-1] == nums[i] && used[i-1] == false这个条件去限制,就可以做到相同数字只有一个排列进入答案。

代码

class Solution {
public:
    vector<vector<int>> ans;
    void backtrack(vector<int>& nums, vector<int>& track, vector<bool>& used){
        if(track.size() == nums.size()){
            ans.push_back(track);
            return;
        }
        for(int i = 0; i< nums.size(); i++){
            if(used[i]==true || (i > 0 && nums[i-1] == nums[i] && used[i-1] == false)){
                continue;
            }
            used[i] = true;
            track.push_back(nums[i]);
            backtrack(nums, track, used);
            track.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<int> track;
        vector<bool> used(nums.size(), false);
        backtrack(nums, track, used);
        return ans;
    }
};

元素无重可复选

例题7:组合总和

分析

想解决这种类型的问题,也得回到回溯树上,我们不妨先思考思考,标准的子集/组合问题是如何保证不重复使用元素的

答案在于 backtrack 递归时输入的参数 start,这个 i 从 start 开始,那么下一层回溯树就是从 start + 1 开始,从而保证 nums[start] 这个元素不会被重复使用,那么反过来,如果我想让每个元素被重复使用,我只要把 i + 1 改成 i 即可:

代码

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> track;
    int sum;
    void backtrack(vector<int>& nums, int target, int start){
        sum = accumulate(track.begin(), track.end(), 0);
        if(sum > target){
            return;
        }
        if(sum == target){
            ans.emplace_back(track);
            return;
        }
        for(int i  = start; i<nums.size(); i++){
            track.emplace_back(nums[i]);
            backtrack(nums, target, i);
            track.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& nums, int target) {
        backtrack(nums, target, 0);
        return ans;
    }
};

其他

例题8:排列序列

代码

思路1:回溯 + 剪枝

class Solution {
public:
    vector<vector<char>> ans;
    vector<char> track;
    int cnt = 0;
    void backtrack(int n, vector<bool> used){
        if(track.size() == n){
            ans.emplace_back(track);
            cnt++;
            return;
        }
        for(int i  = 0; i< n; i++){
            if(used[i]){
                continue;
            }
            track.emplace_back(i+1+'0');
            used[i] = true;
            backtrack(n, used);
            used[i] = false;
            track.pop_back();
        }
    }
    string getPermutation(int n, int k) {
        vector<bool> used(n, false);
        //如果数字过大,直接定位第一位
        if( k > 120){
            int factorial = 1;
            int first;
            for(int i = 1; i < n; i++){
                factorial *= i;
            }
            first = k/factorial;
            k %= factorial;
            track.emplace_back(first + 1 + '0');
            used[first] = true;
        }
        
        //业务逻辑
        backtrack(n, used);
        string ret(ans[k-1].begin(), ans[k-1].end());
        return ret;
    }
};

 思路2:直接定位

class Solution {
public:
    string getPermutation(int n, int k) {
    vector<char> ans, array;
    int tmp, ncp = n, f = 1;
    array.emplace_back('1');
    for(int i = 1; i < ncp; i++){
        f*= i;
        array.emplace_back(i+1+'0');
    }
    f*= ncp;
    k--;
    while(n != 0){
        f /= n--;
        tmp = k/f;
        k %= f;
        ans.emplace_back(array[tmp%ncp]);
        array.erase(array.begin() + tmp);
    }
    string ret(ans.begin(), ans.end());
    return ret;
    }
};

例题9:复原 IP 地址

代码

class Solution {
public:
    vector<string> ans;
    vector<int> segment;
    void backtrack(string s,int pos, int segcnt){
        int segnum = 0;
        if(pos == s.size() && segcnt  == 4){
            string track;
            for(int i = 0; i< 4; i++){
                track += to_string(segment[i]);
                if(i != 3) track += '.';
            }
            ans.emplace_back(track);
            //到达末尾return回去
            return;
        }
        else if((segcnt == 4 && pos != s.size()) || (pos == s.size() && segcnt != 4)){
            return;
        }
        if(s[pos] == '0'){
            segment[segcnt] = 0;
            backtrack(s, pos + 1, segcnt + 1);
            //撤销选择的方法是return
            return;
        }
        for(int i  = pos; i<s.size(); i++){
            segnum  = segnum*10 + (s[i] - '0');
            if(segnum > 0 && segnum <= 0xFF){
                segment[segcnt] = segnum;
                backtrack(s, i+1, segcnt + 1);
            }
            else{
                return;
            }
        }

    }
    vector<string> restoreIpAddresses(string s) {
        segment.resize(4);
        backtrack(s, 0, 0);
        return ans;
    }
};

三、Flood Fill(DFS)

例题10:单词搜索

分析

本题说是回溯,其实很像DFS的方法,参考:

DFS 算法解决岛屿题目

代码

class Solution {
public:
    int m, n;
    bool backtrack(vector<vector<char>>& board, string word, int pos, int i, int j){
        if(i >= m || j >= n || i<0 || j<0 || board[i][j] != word[pos]){
            return false;
        }
        if(pos == word.size()-1){
            return true;
        }
        bool res;
        board[i][j] = 0;
        res = backtrack(board, word, pos+1, i+1, j) || backtrack(board, word, pos+1, i-1, j)||
        backtrack(board, word, pos+1, i, j+1) || backtrack(board, word, pos+1, i, j-1);
        board[i][j] = word[pos];
        return res;
    }
    bool exist(vector<vector<char>>& board, string word) {
        m = board.size();n = board[0].size();
        for(int i = 0; i< m; i++){
            for(int j = 0; j<n; j++){
                if(backtrack(board, word, 0, i, j))
                    return true;
            }
        }
        return false;
    }
};

例题11:被围绕的区域

DFS 算法解决岛屿题目 中有详解

例题12:岛屿数量

DFS 算法解决岛屿题目 中有详解

例题13:​​​​​​​图像渲染

分析

dfs模板:越界返回

                访问返回

                非目标返回

                dfs递归

代码

class Solution {
public:
    int m, n;
    int samecolor;
    void dfs(vector<vector<int>>& image, int i, int j, int color){
        if(i>=m || j>= n|| i<0 || j<0){
            return;
        }
        if(image[i][j] == color){
            return;
        }
        if(image[i][j] != samecolor){
            return;
        }
        image[i][j] = color;
        dfs(image, i+1, j, color);
        dfs(image, i, j+1, color);
        dfs(image, i, j-1, color);
        dfs(image, i-1, j, color);
    }
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) {
        m = image.size(); n = image[0].size();
        samecolor = image[sr][sc];
        dfs(image, sr, sc, color);
        return image;
    }
};

 

三、字符串中的回溯问题

例题14:电话号码的字母组合

代码

class Solution {
public:
    vector<string> ans;
    string track;
    vector<string>array;
    void backtrack(string digits, int pos){
        if(track.size() == digits.size()){
            ans.emplace_back(track);
            return;
        }
        //根据数字算出其对应的选择有哪些
        for(char c : array[digits[pos]-'0']){
            track += c;
            backtrack(digits, pos+1);
            track.erase(track.end()-1);
        }
    }
    vector<string> letterCombinations(string digits) {
        if(digits.size() == 0){
            return ans;
        }
        array.resize(10);
        for(int i = 0; i<18; i++){
            array[i/3+2] += i+'a';
        }
        array[7] += 's'; array[8] = "tuv"; array[9] = "wxyz";
        backtrack(digits, 0);
        return ans;
    }
};

改良:这里用hashmap存储选择数组比较好

class Solution {
public:
    vector<string> ans;
    string track;
    unordered_map<char, string> array;
    void backtrack(string digits, int pos){
        if(track.size() == digits.size()){
            ans.emplace_back(track);
            return;
        }
        for(char c : array[digits[pos]]){
            track += c;
            backtrack(digits, pos+1);
            track.erase(track.end()-1);
        }
    }
    vector<string> letterCombinations(string digits) {
        if(digits.size() == 0){
            return ans;
        }
        array = {
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
        backtrack(digits, 0);
        return ans;
    }
};

 

例题15:字母大小写全排列

代码

class Solution {
public:
    vector<string> ans;
    string track;
    string choice(char c){
        string ret;
        ret += c;
        if(c >= 'a' && c <= 'z'){
            ret += toupper(c);
        }
        if(c >= 'A' && c <= 'Z'){
            ret += tolower(c);
        }
        return ret;
    }
    void backtrack(string s, int pos){
        if(track.size() == s.size()){
            ans.emplace_back(track);
            return;
        }
        //做选择
        for(char c : choice(s[pos])){
            track += c;
            backtrack(s, pos+1);
            track.erase(track.end()-1);
        }
    }
    vector<string> letterCasePermutation(string s) {
        backtrack(s, 0);
        return ans;
    }
};

例题16:括号生成

分析

代码

三、游戏问题

例题17:N 皇后

分析

代码

例题18:解数独

分析

代码

例题19:​​​​​​​祖玛游戏

分析

代码

例题20:​​​​​​​扫雷游戏

分析

代码

四、总结

i)全排列解决方法:

用used数组来选取没有选过的元素

ii)元素可重的通用解决方法

排序 + used 数组,连续相同的元素只能按顺序取,不能前面的没取取后面的。

iii)组合/子集(非排列)解决方法

backtrack传start进去,控制取元素的顺序,避免重复访问。

可重复选:递归的时候传 i

不可重复选:递归的时候传 i + 1

比如,[1, 2, 3],固定一的时候[1,3]取走了,下一次固定3,如果不控制顺序,还会取到[3,1],这样按组合的逻辑来说就是重复的。

iii)只要从树的角度思考,这些问题看似复杂多变,实则改改 base case 就能解决

iv)vector<char>转化为string:直接string str(v.begin(), v.end());

v)string删去最后一个元素: string.erase(string.end()-1);

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

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

相关文章

大学生在线考试|基于SprinBoot+vue的在线试题库系统系统(源码+数据库+文档)

大学生在线考试目录 基于SprinBootvue的在线试题库系统系统 一、前言 二、系统设计 三、系统功能设计 试卷管理 试题管理 考试管理 错题本 考试记录 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#…

wps屏幕录制怎么用?分享使用方法!

数字化时代&#xff0c;屏幕录制已成为我们学习、工作和娱乐中不可或缺的一部分。无论是制作教学视频、分享游戏过程&#xff0c;还是录制网络会议&#xff0c;屏幕录制都能帮助我们轻松实现。WPS作为一款功能强大的办公软件&#xff0c;其屏幕录制功能也备受用户青睐。本文将详…

【LLM多模态】Qwen-VL模型结构和训练流程

note 观点&#xff1a;现有很多多模态大模型是基于预训练&#xff08;和SFT对齐&#xff09;的语言模型&#xff0c;将视觉特征token化并对齐到语言空间中&#xff0c;利用语言模型得到多模态LLM的输出。如何设计更好的图像tokenizer以及定位语言模型在多模态LLM中的作用很重要…

LeetCode54. 螺旋矩阵

LeetCode54.螺旋矩阵 题解思路 代码 class Solution { public:vector<int> spiralOrder(vector<vector<int>>& matrix) {vector<int> res;int n matrix.size();// 行int m matrix[0].size(); // 列vector<vector<bool>> st(n, v…

分步搭建HF区块链网络--部署fabric区块链--加入channel

一.搭建网络规划 采用容器环境&#xff0c;搭建1个排序节点(Orderer)、2个对等节点(Peer)&#xff0c;另外用 一个fabric-cli容器。实训中的绝大部分命令是通过该容器执行的。 容器名称设置 二. 配置HF网络证书 首先docker ps 检查镜像&#xff0c;确保镜像为空 1.生成crypto…

leaftjs+turfjs+idw纯前端实现等值面绘图

最近有个绘制等值面图的需求。我们一般的实现路径是&#xff1a; 1.后台绘图&#xff0c;用surfer绘制好&#xff0c;给前端调用叠加到地图。 2.后台用python绘图&#xff0c;绘制好给前端调用&#xff0c;叠加到地图。 3.后台进行插值计算、地图裁剪、最终生成geojson文件或…

[集群聊天项目] muduo网络库

目录 网络服务器编程常用模型什么是muduo网络库什么是epoll muduo网络库服务器编程 网络服务器编程常用模型 【方案1】 &#xff1a; accept read/write 不是并发服务器 【方案2】 &#xff1a; accept fork - process-pre-connection 适合并发连接数不大&#xff0c;计算任…

第一篇:Python简介:开启你的编程之旅

Python简介&#xff1a;开启你的编程之旅 在这个系列文章中&#xff0c;我将带领大家深入了解Python——一个极具魅力的编程语言。如果你对编程感兴趣&#xff0c;想要掌握一门既实用又强大的语言&#xff0c;那么Python无疑是一个绝佳的选择。本篇文章是这个系列的序章&#…

文件包含漏洞基础

php 中的文件包含函数&#xff1a; incude &#xff1a; require incude_once require_once 为了减少重复性代码的编写&#xff1b; 任意后缀的文件当中只要存在 php 代码就会被当作 php 执行&#xff1b; 本质&#xff1a;由于包含的文件不可控&#xff0c;导致文件包含…

S32K的JLINK与PE接线方法与刷程序失败问题

S32K的JLINK与PE接线方法与刷程序失败问题 1、PE的接线方法2、JLINK的接线方法3、刷程序失败问题 1、PE的接线方法 2、JLINK的接线方法 3、刷程序失败问题 出现如下问题&#xff1a; Secure Debug might be enabled on this device.lf so.please unlock the device via PEmic…

Maven基础篇4

跳过测试(了解) 1.背景 你的模块还没完成&#xff0c;项目经理就已经完成测试模块的开发 自己的模块写好提交测试&#xff0c;看看功能有没有完成&#xff0c;完成了那么就成功&#xff0c;否则失败&#xff1b; 使用背景&#xff1a; 1.测试用例写好了&#xff0c;自己模…

【计算机网络】成功解决 ARP项添加失败:请求的操作需要提升

最近在用Wireshark做实验时候&#xff0c;需要清空本机ARP表和DNS缓存&#xff0c;所以在cmd窗口输入以下命令&#xff0c; 结果发生了错误&#xff1a;ARP项添加失败&#xff1a;请求的操作需要提升 一开始我还以为是操作的命令升级了&#xff0c;但是后面发现其实只是给的权…

C++面向对象程序设计 - 继承与派生

面向对象技术强调软件的可重用性&#xff08;software reusability&#xff09;&#xff0c;C语言提供了类的继承机制&#xff0c;解决了软件重用问题。 C中所谓“继承”就是在一个已存在的类的基础上建立一个新类&#xff0c;从已有的类那里获得已有特性&#xff0c;叫做类的继…

网络协议深度解析:SSL、 TLS、HTTP和 DNS(C/C++代码实现)

在数字化时代&#xff0c;网络协议构成了互联网通信的基石。SSL、TLS、HTTP和DNS是其中最关键的几种&#xff0c;它们确保了我们的数据安全传输、网页的正确显示以及域名的正常解析。 要理解这些协议&#xff0c;首先需要了解网络分层模型。SSL和TLS位于传输层之上&#xff0c…

说说2024年暑期三下乡社会实践工作新闻投稿经验

作为一名在校大学生,我有幸自去年起参与学院组织的暑期大学生三下乡社会实践团活动。这项活动不仅是我们深入基层、服务社会的重要平台,也是展现当代大学生风采、传递青春正能量的有效途径。然而,如何将这些生动鲜活的实践故事、感人至深的瞬间传播出去,让更多人了解并受到启发…

在PostgreSQL中如何创建和使用自定义函数,包括内置语言(如PL/pgSQL)和外部语言(如Python、C等)?

文章目录 一、使用内置语言 PL/pgSQL 创建自定义函数示例代码使用方法 二、使用外部语言 Python 创建自定义函数安装 PL/Python 扩展示例代码使用方法 三、使用外部语言 C 创建自定义函数编写 C 代码编译为共享库在 PostgreSQL 中注册函数注意事项 总结 PostgreSQL 是一个强大的…

前端H5动态背景登录页面(下)

最近正好有点儿时间&#xff0c;把之前没整理完的前端动态背景登录页面给整理一下&#xff01;这是之前的连接前端H5动态背景登录页面&#xff08;上&#xff09;&#xff0c;这主要是两个登陆页面&#xff0c;一个彩色气泡&#xff0c;一个动态云朵&#xff0c;感兴趣的可以点…

08 内核开发-避免冲突和死锁-mutex

08 内核开发-避免冲突和死锁-mutex 课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中。 …

U盘无法正常格式化?教你一个强力的办法

前言 电脑格式化U盘或者移动硬盘的操作&#xff0c;相信各位小伙伴都是有一定经历的。 如果设备正常&#xff0c;那么进入到【此电脑】&#xff0c;在对应的分区点击【鼠标右键】-【格式化】就可以把对应的存储设备恢复到初始状态。 但凡事都会有例外&#xff0c;比如在格式化…

实验 | RT-Thread:L1

1 线程间同步 同步是指按预定的先后次序进行运行&#xff0c;线程同步是指多个线程通过特定的机制&#xff08;如互斥量&#xff0c;事件对象&#xff0c;临界区&#xff09;来控制线程之间的执行顺序&#xff0c;也可以说是在线程之间通过同步建立起执行顺序的关系&#xff0…