【数据结构】第八节:链式二叉树

news2024/12/30 2:19:12

个人主页: NiKo

数据结构专栏: 数据结构与算法

 源码获取:Gitee——数据结构

一、二叉树的链式结构

typedef int BTDataType;
typedef struct BinaryTreeNode {
	BTDataType data;
	struct BinaryTreeNode* left;  // 左子树根节点
	struct BinaryTreeNode* right; // 右子树根节点
}BTNode;

        每一颗二叉树都是由左子树、根、右子树构成的,在实现二叉树的链式结构时我们也要将二叉树看作这三部分。

二、二叉树的遍历

        学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。 遍历是通过递归实现的。
        按照规则,二叉树的遍历有: 前序/中序/后序的递归结构遍历
  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

图1-1

已经创建的二叉树如图1-1。

1.前序遍历 

        前序遍历访问根结点的操作发生在遍历其左右子树之前,访问顺序为根、左子树、右子树。根据概念,在访问二叉树时,需要将二叉树看作根、左子树、右子树,左右子树又可以分为根、左子树、右子树,直到访问的节点为空(NULL)即证明访问到了二叉树的最底层。

        如图1-1所示,首先访问的根是1,这是第一层递归,然后递归访问此根的左子树,根节点为2,这是第二层递归;而以2为根的子树又可以分为以3为根的左子树、NULL,访问完根节点2后应继续递归访问左子树根节点3,这是第三层递归;此时根节点3的左子树为空,右子树为空无法再递归下去,以3为根节点的子树访问完毕,结束第三层递归返回到第二层递归并访问根节点2的右子树,右子树为空无法递归,以2为根节点的子树访问完毕结束第二层递归返回到第一层递归并访问根节点1的右子树,同理根据遍历左子树的方式遍历右子树。

void PrevOrder(BTNode* root) {
    // 不符合递归条件,结束此层递归
	if (root == NULL) {
		printf("N ");
		return;
	}
	printf("%d ", root->data); // 访问根节点
	PrevOrder(root->left);     // 访问根节点的左子树
	PrevOrder(root->right);    // 访问根节点的右子树
}

        根据代码前序遍历的结果示意图:

 

2.中序遍历

        中序遍历访问根结点的操作发生在遍历其左右子树之中(间)。访问顺序为左子树、根、右子树。同前序遍历的分析方式一样,访问一棵树应从他的左子树开始访问,然后再访问根和右子树。

        如图1-1,首先访问以1为根的左子树(根节点2),进入第一层递归;以根节点为2的左子树又可以分为左子树(根节点3)、右子树(NULL),进入第二层递归;此时正在访问的是根节点为3的子树,根据中序遍历的规则应按照左子树、根、右子树的顺序访问,进入第三层递归。3的左右子树都为空不符合递归条件结束第三层递归,返回到第二层递归并访问根节点2,然后访问根节点2的右子树,右子树为空结束第二层递归,返回到第一层递归并访问根节点1,随后访问根节点1的右子树,同理根据遍历左子树的方式遍历右子树。

void InOrder(BTNode* root) {
	if (root == NULL) {
		printf("N ");
		return;
	}
	InOrder(root->left);        // 访问根的左子树
	printf("%d ", root->data);  // 访问根
	InOrder(root->right);       // 访问根的右子树
}

      根据代码中序遍历的结果示意图:


3.后序遍历

        后序遍历访问根结点的操作发生在遍历其左右子树之后。访问顺序为左子树、右子树、根,后序遍历访问一棵树时,根节点是最后访问的。

        如图1-1,首先访问根节点1的左子树(根节点2)进入第一层递归,这颗子树还可以分为左右子树,则进入第二层递归访问根节点2的左右子树。随后进入第三层递归先访问根节点3的左子树,然后是右子树,最后是根节点3,结束第三层递归返回第二层递归并访问根节点2的右子树之后在访问根节点2,结束第二层递归返回第一层递归,访问根节点1的右子树,最后访问根节点1。

