深度优先搜索详解 C++实现

2023-11-14

DFS

全文大概四千字左右,如果您初学DFS相信会对您会有很大的帮助,能力有限,很多术语不够专业,理解万岁

二叉树的深度优先搜索

二叉树的概念这里就不细谈了

使用数组来存储二叉树,根结点从1开始(方便计算),设父节点的下标为n,那么左儿子的下标为2n,右儿子的下标为2n+1

       2
     /   \
    3     5
   / \    / \
  7   8  6    1
 /     \
4       9

上面这棵树用数组就可以表示成

数组下标:0 1 2 3 4 5 6 7 8 9 10 11 
     值:0 2 3 5 7 8 6 1 4 0  0  9

因为深度优先遍历是深度优先,形象一点就是不撞南墙不回头。

深度优先遍历是基于栈完成的,因为我们是记录路线所以下面的步骤应该是这样(您不妨拿上纸和笔来模拟一下这个过程):

  1. 首先准备一个栈;
  2. 然后把根结点入栈,这个时候程序应该把入栈结点的值输出(每次入栈都输出入栈节点的值),然后根节点左边有儿子,那么我们就向左出发,直到4为止,因为4左右都没有节点了,这个时候栈里面的元素从栈底到栈顶为2 3 7 4;
  3. 这时候的栈顶为4,因为4左右都没有节点了,所以我们把4出栈;这时候的栈顶为7,同理7左右都没有节点了,所以7出栈;这时候的栈顶为3,然后3的右边有节点,所以把3右边的8入栈,8右边有节点所以把9入栈,这个时候栈里面的元素从栈底到栈顶为2 3 8 9;
  4. 因为 3 8 9我们都走过了,并且已经没有新的节点让我们走了,所以3 8 9出栈,这时候栈顶为2,然后可以看到根节点的右边还有没访问的节点,所以我们就把5入栈,相信到这里您应该明白了深度优先遍历在二叉树的路线,后面您可以尝试自己把剩下的走完;

所以上面这棵树的深度优先遍历的结果应为 2 3 7 4 8 9 5 6 1

一句话概括就是一直往左边走,左边走完了或者已经走过了就走右边

但是我们在编写代码的时候我们不需要自己手动开一个栈,计算机在内部使用被称为调用栈的栈,也就是说我们可以通过递归来实现上面这个过程,具体的过程和上面一样不用过分纠结,下面是使用递归实现的代码

注意点:

  1. 判断是否为空节点是通过0来判断(这样做是不严谨的,因为有时我们需要存0,我们可以把0替换成一个使用率很低的数字,比如0x3f3f3f3f);
  2. 为了避免越界,tree数组稍微开大一点点;
  3. vis数组判断当前点是否被访问过。访问过为true,没访问过为false;
int tree[50];
bool vis[50];

void dfs(int u) {	// u代表当前在哪个点
  
	vis[u] = true;  // 将当前节点标记为访问过的节点
	printf("%d ", tree[u]); // 输出

	if(tree[2 * u] != 0 && !vis[2 * u]) {
		dfs(2 * u);
	} 

	if(tree[2 * u + 1] != 0 && !vis[2 * u + 1]) {
		dfs(2 * u + 1);
	} 
}

int main() {

	memset(vis, false, sizeof vis);

	tree[1] = 2;
	tree[2] = 3;
	tree[3] = 5;
	tree[4] = 7; 
	tree[5] = 8;
	tree[6] = 6;
	tree[7] = 1;
	tree[8] = 4;
	tree[11] = 9;

	dfs(1);
 	
	return 0;
}

搜索

搜索算法是利用计算机的高性能来有目的的穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。

搜索算法实际上是根据初始条件和扩展规则构造一棵“解答树”并寻找符合目标状态的节点的过程。所有的搜索算法从最终的算法实现上来看,都可以划分成两个部分——控制结构(扩展节点的方式)和产生系统(扩展节点),而所有的算法优化和改进主要都是通过修改其控制结构来完成的。其实,在这样的思考过程中,我们已经不知不觉地将一个具体的问题抽象成了一个图论的模型——树,即搜索算法的使用第一步在于搜索树的建立。

全排列问题https://www.luogu.com.cn/problem/P1706

我们首先思考一下for循环的做法,假设我们只求三位

for(int i = 1; i <= 3; ++i) {
		for(int j = 1; j <= 3; ++j) {
			for(int k = 1; k <= 3; ++k) {
				if(i != j && i != k && j != k) {
					printf("%d %d %d\n", i, j, k);
				}
			}
		}
	}

