由树形解空间入手,深入分析回溯、动态规划、分治算法的共同点和不同点

news2025/1/14 0:55:16

一、回溯、动态规划、分治

其实这三个算法就可以直接认为是一类算法,它们的共同点都是会涉及到递归

更需要清楚明白的一点是,它们的解,都是基于一颗递归形式的树上得来的!也就是——回溯、动态规划、分治的解空间是 一棵树!!!(正是由于这种解的形式,它们都和递归分不开,因为递归就是适合解决这种树结构的求解问题!)

为什么这么说呢,下面我们来逐个分析一下!!!

1.1:回溯

1.1.1:核心思想:构建决策树,遍历路径,收集答案

  • 回溯:可以看作是暴力穷举,递归(eg:全排列问题);可能会有很多重复子问题(很多重复计算,因为子问题之间都有关联)。它的本质就是遍历一颗 决策树 (每个节点都是在做决策),然后全部遍历完成后将叶子节点上的答案进行 收集 起来,就能得到最终答案

    🌸回溯算法和 DFS 算法的细微差别是:回溯算法是在遍历 「树枝」 ,DFS 算法是在遍历 「节点」

    🍀站在回溯树的一个节点上,你只需要思考 3 个问题:
    1、路径:也就是已经做出的选择
    2、选择列表:也就是你当前可以做的选择。
    3、结束条件:也就是到达决策树底层,无法再做选择的条件。

    🪧代码框架:

    result = []
    def backtrack(路径, 选择列表):
        if 满足结束条件:
            result.add(路径)
            return
        
        for 选择 in 选择列表:
            做选择
            backtrack(路径, 选择列表)
            撤销选择
    

    其核心就是 for 循环里面的递归, 在递归调用之前在这个决策节点上「做选择」,在递归调用之后「撤销选择」

具体举例解释(求数字1,2,3的全排列):
在这里插入图片描述
比如在上图这样一颗二叉树,就是我们这个由回溯法求解全排列问题的解空间,我们的解是从这颗三叉树中得来的(包括动态规划和分治算法,它们的解空间本质上也是这样一颗多叉树,但是像动态规划可能存在重复子问题呀,会使用dp备忘录数组进行剪枝)。

而各个算法的关键以及区分点也就是

  1. 如何去构建这样一给解空间(一颗多叉树)——递归函数的定义
  2. 这个多叉树的节点含义是什么
  3. 如何基于这个解空间获得问题的答案。

上面两点既是这三个算法在求解问题流程上的共同点,而不同点是在于这两个流程具体实施的做法不同, 同样,对于这三个算法,只需要弄懂了以上几点,对折三个算法的理解、区别也就了然了。

再回到上面那个全排列问题和那个图,我们来逐一击破

  1. 🚉🚉🚉想要求解[1,2,3] 这三个全排列问题,很容易其实能想到上面那个树形结构,基于暴力穷举的想法,那么我们的二叉树其实就能构建好,很自然而然就能想象到下面这个图形(即使没有学过数据结构,但你的思路和想法的本质也一定是这样的)
    在这里插入图片描述

  2. 🚉🚉🚉基于回溯算法,每个节点其实都在做决策(这也是为什么把由回溯算法生成的解空间的树称为决策树)

    🍀站在回溯树的一个节点上,你只需要思考 3 个问题:
    1、路径:也就是已经做出的选择
    2、选择列表:也就是你当前可以做的选择。
    3、结束条件:也就是到达决策树底层,无法再做选择的条件。在这里插入图片描述
    比如上面那个图的2节点,你的 “路径” 就是[2], “选择列表” 是[1,3],“结束条件” 为:该决策节点的选择列表为空/

  3. 🚉🚉🚉得到这样一颗决策树的解空间后,我们就需要获得问题的答案了,做法是:回溯算法的这个回溯递归函数backtrack 其实就像是一个指针,游走在这颗树上,遍历完每条路径到叶子节点,这个路径上的值即为一个解,把所有路径遍历完成,即将所有答案收集,得到了问题的最终解!

🍊🍊🍊! 注意:实际过程中,肯定不是像上面讲的那样,先把树构建好了,然后去在这棵树上求解,反而是2、3两步同时进行的过程,一边构建这个解空间树,一边求解的过程,上面那么讲也只是为了好理解。比如再回到全排列问题,这个回溯递归函数backtrack 像一个指针,在构建树,维护每个节点的属性(路径、选择列表)时,还需要保证走到叶子节点时收集由这条路径得到的答案。