void PostOrder(BTNode* root) {
	if (root == NULL) {
		printf("N ");
		return;
	}
	PostOrder(root->left);      // 访问左子树
	PostOrder(root->right);     // 访问右子树
	printf("%d ", root->data);  // 访问根
}

        根据代码后序遍历的结果示意图:

 三、节点个数


  • 对于一棵树而言,他的节点分为两种情况:
  1. 根节点为空,返回0。
  2. 根节点不为空,这颗树的节点数=左子树节点个数+右子树节点个数+根节点(1)。
  • 根据这两种情况写出的代码如下:
int TreeSize(BTNode* root) {
	return root == NULL ? 0 :
		TreeSize(root->left) + TreeSize(root->right) + 1;
}

  • 分析:第一个访问的根节点1不为空根据表达式先计算左子树的节点个数,通过TreeSize(root->left)进入第一层递归访问到根节点2,在第二层递归中通过TreeSize(root->left)进入第二层递归访问根节点3,根节点3不为空通过TreeSize(root->left)访问左子树进入第三层递归,左子树为空返回0,右子树为空返回0,第三层递归的返回值为0+0+1=1结束第三层递归,返回到第二层递归的TreeSize(root->left)的值就是1,然后在第二层递归中通过TreeSize(root->right)访问根节点2的右子树,右子树为空返回0,第二层递归的返回值1+0+1=2,结束第二层递归并将值返回到第一层递归的TreeSize(root->left),这样左子树的节点个数计算完毕为2,紧接着再计算第一层递归的TreeSize(root->right),同理可得TreeSize(root->right)的值为3,最后得出这棵树的节点个数是6。

四、叶子节点个数

        叶子节点是指不含有任何子树的节点(度为0),即root->left和root->right均为NULL。判断叶子节点的个数需要我们找到度为0的节点。


  • 对于一颗子树,它的叶子节点有三种情况:
  1. 根节点为空,返回0
  2. 根节点不为空,左右子树均为空,它本身就是叶子节点,返回1
  3. 根节点不为空,左右子树至少有一个不为空,这棵树的叶子节点树=左子树叶子节点数+右子树叶子节点数
  • 根据这三种情况写出的代码如下:
int TreeLeafSize(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	else if (root->left == NULL && root->right == NULL) {
		return 1;
	}
	else {
		return TreeLeafSize(root->left) + TreeLeafSize(root->right);
	}
}

  • 分析:进入第一层递归访问根节点1,根节点1不为空且左右子树不为空,通过TreeLeafSize(root->left)进入第二层递归访问根节点2,根节点2不为空且左子树不为空,通过TreeLeafSize(root->left)进入第三层递归访问根节点3,根节点3不为空但是此时左右子树为空,说明节点3是叶子节点,结束第三层递归返回1到第二层递归,在第二层递归中通过TreeLeafSize(root->right)访问根节点2的右子树,为空返回0,则以2为根节点的子树叶子节点个数0+1=1,结束第二层递归返回1到第一层递归,同理可得右子树的叶子节点为1+1=2,则这棵树的叶子节点数1+2=3。

五、高度

        树的高度是指树中节点的最大层次,在二叉树中,左右两棵子树的高度较大者作为这棵树的高度。


  • 求树的高度有两种情况:
  1. 根节点为空,返回0
  2. 根节点不为空,这棵树的高度=max(左子树高度,右子树高度)+1
  • 根据这两种情况写出的代码如下:
int TreeHeight(BTNode* root) {
	if (root == NULL) {
		return 0;
	}
	else {
		return max(TreeHeight(root->left), TreeHeight(root->right)) + 1;
	}
}

  • 分析:进入第一层递归访问根节点1,不为空通过TreeHeight(root->left)进入第二层递归访问根节点2,不为空通过TreeHeight(root->left)访问根节点3进入第三层递归,此时TreeHeight(root->left)和TreeHeight(root->right)的返回值都为0,第三层递归返回0+0+1=1到第二层递归并结束第三层递归。第二层递归返回1+0+1=2到第一层递归,同理再访问右子树得TreeHeight(root->right)的返回值为2,max(TreeHeight(root->left),TreeHeight(root->right))+1的结果为3,故此树的高度就是3。