问题引刃而解,但是很明显数据范围0~9,我们不可能这么写。

这个时候我们仔细思考一下,我们求全排列的位数与有几层for是相同的,我们都知道遇到这种代码相似点比较多的时候(不知道也没关系,起码现在知道了),我们可以考虑使用递归去解决。

不过在我们开始写递归之前,我们先看看这句话搜索算法的使用第一步在于搜索树的建立

搜索树是什么?怎么建立?让我们看看上面三层for循环的代码

i = 1, j = 1, k = 1,不符合题意所以我们不输出
i = 1, j = 1, k = 2,不符合题意所以我们不输出
i = 1, j = 1, k = 3,不符合题意所以我们不输出
i = 1, j = 2, k = 1,不符合题意所以我们不输出
i = 1, j = 2, k = 2,不符合题意所以我们不输出
i = 1, j = 2, k = 3,符合题意所以我们输出答案 1 2 3
i = 1, j = 3, k = 1,不符合题意所以我们不输出
i = 1, j = 3, k = 2,符合题意所以我们输出答案 1 3 2
i = 1, j = 3, k = 3,不符合题意所以我们不输出

篇幅原因我们只写i = 1的情况,我们尝试把上面的过程画成树,大概长下面这样

i:            1
       /      |     \
j:    1       2       3
    / | \   / | \   / | \
k: 1  2  3 1  2  3 1  2  3

那么这个连接的路径就是我们的答案

我们可以发现这个答案产生的过程和上面树的深度优先搜索是不是非常非常的神似,所以说当前的问题就是把这棵树建立出来,我们肯定不会像上面那样手动一个一个去填对吧,这个时候就需要引入另外一个概念,叫作回溯。

回溯

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

       2
     /   \
    3     5
   / \    / \
  7   8  6    1
 /     \
4       9
还是这棵树,我们可以看到当我们走到4的时候发现没有节点让我们走了,所以我们选择回到7,从4回到7的这一步叫作“回溯”
这里的回溯我们是通过递归调用时的调用栈出栈来完成的
思考一下,我们又应该如何去处理全排列问题的回溯

我们可以这样去思考,不要把这棵树想的太过具体,我们首先需要一个数组来存储我们最后的答案

vector<int> num;

我们还是使用递归来解决,有了上面那棵树我们可以知道这个数组的变化应该是这样的

1
1 1 
1 1 1
1 1		回溯
1 1 2
1 1		回溯
1 1 3
1 1		回溯
1 		回溯
1 2	  
1 2 1
1 2		回溯
1 2 2
1 2		回溯
1 2 3
1 2   回溯
1     回溯
1 3 
1 3 1
1 3		回溯
1 3 2
1 3		回溯
1 3 3
1 3   回溯
1     回溯

众所周知,递归需要一个递归出口,那么这里很明显我们就知道了,递归的出口就是当数组的大小为3的时候,同时我们还需要把数组里的元素输出出来,所以出口的代码应该长这样

void dfs() {
  if(num.size() == len) {
		for (int i : num) {
			cout << i << " ";
		}
		cout << endl;
		return;
	}
}

接下来的步骤就是往里面加元素,我们需要把从1到3所有的数字都放进去,这一步除了循环或许没有更好的办法吧

void dfs() {
  if(num.size() == len) {
		for (int i : num) {
			cout << i << " ";
		}
		cout << endl;
		return;
	}
  
	for(int i = 1; i <= 3; ++i) {
		num.push_back(i);
		
	}
}

忘了一点我们函数还没写参数呢!仔细想想我们需要什么参数

我们需要一个长度吧,这样我们才知道什么时候退出递归,那么我们再调整一下我们的代码

void dfs(int len) {
  if(num.size() == len) {
		for (int i : num) {
			cout << i << " ";
		}
		cout << endl;
		return;
	}
  
	for(int i = 1; i <= len; ++i) {
		num.push_back(i);
		
	}
}


写到这一步,就来到最关键的步骤了,既然我们要让下一步出现1 1还有1 1 1的排列,很明显这个时候是不是应该递归了?

for(int i = 1; i <= len; ++i) {
		num.push_back(i);
		dfs(len);	// len代表程序递归结束时的长度 直接跟着传就行了	
	}

不出意外的话,这个时候点运行程序输出了1 1 1,这是我们期望的结果之一,但是没有列举出所有情况,原因就是我们没有进行回溯,回到上面的列出的数组变化情况,我们可以发现回溯的过程就是数组删除最后一位的过程,那么很明显我们需要加上num.pop_back(),所以我们代码应该长这样