1.1.2:求解答案:如何遍历树(和DFS的区别)

其实呢,弄懂上面三点,你对整个回溯算法的理解其实已经就差不多了,这时基于宏观理解上面,再深入到回溯算法本身的特性上,进一步,从第三点,遍历树,寻找答案,那么具体该如何遍历呢?

对于回溯算法决策树的遍历的框架如下:

void traverse(TreeNode* root) {
    for (TreeNode* child : root->childern) {
        // 前序位置需要的操作
        traverse(child);
        // 后序位置需要的操作
    }
}

在这里插入图片描述

这里有一点需要注意的是,这个遍历框架和DFS遍历树的框架不太一样,多叉树的DFS遍历代码如下:

/* 多叉树遍历框架 */
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    // 前序位置
    for (TreeNode* child : root->children) {
        traverse(child);
    }
    // 后序位置

可以看到,DFS的前序遍历位置和后序位置在for循环遍历子节点外面,而回溯算法遍历树的前序位置和后续位置在遍历子节点的for循环之内。

其实在1.1.1小节讲到回溯算法的核心思想开头就已经提到:

🌸回溯算法和 DFS 算法的细微差别是:回溯算法是在遍历 「树枝」 ,DFS 算法是在遍历 「节点」

也就是说,回溯算法更关注的是边,它遍历的对象是边,而非节点,它所遍历的就是root节点下的边,而DFS遍历的就是root这个节点,两者关注点不一样,你想遍历哪个,就按照对应的代码进行书写。

在这里插入图片描述

⭐它们的区别可以这样反映的代码上,可以更直观的理解!


// DFS 算法,关注点在节点
void traverse(TreeNode* root) {
    if (root == nullptr) return;
    printf("进入节点 %s", root);
    for (TreeNode* child : root->children) {
        traverse(child);
    }
    printf("离开节点 %s", root);
}

// 回溯算法,关注点在树枝
void backtrack(TreeNode *root) {
    if (root == nullptr) return;
    for (TreeNode* child : root->children) {
        // 做选择
        printf("从 %s 到 %s", root, child);
        backtrack(child);
        // 撤销选择
        printf("从 %s 到 %s", child, root);
    }
}


到上面,我们基本上弄懂了了回溯的遍历思路以及和DFS遍历思路侧重点的不同,那么下面我们再回过头来看回溯算法的决策树和遍历过程:

⭐⭐⭐⭐⭐ 「路径」和「选择」是每个节点的属性,函数在树上游走要正确处理节点的属性,那么就要在这两个特殊时间点搞点动作:

  • 前序遍历的代码在进入某一个节点(做决策)之前的那个时间点执行:进行当前决策节点的 【做选择】
  • 后序遍历代码在离开某个节点之后的那个时间点执行:【撤销选择】,即撤销当前这个节点的当前这个选择(为了从选择列表中选取下一个选择做准备)
    在这里插入图片描述

那么现在,对核心框架中的这段代码我们应该有更好的理解了:

for 选择 in 选择列表:
    # 做选择
    将该选择从选择列表移除(表示做这个选择)
    路径.add(选择)
    
    #基于这个选择往后面的子节点进行树枝遍历
    backtrack(路径, 选择列表)
    
    # 撤销选择(为从选择列表中做出下一个选择做准备)
    路径.remove(选择)
    将该选择再加入选择列表

他是对于当前非结束决策节点的操作,本质是在决策树上遍历当前节点下面的边,从而在遍历(也是构建)决策树的过程中收集答案。

1.1.3:学习代码实现细节:例题:N排列

1:题目描述

题目链接:N排列
在这里插入图片描述

2:解题思路

其实整体的解题思路在上面解题思路中已经讲解了,这里以例题形式更详细解释一下详细代码的实现过程:

还是以[1,2,3]为例,整个解空间为以下树的形式
在这里插入图片描述

🪧 Tips: 这里的每个节点可以有一个used数组属性,其实这是【选择列表】的抽象,表示当前还有哪些元素没用,从而得到剩余集合:真正的选择列表。而【排列】也就是指遍历的到当前决策节点处得到的【路径】在这里插入图片描述
即:没有显式记录「选择列表」,而是通过 used 数组排除已经存在 track 中的元素,从而推导出当前的选择列表:在这里插入图片描述