六、第k层的节点个数

        求二叉树的第k层节点可以向下转化为求这棵树的左右子树的第k-1层节点,当k=1时就到达了这棵树的第k层。


  • 在二叉树中每次向下递归一层的情况有三种:
  1. 根节点为空,返回0
  2. 根节点不为空且k不等于1,继续向下递归直到k等于1
  3. 根节点不为空且k等于1,说明到达目标层,返回1
  • 根据这三种情况写出的代码如下:
int TreeLevelKSize(BTNode* root, int k) {
	if (root == NULL) {
		return 0;
	}

	if (k == 1) {
		return 1;
	}
	else {
		return TreeLevelKSize(root->left, k - 1) + 
            TreeLevelKSize(root->right, k - 1);
	}
}

  • 分析:假设k=3,进入第一层递归访问根节点1,不为空且k不等于1(k=3);通过TreeLevelKSize(root->left, k - 1)进入第二层递归访问根节点2,不为空且k不等于1(k=2);通过TreeLevelKSize(root->left, k - 1)进入第三层递归访问根节点3,不为空,此时k=1说明到达目标层,返回1结束第三层递归。在第二层递归中通过TreeLevelKSize(root->right, k - 1)访问右子树,为空返回0,结束第二层递归返回1到第一层递归,同理通过TreeLevelKSize(root->right, k - 1)可到达根节点1的右子树且第k层节点数是2,所以这棵树的第三层有1+2=3个节点。

七、查找值为x的节点

        遍历二叉树找到值为x的节点返回即可。


  • 遍历的结果有四种情况:
  1. 根节点为空,返回NULL
  2. 根节点不为空,节点的值等于x,返回这个节点(地址)
  3. 如果在左子树中找到了目标节点,不用再遍历右子树
  4. 左右子树都没有找到这个节点,返回NULL
  • 根据这四种情况写出的代码如下:
BTNode* TreeFind(BTNode* root, BTDataType x) {
	if (root == NULL) {
		return NULL;
	}

	if (root->data == x) {
		return root;
	}
	BTNode* ptr = TreeFind(root->left, x);
	if (ptr != NULL) {
		return ptr;
	}
	else {
		BTNode* ptr = TreeFind(root->right, x);
		if (ptr == NULL) {
			return NULL;
		}
		else {
			return ptr;
		}
	}
}

  • 分析:假设x=6,进入第一层递归访问根节点1,1不等于6且不为空通过TreeFind(root->left, x)进入第二层递归访问根节点2,2不等于6且不为空进入第三层递归访问根节点3,3不等于6,此时结束第三层递归返回值NULL,根节点2的左子树没有找到,向右子树遍历,右子树也没有找到结束第二层递归返回NULL,说明在左子树中没有找到目标节点,这时候查找右子树,在右子树中找到了就返回这个节点。

八、创建二叉树

        已知一个字符数组,根据这个字符数组创建二叉树;创建二叉树的顺序应该是根、左子树、右子树。


  • 假设给出这样一个数组:"abc##de#g##f###",其中"#"表示NULL,字母代表树中节点存储的值,还是用递归的思想,根据这个数组建立一个二叉树。
  • 首先需要遍历这个数组,遍历的结果有两种:
  1. 指针指向的元素为"#",代表这个节点为NULL(左子树或右子树建立完成)
  2. 指针指向非"#"元素,将值赋给这个节点后继续建立这个节点的左子树和右子树
  • 根据这两种情况写出的代码如下:
// abc##de#g##f###
BTNode* CreateTree(BTDataType* arr, int* pi) {
	if (arr[*pi] == '#') {
		(*pi)++;
		return NULL;
	}
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL) {
		perror("malloc fail!");
		exit(-1);
	}
	node->data = arr[(*pi)++];
	node->left = CreateTree(arr, pi);
	node->right = CreateTree(arr, pi);
	return node;
}

  • 分析:定义一个指针,代表数组的下标,每当创建一个节点或遇到NULL就后移。创建好一个节点后,给节点赋值,然后进入递归,当函数返回NULL时说明这颗子树创建完成,以此类推。