void dfs(int len) {

	if(num.size() == len) {
		for (int i : num) {
			cout << i << " ";
		}
		cout << endl;
		return;
	}


	for(int i = 1; i <= len; ++i) {
		num.push_back(i);
		dfs(len);
		num.pop_back();
	}
}

这段代码会把所有结果都输出出来,但是我们只需要符合题意的,所以我们需要检查一下,再根据题意修改一下代码,完整代码如下

#include "iostream"
#include "vector"
using namespace std;

vector<int> num;
bool vis[10]; // 这个数组用于检查数组中是否含有重复的数字

void dfs(int len) {

	if(num.size() == len) {

		for(int i = 1; i <= len; ++i) vis[i] = false; // 每次检查时记得把数组初始化

		for(int i = 0; i < num.size(); ++i) {
			if(vis[num[i]] == true) // 如果重复出现了就不符合题意 跳出 反之将这个点标记
				return ;
			else
				vis[num[i]] = true; 
		}
		// 好了接下来的就是正确答案
		for (int i : num) {
			cout << "    " << i;
		}
		cout << endl;
		return;
	}

	for(int i = 1; i <= len; ++i) {
		num.push_back(i);
		dfs(len);
		num.pop_back();
	}
}

int main() {

	int n;
	cin >> n;
	dfs(n);

	return 0;
}

结果有一个点超时了,这个时候我们来想一想该如何去优化这个算法

先看看我们之前的算法有什么问题

其实一眼就可以看出来就是每次我们都需要检查是否有重复的,我们可不可以简化成只要出现重复的就不走这个分支

那么就可以将这个递归树简化成这样

     什么都不选
   /    |    \
  1     2     3
 / \   / \   / \
2   3 1   3 1   2
|   | |   | |   |
3   2 3   1 2   1

同样的搜索的路径就是答案,我们可以发现相比于上面的算法光是1这一个分支就有13个节点,我们优化后的算法总共才15个节点,时间复杂度大大降低,这一步也被称为剪枝,再根据题意修改一下代码细节,具体的实现代码如下

#include "iostream"
#include "vector"
using namespace std;

vector<int> num;
bool vis[10];

void dfs(int len) {

	if(num.size() == len) {

		for (int i : num) {
			cout << "    " << i;
		}
		cout << endl;
		return;
	}

	for(int i = 1; i <= len; ++i) {
		if(!vis[i]) { // 判断这个节点有没有被访问过
			vis[i] = true; // 没有访问过那就标记 然后加入num数组
			num.push_back(i);
			dfs(len);
			vis[i] = false; // 回溯 所以将这个点重新标记成未访问过的状态
			num.pop_back();
		}
	}
}

int main() {

	int n;
	cin >> n;
	dfs(n);

	return 0;
}

AC了(鼓掌)

总结一下我们刚刚的步骤:

  1. 画出搜索树(递归树)
  2. 寻找递归出口
  3. 寻找递归条件
  4. 剪枝优化

代码模板

void dfs(int u) {
	if(……) { // 递归出口
		……;
		return ;
	}
	
	for(int i = 1; i <= u; ++i) {
		if(check()) { // 剪枝
			……; // 选择分枝
			dfs(u);
			……; //回溯
		}
	}
}

接下来挑战一下难题

八皇后https://leetcode-cn.com/problems/eight-queens-lcci/submissions/

根据我们上面的步骤来解决这个问题

这里我们来画棋盘为2*2的情况

 什么都不选
 /       \
 Q.      .Q
/ \      / \
Q. Q.   .Q .Q
Q. .Q   Q. .Q

很明显上面的情况都不符合情况,直到4才会出现解,但是到4的时候有256种情况,所以我们选择符合题意的两个情况

.Q..
...Q
Q...
..Q.

..Q.  
Q...
...Q
.Q..

很明显递归的出口就是列数达到需要求的棋盘大小的时候

但是这个时候我们就需要来思考剪枝的情况了

因为当棋盘大小来到9的时候,粗略估计我们需要435848049次匹配才能得出所有的答案,在加上检查的时间,很明显1秒无法完成这个算法,而且内存也会炸

仔细思考也不难发现,剪枝的情况就是皇后之间相互冲突的时候,也就是我们每次摆放下一个皇后的时候,判断一下当前的摆放会不会和前面已经摆放的皇后产生冲突,如果产生冲突,那这后面的情况我们都可以选择不要了,当然我们这里只需要检查左上、上和右上的情况,因为后面的皇后还没有放