3:⭐代码实现:回溯三部曲

🪧回顾:核心代码框架:

	result = []
	def backtrack(路径, 选择列表):
	    if 满足结束条件:
	        result.add(路径)
	        return
	    
	    for 选择 in 选择列表:
	        做选择
	        backtrack(路径, 选择列表)
	        撤销选择

有了解题思路,代码框架,我们具体实现代码时,其实就是对代码框架进行填充,完善,把不确定的部分给实现了,其实总结下来这个完善过程就是以下的递归三部曲:

  1. 递归函数的定义(明确这个递归函数的作用,包括参数:路径、选择列表)——明确决策节点的两个属性:路径、选择列表

      这里我们递归函数的定义就是对

    vector<vector<int>> result; //毋庸置疑,是用于在递归遍历过程中收集答案的
     oid backtrack(vector<int>& nums, vector<int>& track, vector<bool>& used)
    
  2. 递归终止条件: 选择列表为空,而我们的选择列表并不是一个真实存在的列表,而是numsused数组共同体现的一个东西,判断终止也可以用以下代码判断:路径的大小和nums大小相同,其实本质也就是说选择列表中的元素已经用完了

      // 触发结束条件
            if (track.size() == nums.size()) {
                res.push_back(track);
                return;
            }
    
  3. 求解答案单层搜索的逻辑(回溯遍历):这个其实就是指backtracking这个函数在这个树结构上“游走”的过程,即遍历树的过程,这在`1.1.2·小节中已经讲过,不再重复

      	for (int i = 0; i < nums.size(); i++) {
                // 排除不合法的选择
                if (used[i]) {
                    //nums[i] 已经在 track中,跳过
                    continue;
                }
                // 做选择
                track.push_back(nums[i]);
                used[i] = true;
                // 进入下一层决策树
                backtrack(nums, track, used);
                // 取消选择
                track.pop_back();
                used[i] = false;
            }
    

✅完整代码:

class Solution {
public:

    vector<vector<int>> result; //全局变量,存储在回溯过程中收集的最终结果

    //定义回溯代码(核心框架)
    void backtracking(vector<int>&nums, vector<bool>& used, vector<int>& track){
        
        //某条回溯路径的终止条件
        if(track.size() == nums.size() ){
            result.push_back(track);
            return;
        }

        //从选择列表里做选择
        for(int i = 0; i < nums.size(); i ++){
            if(used[i]){
                continue;
            }
            //做选择
            used[i] = true;
            track.push_back(nums[i]);
            //进入下一层决策树
            backtracking(nums, used, track);
            //撤销选择(为下一个选择做准备)
            used[i] = false;
            track.pop_back();
        }
    }

    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();

        vector<int> track; //记录当前路径
        vector<bool> used (nums.size(), false); // 结合nums数组共同作用为【选择列表】,true代表当前元素已经被选择
        backtracking(nums, used, track); // 执行核心算法逻辑:回溯
        
        return result; //返回最终结果

    }
};

1.2:动态规划

1.2.1:核心思想

  • 动态规划:一般是来求最值/最优值问题;存在最优子结构(问题的最优解由子问题的最优解推导而来)即基于子问题最优解进行选择,由子问题 推导 出问题的最终最优解。

关于动态规划的思想篇我推荐看东哥的这篇:动态规划解题套路框架

当然,只有真正实践了,刷题了,才能更好的去理解一个算法,围绕动态规划的解题五部曲,我也写了几篇博客:

  • 【Leetcode每日一刷】动态规划算法: 62. 不同路径、63. 不同路径 II
  • 【Leetcode每日一刷】动态规划:509. 斐波那契数、322. 零钱兑换、300. 最长递增子序列
  • 【Leetcode每日一刷】动态规划:931. 下降路径最小和

在这篇文章中,我依然想从这三个角度,来解释这些算法:

而各个算法的关键以及区分点也就是

  1. 如何去构建这样一给解空间(一颗多叉树)——递归函数的定义
  2. 这个多叉树的节点含义是什么
  3. 如何基于这个解空间获得问题的答案。

这里,我也会从一个例题入手:322. 零钱兑换去讲解,建立在我们已经有前面几篇博客的知识基础上,去从这三个角度去深挖动态规划的本质(这也是在前面几篇博客里没有讲到的)。
在这里插入图片描述
关于怎么做这个题,可以看我前面的题解博客,现在我们从上面三个角度,去反思一下:

  1. 动态规划的树形解空间:在这里插入图片描述
    和回溯算法一样,动态规划也是一个树形的解空间,它的构建方法是:由状态转移方程,将原问题分解成规模更小的子问题(最优子结构),由子问题去 推导(这个推导很值得深究) 出问题的解。

  2. 通过前面对回溯算法的学习,我们知道,回溯的决策树上的点代表一个选择(决策),它有两个属性:路径&选择列表。同样,对于动态规划的解空间,它的节点的含义很简单:基于原问题所划分的小规模子问题的解

  3. 如何基于这个解空间去获得问题的答案?说白了,就是我们获得了这样一棵树,答案也是我们通过处理这个树,取得来的。对于回溯,它的做法是收集决策树的子节点路径。而对于动态规划呢?其实也就是我们要深究上面第一点所说的这个【推导】的含义,具体是什么?其实就是状态转移方程(下面的节点推导出上面节点的值),但其本质呢?其实本质就是对树上节点的处理,这个处理,是基于节点的,对于动态规划,它的处理策略就是:在这里插入图片描述
    其实你看,就是那个状态转移方程,由子问题(子节点)推导而来。

1.2.2:分析:重叠子问题和子问题独立性?

OK,首先我们来说说子问题的重叠,动态规划的一大特点就是消除了子问题的重叠性,这个是毋庸置疑的。下面这张图其实是给出在暴力递归下的树形解空间:
在这里插入图片描述
可以看到,对于节点5,其实在构造这个抽象的树形解空间时,它被计算了多次,而动态规划对此的改进就是:备忘录、DP数组。本质就是在追求 “如何聪明地穷举” 。用空间换时间的思路。

那么子问题的独立性呢?我在不同的地方看到不同的解释,可能你在学习的时候也会有疑惑。后来我才发现,这两个“独立”并不是一个含义,接下来我将逐个讲解。

  • 子问题相互独立,是最优子结构的前提——>是可以用DP求解的前提

      这里我们用一个例子来说明:比如说,假设你考试,每门科目的成绩都是互相独立的。你的原问题是考出最高的总成绩,那么你的子问题就是要把语文考到最高,数学考到最高…… 为了每门课考到最高,你要把每门课相应的选择题分数拿到最高,填空题分数拿到最高…… 当然,最终就是你每门课都是满分,这就是最高的总成绩。
      得到了正确的结果:最高的总成绩就是总分。因为这个过程符合最优子结构,「每门科目考到最高」这些子问题是互相独立,互不干扰的。
      但是,如果加一个条件:你的语文成绩和数学成绩会互相制约,不能同时达到满分,数学分数高,语文分数就会降低,反之亦然。
      这样的话,显然你能考到的最高总成绩就达不到总分了,按刚才那个思路就会得到错误的结果。因为「每门科目考到最高」的子问题并不独立,语文数学成绩户互相影响,无法同时最优,所以最优子结构被破坏。
      回到凑零钱问题,为什么说它符合最优子结构呢?假设你有面值为 1, 2, 5 的硬币,你想求 amount = 11 时的最少硬币数(原问题),如果你知道凑出 amount = 10, 9, 6 的最少硬币数(子问题),你只需要把子问题的答案加一(再选一枚面值为 1, 2, 5 的硬币),求个最小值,就是原问题的答案。因为硬币的数量是没有限制的,所以子问题之间没有相互制约,是互相独立的。

  • 子问题不独立:这里的“独立”是指,子问题之间具有相互推导关系你看下面这个图dp[11]可以由下面的三个孩子根据状态方程推得,而dp[10]是也可以由它下面三个孩子根据状态方程推得…其实这个不独立主要是和下面要讲的分治算法作比较而提出的不独立,和上面那个独立是两个概念。这
    在这里插入图片描述

1.3:分治

1.3.1:核心思想

  • 分治:分治则是,原问题的解本身就是可以分解为与原问题相同但规模更小的子问题,然后 合并 子问题,从而得到原问题的解。

最典型的分治算法就是归并排序了,核心逻辑如下:

void sort(int[] nums, int lo, int hi) {
    int mid = (lo + hi) / 2;
    /****** 分 ******/
    // 对数组的两部分分别排序
    sort(nums, lo, mid);
    sort(nums, mid + 1, hi);
    /****** 治 ******/
    // 合并两个排好序的子数组
    merge(nums, lo, mid, hi);
}

「对数组排序」是一个可以运用分治思想的算法问题,只要我先把数组的左半部分排序,再把右半部分排序,最后把两部分合并,不就是对整个数组排序了吗?

同样的,我们再从三个视角对这个算法进行审视(以归并排序为例)在这里插入图片描述

  1. 分治算法的树形结构:和动态规划其实有的像的是,分治算法树形结构也是构建的核心也是:将原问题分解为规模更小的子问题,但是这个分并不基于什么状态转移方程,因为它的子问题之间没有什么关系(也就是上面在动态规划里讲的:子问题之间的推导关系)——所以由于这种情况的存在,分治也不存在什么DP里的重叠子问题!
  2. 每个节点的含义:这个就和动态规划一样了:原问题较小规模的子问题的解
  3. 如何基于这个树形解空间来求得问题的解?其实对于分治,很明显的一点就是:合并,没错。其实你看处理位置上,和动态规划一样,就是处理方法不一样。动态规划采用状态转移方程(即:推导)进行处理,而分治采用的是合并!在这里插入图片描述

1.3.2:例题:棋盘覆盖问题

1:题目描述

在这里插入图片描述

2:解题思路

这题就是我觉得比起知道用分治怎么解,更难的是想到应该用分治!

直接上图吧!

在这里插入图片描述
分析:

  1. 首先,当k>0时,将 2 k × 2 k 2^k×2^k 2k×2k的棋盘可以等分为四个规模更小且与原问题类型一致的问题:4个 2 k − 1 × 2 k − 1 2^{k-1}×2^{k-1} 2k1×2k1的棋盘覆盖问题,其中有三个不存在特殊方格,可以使用一个L型先进行覆盖,那么其覆盖到这3个子棋盘的会和的位置,作为该子问题的特殊方格
  2. 经过上面处理,我们成功地获得了4个 2 k − 1 × 2 k − 1 2^{k-1}×2^{k-1} 2k1×2k1的棋盘覆盖子问题。递归地使用这种分割策略(也就是分治里的【分】),直到棋盘为1×1则终止(递归必需的终止条件)
  3. 再合并结果(其实不用显示合并,只用在递归遍历时把结果填入,最终全部遍历完,就是已经是合并后的结果了)

实现:

  1. 明确递归函数的定义是什么,相信并且利用好函数的定义!
<
//tr,tc(top_row,top_col):代表棋盘左上角坐标
//sr,sc(special_row, special_col):代表特殊点坐标
//size:棋盘的大小

//递归
void chessBoard(int tr, int tc, int sr, int sc, int size);
  1. 递归函数的实现:这题很简单,就是怎么分!进行四个判断即可!
    每次判断是基于分割后的四个小方块进行判断,判断特殊方格是否在内。判断方法是根据分割后小棋盘左上角的坐标和特殊棋盘的坐标进行比较。若在里面则直接接着递归下去,如果不在呢,根据没有特殊方格的三个子棋盘的位置不同,将汇合处标记为特殊位置。

✅完整代码

#include <stdio.h>
#include <stdlib.h>
#include <cstring>    // Include for memset

int nCount = 0;
int Matrix[100][100];

void chessBoard(int tr, int tc, int sr, int sc, int size);

int main()
{
    int size,r,c,row,col;
    std::memset(Matrix,0,sizeof(Matrix));    // Use std:: prefix for memset
    scanf("%d",&size);
    scanf("%d%d",&row,&col);
    chessBoard(0,0,row,col,size);

    for (r = 0; r < size; r++)
    {
        for (c = 0; c < size; c++)
        {
            printf("%2d ",Matrix[r][c]);
        }
        printf("\n");
    }

    return 0;
}

void chessBoard(int tr, int tc, int sr, int sc, int size)
{
    //tr and tc represent the top left corner's coordinate of the matrix

    if (1 == size) return;

    int s,t;
    s = size/2; //The number of grid the matrix's edge
    t = ++ nCount;

    //locate the special  grid on bottom right corner
    if (sr < tr + s && sc < tc +s)
    {
        chessBoard(tr,tc,sr,sc,s);
    }
    else
    {
        Matrix[tr+s-1][tc+s-1] = t;
        chessBoard(tr,tc,tr+s-1,tc+s-1,s);
    }

    //locate the special  grid on bottom left corner
    if (sr < tr + s && sc >= tc + s )
    {
        chessBoard(tr,tc+s,sr,sc,s);
    }
    else
    {
        Matrix[tr+s-1][tc+s] = t;
        chessBoard(tr,tc+s,tr+s-1,tc+s,s);
    }

    //locate the special  grid on top right corner
    if (sr >= tr + s && sc < tc + s)
    {
        chessBoard(tr+s,tc,sr,sc,s);
    }
    else
    {
        Matrix[tr+s][tc+s-1] = t;
        chessBoard(tr+s,tc,tr+s,tc+s-1,s);
    }

    //locate the special  grid on top left corner
    if (sr >= tr + s && sc >= tc + s)
    {
        chessBoard(tr+s,tc+s,sr,sc,s);
    }
    else
    {
        Matrix[tr+s][tc+s] = t;
        chessBoard(tr+s,tc+s,tr+s,tc+s,s);
    }

}

二、总结

回溯、动态规划、分治可以认为是一类算法,其实不用严格的将其分为太开,它们的共同点是解空间都是一棵多叉树,获得解也是在这个多叉树上进行操作

只需要从这三个视角对其有一个直观认识即可

而各个算法的关键以及区分点也就是

  1. 如何去构建这样一给解空间(一颗多叉树)——递归函数的定义
  2. 这个多叉树的节点含义是什么
  3. 如何基于这个解空间获得问题的答案。

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

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

相关文章

ssm+vue的私人健身和教练预约管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的私人健身和教练预约管理系统(有报告)。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通…

深度学习常用优化算法笔记介绍,各种梯度下降法详细介绍

优化算法 mini-batch梯度下降法 当一个数据集其数据量非常大的时候&#xff0c;比如上百万上千万的数据集&#xff0c;如果采用普通的梯度下降法&#xff0c;那么运算速度会非常慢&#xff0c;因为如果使用梯度下降法在每一次迭代的时候&#xff0c;都需要将这整个上百万的数…

机器学习:基于TF-IDF算法、决策树,使用NLTK库对亚马逊美食评论进行情绪分析

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

第一天复习Qt文件读取

Qt文件操作&#xff1a; 1、QFile QTextStream操作文件案例&#xff1a; 1、打开文件 QFile file(absolute filepath | relative path); file.readLine()返回内容长度&#xff0c;如果为-1就是读取失败 file. Close()读取后关闭 file.errorString()返回文件打开发生的错误2、…

数据仓库实验三:分类规则挖掘实验

目录 一、实验目的二、实验内容和要求三、实验步骤1、创建数据库和表2、决策树分类规则挖掘&#xff08;1&#xff09;新建一个 Analysis Services 项目 jueceshu&#xff08;2&#xff09;建立数据源视图&#xff08;3&#xff09;建立挖掘结构 DST.dmm&#xff08;4&#xff…

16.接口自动化学习-编码处理与装饰器

1.编码和解码 编码&#xff1a;将自然语言翻译成计算机可以识别的语言 hello–01010 解码&#xff1a;将机器识别的语言翻译成自然语言 2.编码格式 UTF-8 GBK unicode 3.编码操作 #编码操作str1"hello呀哈哈哈"str2str1.encode(gbk)print(str2)print(type(str2))…

FPGA ov5640视频以太网传输

1 实验任务 使用DFZU4EV MPSoC 开发板及双目OV5640摄像头其中一个摄像头实现图像采集&#xff0c;并通过开发板上的以太网接口发送给上位机实时显示。 2 Verilog代码 2.1 顶层模块 timescale 1ns / 1ps //以太网传输视频顶层模块module ov5640_udp_pc (input sys_cl…

在WPS表格(Excel)中,每10行增加一个特定的值

注&#xff1a;如下为WPS表格操作演示 例如1&#xff0d;15的数值是1&#xff0c;16-30就变为2&#xff0c;31-45就变为3&#xff0c;类推&#xff01; 1、在B1单元格输入一个起始值&#xff0c;B2单元格输入公式IF(MOD(ROW(),15)0,B11,B1) 然后鼠标放到B2单元格右下角小点处&…

开机弹窗找不到OpenCL.dll是怎么回事,哪种修复方法更推荐

当用户在操作电脑过程中遇到系统提示“OpenCL.dll丢失”时&#xff0c;这究竟是怎么一回事呢&#xff1f;OpenCL.dll&#xff0c;作为Open Computing Language&#xff08;开放计算语言&#xff09;的重要动态链接库文件&#xff0c;它在图形处理器&#xff08;GPU&#xff09;…

springboot整合rabbitmq的不同工作模式详解

前提是已经安装并启动了rabbitmq&#xff0c;并且项目已经引入rabbitmq&#xff0c;完成了配置。 不同模式所需参数不同&#xff0c;生产者可以根据参数不同使用重载的convertAndSend方法。而消费者均是直接监听某个队列。 不同的交换机是实现不同工作模式的关键组件.每种交换…

县供电公司员工向媒体投稿发文章用亲身经历告诉你并不难

在县供电公司的日子里,我肩负着一项至关重要的使命——信息宣传工作。这不仅仅是一份职责,更是连接公司与外界的桥梁,通过新闻稿件传递我们的声音,展示我们的成果。然而,回忆起刚刚踏入这个领域的时光,那段经历至今让我感慨万千。 初涉投稿,步履维艰 刚接手这项工作时,我的投稿…

信息化飞速发展下,源代码防泄密方案该如何选择?常见四种有效方案分享

信息化时代发展迅速&#xff0c;数据防泄露一词也频繁的出现在我们身边。无论企业或政府单位&#xff0c;无纸化办公场景越来越多&#xff0c;数据泄露的时间也层出不穷。例如&#xff1a;世界最大职业中介网站Monster遭到黑客大规模攻击&#xff0c;黑客窃取在网站注册的数百万…

Dockerfile实践java项目

目的&#xff1a;用java项目测试dockerfil部署&#xff08;前提是安装好了docker&#xff09; 部署准备文件如下 1. java项目 java项目demo地址 https://gitee.com/xiaoqu_12/dockerfileDemo.git 或者百度网盘直接下载打包好的jar包 链接&#xff1a;https://pan.baidu.com/s/…

PostgreSQL的学习心得和知识总结(一百四十一)|深入理解PostgreSQL数据库数据库角色的使用及预定义角色的原理

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

【计算机毕业设计】基于SSM++jsp的电子竞技管理平台【源码+lw+部署文档+讲解】

目录 1 绪论 1.1 研究背景 1.2 目的和意义 1.3 论文结构安排 2 相关技术 2.1 SSM框架介绍 2.2 B/S结构介绍 2.3 Mysql数据库介绍 3 系统分析 3.1 系统可行性分析 3.1.1 技术可行性分析 3.1.2 经济可行性分析 3.1.3 运行可行性分析 3.2 系统性能分析 3.2.1 易用性指标 3.2.2 可…

您的浏览器不支持 undefined 代理认证!如有问题请联系您的浏览器支持,请勿反馈此问题给 SwitchyOmega.

一、【问题描述】 PAC 文件是一个 JavaScript 文件&#xff0c;用于定义客户端的代理规则。您可以在 PAC 文件中编写规则&#xff0c;根据不同的目标网址或其他条件&#xff0c;决定是否通过代理服务器进行访问。您可以将 PAC 文件部署到服务器上&#xff0c;并在客户端配置浏…

QT学习PCL库代码

找关键点keypoints 绿色的点就是keypoints outofcore

应用软件安全保证措施方案书

系统安全保证措施方案—word原件 软件全套资料进主页获取或者本文末个人名片直接获取。

【Docker学习】docker run的端口映射-p和-P选项

docker run的端口映射选项分为-p&#xff08;小写&#xff0c;全称--publish&#xff09;&#xff0c;-P&#xff08;大写&#xff0c;全称--publish-all&#xff09;&#xff0c;之前认为只有改变容器发布给宿主机的默认端口号才会进行-p的设置&#xff0c;而不改变默认端口号…

STC8增强型单片机开发 【第一个程序 - 点亮第一盏灯】

目录 一、创建项目 1. 创建一个新的项目 ​编辑 2. 配置开发板信息 ​编辑 3. 取消汇编配置 4. 项目结构 二、编码实现 1. 项目准备 2. 代码实现 点灯&#xff1a; 熄灯&#xff1a; 3. 编译烧录运行 配置编译输出 保存和编译代码 ​编辑 烧录 一、创建项目 1. …