九、销毁二叉树

        销毁二叉树时,采用后序遍历销毁节点,原因是如果采用前序遍历或中序遍历会导致根节点销毁后无法销毁他的左子树或右子树。


  • 销毁二叉树步骤:
  1. 如果当前节点为NULL,直接结束
  2. 不为空,先销毁左子树,再销毁右子树,最后销毁根
  • 代码如下:
void TreeDestory(BTNode* root) {
	if (root == NULL) {
		return;
	}

	// 采用后序遍历销毁树
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}

  • 分析:进入函数后先判断这个节点是不是空,如果是代表已经销毁,不是通过递归依次销毁左子树和右子树,最后销毁根。

十、层序遍历 

        层序遍历(广度优先遍历)指的是在一棵二叉树中,依次遍历访问每一层的节点,直到最后一层。层序遍历需要使用到队列结构,相关代码在gitte中。


  • 层序遍历基本步骤:
  1. 如果节点为空,不进队列;节点不为空,进队列
  2. 进入循环,取队列的头节点,头节点的左右节点(非空)入队列,头节点出队列,这时访问了一层节点
  3. 当队列为空时,结束循环
  • 代码如下:
void TreeLevelOrder(BTNode* root) {
	Queue q;
	QueueInit(&q);

	// 不为空进队列
	if (root) {
		QueuePush(&q, root);
	}

	// 如果队列是空的,代表已经访问完所有元素
	while (!QueueEmpty(&q)) {
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);

		if (front->left) {
			QueuePush(&q, front->left);
		}

		if (front->right) {
			QueuePush(&q, front->right);
		}
	}
	QueueDestroy(&q);
}

  • 分析:初始化队列,如上图,节点1入队列,进入循环,取队头节点并打印在控制台,节点1的左右节点2,4入队列,节点1出队列(队列中剩2,4);开始第二次循环,取队头节点2,2的左节点3入队列,2出队列(队列中剩4,3),以此类推,开始第三次循环后节点4的左右节点进队列,完成层序遍历。

十一、判断完全二叉树

        完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。 完全二叉树就是这样一种特殊的非满二叉树:除了最后一层外其他每一层都是填满的,并且最后一层的节点都尽可能地靠左排列。如上图。


  • 具体实现步骤:判断完全二叉树不能利用递归实现,需要利用队列的相关知识(广度优先遍历)实现。
  1. 创建队列,无论树中的节点是否为空,都进入队列,根据层序遍历,将队头节点的左右子节点带入队列,在进入队列时如果遇到了第一个空节点就停止将节点带入队列,开始判断队列中的节点是否都为空。如果是,代表这棵树是完全二叉树;如果不是,代表这棵树是完全二叉树。
  • 代码:
bool TreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		// 遇到第一个空,就可以开始判断,如果队列中还有非空,就不是完全二叉树
		if (front == NULL)
		{
			break;
		}

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);

		// 如果有非空,就不是完全二叉树
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

  • 队列的源码可到博主的个人码云中获取(Project_Queue)

十二、补充二叉树的性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有2的i-1次方个结点.

  2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2的h次方减1.
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为n2 ,则有n0 =n2 +1​​​​​​​
  4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度h=log2(n+1). (ps:log2(n+1)是log以2为底,n+1为对数)
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:​​​​​​​
  • ​​​​​​​若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点  
  • 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子
  • 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

针对性质3:

/*
* 假设二叉树有N个结点
* 从总结点数角度考虑:N = n0 + n1 + n2 ①
* 
* 从边的角度考虑,N个结点的任意二叉树,总共有N-1条边
* 因为二叉树中每个结点都有双亲,根结点没有双亲,每个节点向上与其双亲之间存在一条边
* 因此N个结点的二叉树总共有N-1条边
* 
* 因为度为0的结点没有孩子,故度为0的结点不产生边; 度为1的结点只有一个孩子,故每个度为1的结
点* * 产生一条边; 度为2的结点有2个孩子,故每个度为2的结点产生两条边,所以总边数为:
n1+2*n2 
* 故从边的角度考虑:N-1 = n1 + 2*n2 ②
* 结合① 和 ②得:n0 + n1 + n2 = n1 + 2*n2 - 1
* 即:n0 = n2 + 1
*/

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

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