我们使用res二维数组来表示最后答案,使用board来表示棋盘

class Solution {
public:
    vector<vector<string>> res;

    vector<vector<string>> solveNQueens(int n)
    {
        //初始化
        vector<string> board(n, string(n, '.'));
        //开始选择
        dfs(board, 0);
        return res;
    }

    void dfs(vector<string>& board, int row)
    {
        //递归出口
        if (row == board.size())
        {
            res.push_back(board);
            return;
        }

        int n = board[row].size();

        for (int col = 0; col < n; col++)
        {
            //判断当前位置能否被其他皇后攻击
            if (!isVal(board, col, row)) continue;

            //选择
            board[row][col] = 'Q';
            dfs(board, row + 1);
            //撤销选择
            board[row][col] = '.';
        }
    }

    //因为新的一行皇后下面没有皇后 所以不用检查下面 同理 左右也不用检查 只用检查左上 上 右上
    bool isVal(vector<string>& board, int col, int row)
    {
        //上
        for (int i = 0; i < row; i++)
        {
            if (board[i][col] == 'Q')
                return false;
        }
        //左上
        for (int i = row, j = col; i >= 0 && j >= 0; --i, --j)
        {
            if (board[i][j] == 'Q')
                return false;
        }
        //右上
        for (int i = row, j = col; i >= 0 && j <= board[row].size(); --i, ++j)
        {
            if (board[i][j] == 'Q')
                return false;
        }
        //没有代表可以放置
        return true;
    }
};

成功A掉(鼓掌)

最后希望您能自己独立把上面的代码都实现一遍,只有真正动手了才能跟家深入的了解这个算法的奇妙

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度优先搜索详解 C++实现 的相关文章

  • 适合初学者的良好调试器教程[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有谁知道一个好的初学者教程 在 C 中使用调试器 我感觉自己好像错过了很多 我知道怎么做 单步执行代码并查看局部变量 虽然这常常给我带来问
  • 使用 C# 登录《我的世界》

    我正在尝试为自己和一些朋友创建一个简单的自定义 Minecraft 启动器 我不需要启动 Minecraft 的代码 只需要登录的实际代码行 例如 据我所知 您过去可以使用 string netResponse httpGET https
  • IdentityServer 4 对它的工作原理感到困惑

    我阅读和观看了很多有关 Identity Server 4 的内容 但我仍然对它有点困惑 因为似乎有很多移动部件 我现在明白这是一个单独的项目 它处理用户身份验证 我仍然不明白的是用户如何注册它 谁存储用户名 密码 我打算进行此设置 Rea
  • 函数参数的默认参数是否被视为该参数的初始值设定项?

    假设我有这样的函数声明 static const int R 0 static const int I 0 void f const int r R void g int i I 根据 dcl fct default 1 如果在参数声明中指
  • 从客户端访问 DomainService 中的自定义对象

    我正在使用域服务从 Silverlight 客户端的数据库中获取数据 在DomainService1 cs中 我添加了以下内容 EnableClientAccess public class Product public int produ
  • File.AppendText 尝试写入错误的位置

    我有一个 C 控制台应用程序 它作为 Windows 任务计划程序中的计划任务运行 此控制台应用程序写入日志文件 该日志文件在调试模式下运行时会创建并写入应用程序文件夹本身内的文件 但是 当它在任务计划程序中运行时 它会抛出一个错误 指出访
  • 识别 Visual Studio 中的重载运算符 (c++)

    有没有办法使用 Visual Studio 快速直观地识别 C 中的重载运算符 在我看来 C 中的一大问题是不知道您正在使用的运算符是否已重载 Visual Studio 或某些第三方工具中是否有某些功能可以自动突出显示重载运算符或对重载运
  • 如何在 Qt 应用程序中通过终端命令运行分离的应用程序?

    我想使用命令 cd opencv opencv 3 0 0 alpha samples cpp cpp example facedetect lena jpg 在 Qt 应用程序中按钮的 clicked 方法上运行 OpenCV 示例代码
  • 为什么这个二维指针表示法有效,而另一个则无效[重复]

    这个问题在这里已经有答案了 这里我编写了一段代码来打印 3x3 矩阵的对角线值之和 这里我必须将矩阵传递给函数 矩阵被传递给指针数组 代码可以工作 但问题是我必须编写参数的方式如下 int mat 3 以下导致程序崩溃 int mat 3
  • C++ int 前面加 0 会改变整个值

    我有一个非常奇怪的问题 如果我像这样声明一个 int int time 0110 然后将其显示到控制台返回的值为72 但是当我删除前面的 0 时int time 110 然后控制台显示110正如预期的那样 我想知道两件事 首先 为什么它在
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • 在屏幕上获取字符

    我浏览了 NCurses 函数列表 似乎找不到返回已打印在屏幕上的字符的函数 每个字符单元格中存储的字符是否有可访问的值 如果没有的话Windows终端有类似的功能吗 我想用它来替换屏幕上某个值的所有字符 例如 所有a s 具有不同的特征
  • WebBrowser.Print() 等待完成。 。网

    我在 VB NET 中使用 WebBrowser 控件并调用 Print 方法 我正在使用 PDF 打印机进行打印 当调用 Print 时 它不会立即启动 它会等到完成整个子或块的运行代码 我需要确保我正在打印的文件也完整并继续处理该文件
  • C++ new * char 不为空

    我有一个问题 我在 ASIO 中开发服务器 数据包采用尖头字符 当我创建新字符时 例如char buffer new char 128 我必须手动将其清理为空 By for int i 0 i lt 128 i buffer i 0x00
  • 将数组作为参数传递

    如果我们修改作为方法内参数传递的数组的内容 则修改是在参数的副本而不是原始参数上完成的 因此结果不可见 当我们调用具有引用类型参数的方法时 会发生什么过程 这是我想问的代码示例 using System namespace Value Re
  • 在 Windows Phone silverlight 8.1 上接收 WNS 推送通知

    我有 Windows Phone 8 1 silverlight 应用程序 我想使用新框架 WNS 接收通知 我在 package appxmanifest 中有
  • 使用 C 在 OS X 中获取其他进程的 argv

    我想获得其他进程的argv 例如ps 我使用的是在 Intel 或 PowerPC 上运行的 Mac OS X 10 4 11 首先 我阅读了 ps 和 man kvm 的代码 然后编写了一些 C 代码 include
  • GCC 的“-Wl,option”和“-Xlinker option”语法之间有区别吗?

    我一直在查看一些配置文件 并且看到它们都被使用 尽管在不同的体系结构上 如果您在 Linux 机器上使用 GCC 将选项传递给链接器的两种语法之间有区别吗 据我所知 阅读 GCC 手册时 他们的解释几乎相同 From man gcc Xli
  • 我可以在“字节数”设置为零的情况下调用 memcpy() 和 memmove() 吗?

    当我实际上没有什么可以移动 复制的时候 我是否需要处理这些情况memmove memcpy 作为边缘情况 int numberOfBytes if numberOfBytes 0 memmove dest source numberOfBy
  • Objective-C / C 给出枚举默认值

    我在某处读到过关于给枚举默认值的内容 如下所示 typedef enum MarketNavigationTypeNone 0 MarketNavigationTypeHeirachy 1 MarketNavigationTypeMarke

随机推荐

  • [ Android 编译 ] 如果不指定LOCAL_CERTIFICATE,默认使用哪个key进行签名

    Android 编译 如果不指定LOCAL CERTIFICATE 默认使用哪个key进行签名 尊重原创 转载请注明出处 创作不易 如有帮助请点赞支持 参考 Android系统签名简介 开发系统应用时 通常情况下我们都会在 Android
  • C++详细学习笔记(一)

    首先 本次学习主要参考的书籍是C Primer Plus第六版 为了方便今后参阅 故写下该学习笔记 C 是在C语言基础上开发的一种集面向对象编程 泛型编程和过程化编程于一体的编程语言 是C语言的超集 一 预备知识 C 有三件法宝 1 继承了
  • 计算机系统(四)处理器体系结构

    现代微处理器可以称得上是人类创造的最复杂的系统之一 一块手指甲大小的硅片上 可以容纳一个完整的高性能处理器 大的高速缓存 以及用来连接到外部设备的逻辑电路 到目前为止 我们看到的计算机系统只限于机器语言程序级 我们知道处理器必须执行一系列指
  • 2021-03-07

    昨天 拳王分享了一篇 1个被严重低估的暴利项目 竟多达7种变现方式 玩转1个轻松月入过万 揭秘7种可变现方式的被严重低估的暴利项目 今天 我们分享的是 1个简单至极又赚钱到发指的项目 有人靠它年入7位数 容易可复制 今天分享的是一个极为简单
  • mysql case when多个参数结果

    查看所有学生的姓名班级 如果班级为1表示一年级 2表示二年级 3表示三年级 select s StudentName CASE WHEN s GradeId 1 THEN 一年级 WHEN s GradeId 2 THEN 二年级 WHEN
  • 【NLP】每个NLP工程师都应该知道的10 种不同的 NLP 技术

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • CCNP-冗余链路中的广播风暴、多帧复制、地址表的不稳定

    STP协议 当网络中存在备份链路时 只允许主链路激活 如果主链路因故障而断开后 备用链路才会被打开 广播风暴 在没有避免交换环路措施的情况下 每个交换机都无穷无尽的转发广播帧 广播流量破坏了正常的通信流 消耗了带宽和交换机的CPU资源 直至
  • python一行输出多个空格_python 在同一行依次输入三个值a,b,c,用空格分开,输出 b*b-4*a*c的值...

    拒绝回答一系列代码细节 自行百度学习对应函数 import re import math print Input four numbers seperated by scape enter for ending input myinputs
  • 如何优化算法提高卷积神经网络的泛化能力

    卷积网络的优化方式 方法 说明 使用更多数据 在有条件的前提下 尽可能多地获取训练数据是最理想的方法 更多的数据可以让模型得到充分的学习 也更容易提高泛化能力 使用更大批次 在相同迭代次数和学习率的条件下 每批次采用更多的数据将有助于模型更
  • ZYNQ 7000的硬件SPI控制器配置为三线制SPI

    使用zynq去做一些AD DA的操作时候有些只支持三线制的SPI那么我们如何使用PS端的控制器EMIO到PL端实现3线制呢 以上是SPI0的接口信号 我们可以把PL侧的三线制的SDIO信号 inout 通过一个IOBUF原语进行转换出in
  • 数字电路的逻辑符号

  • Mybatis框架之解决列名(表中的字段名称)和实体类中的属性名不一致

    查询数据的时候 查不到userName的信息 原因 数据库的字段名是user name 而POJO中的属性名字是userName 两端不一致 造成mybatis无法填充对应的字段信息 修改方法 在sql语句中使用别名 解决方案1 在sql语
  • 深入理解CRITICAL_SECTION

    http www cnblogs com dirichlet archive 2011 03 16 1986251 html 摘要 临界区是一种防止多个线程同时执行一个特定代码节的机制 这一主题并没有引起太多关注 因而人们未能对其深刻理解
  • Hive其他优化

    一 关联优化器 在Hive的一些复杂关联查询中 可能同时还包含有group by等能够触发shuffle的操作 有些时候shuffle操作是可以共享的 通过关联优化器选项 可以尽量减少复杂查询中的shuffle 从而提升性能 set hiv
  • GPU显存问题-解决Failed to get convolution algorithm. This is probably because cuDNN failed to initialize

    GPU显存问题 使用服务器跑深度学习代码时出现了这样的问题 Failed to get convolution algorithm This is probably because cuDNN failed to initialize so
  • Docker私有仓库搭建与界面化管理

    一 关于Registry 官方的Docker hub是一个用于管理公共镜像的好地方 我们可以在上面找到我们想要的镜像 也可以把我们自己的镜像推送上去 但是有时候我们的使用场景需要我们拥有一个私有的镜像仓库用于管理我们自己的镜像 这个可以通过
  • 动态定义数组

    首先 为什么需要动态定义数组呢 这是因为 很多情况下 在预编译过程阶段 数组的长度是不能预先知道的 必须在程序运行时动态的给出 但是问题是 c 要求定义数组时 必须明确给定数组的大小 要不然编译通不过 如 int Array 5 正确 in
  • android频响曲线图,EQ 频响曲线

    import android annotation SuppressLint import android content Context import android graphics Canvas import android grap
  • 利用matplotlib绘制折线图:CSV格式

    熬过初学阶段的不适和挫败感 我们其实完全可以掌握一个全新的领域 本文为 python编程 从入门到实践 以下简称参考书 中第16章 下载数据中的内容 利用matplotlib绘制最高及最低气温折线图 在此整理为笔记 一为温习 二为备份 以便
  • 深度优先搜索详解 C++实现

    DFS 全文大概四千字左右 如果您初学DFS相信会对您会有很大的帮助 能力有限 很多术语不够专业 理解万岁 二叉树的深度优先搜索 二叉树的概念这里就不细谈了 使用数组来存储二叉树 根结点从1开始 方便计算 设父节点的下标为n 那么左儿子的下