相关文章

2024年沈阳都市圈电竞大赛 暨TGA腾讯电竞运动会辽宁省选拔赛盛大开赛

去年&#xff0c;由沈阳市体育局主办的“2023年沈阳都市圈首届电竞大赛暨TGA浑南之夏辽宁省英雄联盟选拔赛”成功举办。通过搭建赛事平台&#xff0c;营造了沈阳都市圈电竞氛围&#xff0c;促进了电子竞技全业态发展。 今年&#xff0c;“2024年沈阳都市圈电竞大赛暨TGA腾讯电…

Day24_0.1基础学习MATLAB学习小技巧总结(24)——图形对象属性值的设置和查询

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a;《MATLAB基础教程 (第三版) (薛山)》 之前的章节都是…

vue3项目实现全局国际化

本文主要梳理vue3项目实现全项目格式化&#xff0c;例如在我前面文章使用若依创建vue3的项目中&#xff0c;地址&#xff1a;若依搭建vue3项目在导航栏中切换&#xff0c;页面中所有的组件的默认语言随之切换&#xff0c;使用的组件库依旧是element-plus&#xff0c;搭配vue-i1…

LeetCode --- 414周赛

题目列表 3280. 将日期转换为二进制表示 3281. 范围内整数的最大得分 3282. 到达数组末尾的最大得分 3283. 吃掉所有兵需要的最多移动次数 一、将日期转换成二进制表示 题目本质就是将数字转成二进制字符串&#xff0c;可以类比将十进制数字的每一位拆开拼成字符串&#x…

【Redis】redis5种数据类型(list)

目录 基本介绍 命令 LPUSH LPUSHX RPUSH RPUSHX LRANGE LPOP RPOP LINDEX LINSERT LLEN LREM LTRIM LSET 阻塞版本的命令 BLPOP 内部编码 基本介绍 list相当于c的双端队列deque 区分获取和删除的区别 lindex能获取到元素的值lrem也能返回被删除元素的值 命…

一条SQL实现GPT大模型【完全看不懂】

用一条SQL实现GPT大模型&#xff0c;简直让人不可思议&#xff0c;但是俄罗斯一位名叫Quassnoi的SQL牛人做到了&#xff0c;Quassnoi每年只写一条SQL&#xff0c;但是每条SQL都非常复杂&#xff1a; 2021年&#xff0c;用SQL绘制新冠病毒的3D图片 2022年&#xff0c;用SQL模拟…

为何初创数字影像企业纷纷选择入驻孵化基地?

在当今数字化时代&#xff0c;数字影像行业正蓬勃发展&#xff0c;越来越多的初创数字影像企业如雨后春笋般涌现。而这些充满活力与创新的初创企业&#xff0c;为何纷纷选择入驻数字影像企业孵化基地呢&#xff1f; 首先&#xff0c;数字影像企业孵化基地为初创数字影像企业提供…

再次进阶 舞台王者 第八季完美童模全球赛形象大使【邱玳莹】赛场秀场超燃合集!

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕。在盛大的颁奖典礼上&#xff0c;一位才能出众的少女——邱玳莹&#xff0c;迎来了她舞台生涯的璀璨时刻。 形象大使——邱玳莹&#xff0c;以璀璨童星之姿&#xff0c;优雅地踏上完美童模盛宴的绚丽舞台&am…

基于图像级监督和自训练的跨模态肿瘤分割转换器模型|文献速递--Transformer架构在医学影像分析中的应用

Title 题目 Image-level supervision and self-training for transformer-basedcross-modality tumor segmentation 基于图像级监督和自训练的跨模态肿瘤分割转换器模型。 01 文献速递介绍 深度学习在各种医学图像分析应用中展现了出色的性能和潜力&#xff08;Chen等&…

C语言 12 函数

其实函数在一开始就在使用了&#xff1a; // 这就是定义函数 int main() { ... }程序的入口点就是main函数&#xff0c;只需要将程序代码编写到主函数中就可以运行了&#xff0c;不过这个函数只是由我们来定义&#xff0c;而不是我们来调用。 当然&#xff0c;除了主函数之…

SMT | Kriging代理模型原理及应用

前言 代理模型工具箱 (surrogate modeling toolbox, SMT) 是一个基于Python开发的第三方包&#xff0c;其中包含代理模型方法、采样技术和基准测试函数。有关SMT的详细介绍参见&#xff1a; SMT | 代理模型Python工具包推荐 SMT可实现几个与高斯过程回归相关的代理模型&#x…

串口输出时:英文正常输出、中文乱码输出

一、问题&#xff1a;英文正常输出&#xff0c;英文乱码输出 二、解决方法 1、查看自己使用的串口助手的编码格式 2、查看自己使用输出的文件编码格式 以记事本的格式查看&#xff0c;原则上这两种应该保持相同&#xff0c;如果不相同&#xff0c;就需要把这个文件去另保存一…

图像与文本并存,多模态检索如何带来新的搜索革命

01 火热的多模态智能 回顾到2024的大型语言模型&#xff08;LLM&#xff09;的发展&#xff0c;让人欣喜的一点是scaling law依然奏效&#xff0c;智能随着资源的提高继续提高。但另一个让人担忧的点是高质量的文本语料似乎即将触及上限。为了加入更多的数据喂给模型&#xff0…

体育场座位【python实现】

题目来自此处 def main():seats list(map(int,input().split()))count 0for i in range(len(seats)):if seats[i] 0:if (i 0 or seats[i-1] 0) and(i len(seats)-1 or seats[i1] 0):count 1seats[i] 1print(seats)print(count) if __name__ "__main__":mai…

Win11 eNSP安装

前言 新买的电脑&#xff0c;安装eNSP总会遇到一些问题。如果你之前就是做网络安全而现在需要安装eNSP&#xff0c;你可能会因为安装过Wireshark导致一些问题。所以这里就为大家综合一篇文章&#xff0c;修复一些简单的问题。 下载地址&#xff1a;https://pan.baidu.com/s/17p…

Shell:初识sed、awk

Linux系统提供了两个常见的具备上述功能的工具。本节将会介绍Linux世界中最广泛使用的 两个命令行编辑器:sed和gawk。 1. sed编辑器 sed编辑器被称作流编辑器(stream editor)&#xff0c;流编辑器则会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。 sed编辑器可…

会做“减法”的项目经理,在工作中赢麻了!

都说我们在生活中要学会“断舍离”&#xff0c;其实工作中的一些事项、流程&#xff0c;也可以尽量精简&#xff01;对于项目经理来说也是如此&#xff0c;每天会议很多、需求不断&#xff0c;要适当做好“减法”&#xff0c;才能更好朝着目标方向前进&#xff01; 01、什么是做…

Linux 添加新用户之adduser 和 useradd 的区别 | 添加用户到 sudo 组【笔记型博文】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 ❤️ 创建新用户adduser 用法【推荐】useradd 用法 安装 sudo添加用户到 sudo 用…

Transformer学习(1):注意力机制

文章目录 什么是注意力如何实现注意注意力的计算过程总结 什么是注意力 在一张图像中&#xff0c;包含了各种信息&#xff0c;而我们会自动关注重要的信息。下图是注意力热力图&#xff0c;可以发现人们会注意兔子的脸这些重要信息。 而在深度学习中&#xff0c;输入数据包含…

Selenium自动化 Web 浏览器操作

文章目录 Selenium自动化 Web 浏览器操作Selenium简介安装Selenium安装WebDriver使用问题驱动加载报错版本不匹配 常用API创建实例定位元素鼠标事件表单相关多窗口切换等待显示等待隐式等待 文件上传下载经验总结 Selenium自动化 Web 浏览器操作 Selenium简介 Selenium可以模…