当前位置: 首页 > news >正文

二叉树与递归的相爱相杀

数据结构之二叉树

  • 一、基于二叉树的基础操作
    • 1.二叉树的构建
    • 2.二叉树的遍历
      • ①前序遍历(深度遍历)
      • ②中序遍历
      • ③后序遍历
      • ④层序遍历
        • 判断一棵二叉树是否是完全二叉树(基于层序遍历的思想)
    • 3.二叉树的数量问题
      • ①求二叉树结点个数
      • ②求二叉树叶子结点个数
      • ③求二叉树第K层结点个数
    • 4.查找某个结点所在位置
    • 5.二叉树的高度
  • 二、与二叉树相关的练习题(点击标题即可跳转至对应题目)
    • 1.单值二叉树
    • 2.判断两棵二叉树是否相同
    • 3.对称二叉树
    • 4.另一棵树的子树
    • 4.二叉树的前序遍历
    • 6.二叉树的构建及遍历
  • 三、第一部分的全部代码(复制粘贴到vs一定能跑通)
    • BinaryTree.h
    • BinaryTree.c
    • test.c

一、基于二叉树的基础操作

1.二叉树的构建

先看下面两句话
    我们整个操作是建立在三个文件上的。BinaryTree.h放置全部需要引用的头文件、二叉树结点的定义以及所有自定义函数的声明;BinaryTree.c放置所有自定义函数的实现(这里并不是很准确,有一些自定义函数是供其他一些自定义函数使用的函数是可以不用放到.h文件中去的,.h文件中放置的自定义函数主要是在test.c文件中需要使用的函数)test.c就放置主函数,供我们测试二叉树写得是否正确。其中BinaryTree.c和test.c文件引用BinaryTree.h
    前序遍历构建方法在第六道练习题中体现

刚开始我们用很简单的方法构建(三步搞定)
第一步:首先我们要定义单个结点

//BinaryTree.h
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;int val;
}BTNode;

第二步:基本架构

//test.c
int main()
{//构建6个节点BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);//建立这六个节点的指向关系node1->left = node2;node2->left = node3;node1->right = node4;node4->left = node5;node4->right = node6;
}

请添加图片描述
第三步:把BuyNode函数补上

BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc failed");exit(-1);}node->left = NULL;node->right = NULL;node->val = x;return node;
}

2.二叉树的遍历

①前序遍历(深度遍历)

正如文章标题所说的,二叉树与递归相爱相杀,所以这里必然用的是递归来遍历
至于原因呢,就是二叉树是一个很好的递归结构(二路递归)

  • 就拿前序遍历来说,先访问根,然后是左子树,右子树。其中访问左子树的时候,也是先访问左子树的根结点、左子树的左子树、左子树的右子树
  • 这就很好的满足了递归的思想——把大问题化成与其类似的规模较小的子问题,通过递归调用解决小问题。
  • 要注意的每次递归都会使问题变得更简单,直到问题已经简单到不需要进一步递归即可解决
  • 递归的两个关键属性是基本情况递推关系。基本情况是指递归过程中不再继续递归的条件,而递推关系则是将所有其他情况转换为基本情况的规则。
  • 一般情况下基本情况写在递推关系前面
void PreOrder(BTNode* root)
{//前面说到的,这就是递归的基本情况----递归不再继续的条件if (root == NULL)return;//递归的基本关系----大问题化小问题printf("%d ", root->val);//先访问根,遇到根就打印PreOrder(root->left);//根访问完,访问左子树PreOrder(root->right);//再访问右子树
}

递归图,按顺序走
请添加图片描述
最终打印结果(空未打印)
在这里插入图片描述

②中序遍历

中序、后序和前序很类似,只是改一下根节点访问时机,这里我就放一下代码

void InOrder(BTNode* root)
{if (root == NULL)return;InOrder(root->left);printf("%d ", root->val);InOrder(root->right);
}

③后序遍历

void PastOrder(BTNode* root)
{if (root == NULL)return;PastOrder(root->left);PastOrder(root->right);printf("%d ", root->val);
}

④层序遍历

层序遍历要结合队列来解决

  • 二叉树的层序遍历利用队列的原因主要在于队列的先进先出(FIFO)特性。
    层序遍历的目标是按层级顺序遍历二叉树的所有节点。
  • 具体地说,首先将二叉树的根节点推入队列,然后检查队列是否为空。如果不为空,就从队列中取出队头的元素,并访问这个元素代表的节点。
    然后,如果这个节点有左子树,就将左子树推入队列;如果有右子树,也将右子树推入队列。重复这个过程,直到队列为空。
  • 这样做的原因是,队列保证了我们总是先处理最先进入队列的节点,即按照层级顺序进行遍历。同时,这种方法适用于各种不同的二叉树结构

代码如下:(前提是有队列这个数据结构哈,没有的我会把代码一起放在第三部分,这里就展示层序遍历这部分的代码)

void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);//	首先根不为空,让根进队列if (root != NULL)QueuePush(&q, root);//在队列不为空的情况下while (!QueueEmpty(&q)){//读取对头元素printf("%d", QueueFront(&q)->val);//在左右子树不为空的情况下,让左右子树入队列if (QueueFront(&q)->left != NULL)QueuePush(&q,QueueFront(&q)->left);if (QueueFront(&q)->right != NULL)QueuePush(&q, QueueFront(&q)->right);//让对头元素出队列QueuePop(&q);}QueueDestroy(&q);
}

层序遍历的过程如下图(子树为空的时候不进队列)

请添加图片描述

判断一棵二叉树是否是完全二叉树(基于层序遍历的思想)
  • 完全二叉树的特点大家还记得吗,就是假如完成二叉树有k层,那么其前k-1层都是满的,而第k层所有结点都连续集中在最左边
  • 那么如何和层序遍历结合起来呢,就是我们按照层序遍历的方式一次将每一层入队列,然后出结点,接着带入左右子树。左右子树为空的时候也要入进去,就入个空值就好
  • 如果不是完全二叉树,那么在出队列时遇到空值时,队列里还有非空元素。而如果是完全二叉树,遇到空值的时候也代表则元素已经遍历完了
    非完全二叉树在这里插入图片描述
    完全二叉树
    在这里插入图片描述
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root == NULL)return 1;QueuePush(&q, root);while (!QueueEmpty(&q)){//此处碰到空值就跳出循环开始判断是否是完全二叉树if (QueueFront(&q) == NULL)break;//if (QueueFront(&q)->left != NULL)QueuePush(&q, QueueFront(&q)->left);//if (QueueFront(&q)->right != NULL)QueuePush(&q, QueueFront(&q)->right);QueuePop(&q);}//上述代码就是入队列的过程//走到这意味着遇到空,如果此时队列里都是空,则表示是完成二叉树while (!QueueEmpty(&q)){if (QueueFront(&q) == NULL){QueuePop(&q);continue;}//走到这里说明此时队列不为空,而出现了空值QueueDestroy(&q);return 0;}QueueDestroy(&q);return 1;
}

3.二叉树的数量问题

①求二叉树结点个数

递归思想:要求二叉树结点的个数,可以化为求左子树的结点个数+右子树结点个数+1(这个1就是算上根结点)

//相当于二叉树的后序遍历
int TreeSize(BTNode* root)
{//写法1//if (root == NULL)//	return 0;划分为左树的节点数+右树的节点数+1//return TreeSize(root->left) + TreeSize(root->right) + 1;//写法2//更简洁的写法return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

②求二叉树叶子结点个数

思路和上一题类似,只不过是找到叶子结点才算数

int TreeLeafSize(BTNode* root)
{//1.空节点返回0if (root == NULL)return 0;//注意,这里不能写成 return ;  因为 return ; 时默认就return 1回去,所以这样子求得的数值就是最后一层满载的时候的节点个数//2.叶子节点返回1if (root->left == NULL && root->right == NULL)return 1;//3.其他节点就递归到左右子树return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}

③求二叉树第K层结点个数

  • 这个有难度了,该怎么求第k层的结点个数呢?
  • 其实也是递归的思想:求从根结点开始的第k层的个数,等同于求从第二层开始的第k-1层的结点个数,也等同于求从第三层开始的第k-2层结点的个数。。。。。。
int TreeKLevelSize(BTNode* root, int k)
{if (root == NULL)return 0;//从第一层看第k层等于第二层看第k-1层//走到这k == 1时表示递归走到了该层,此时节点不会为空,,即表示这层有节点if (k == 1)return 1;//二叉树中双路递归的思想真的很重要!!!return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}

4.查找某个结点所在位置

  • 看到这个问题,大概思路大家肯定都能想到,就遍历呗,在遍历的过程中比较值是否相等呗,很简单
  • 但是这里有个问题哈,我们函数的返回值是找到的结点的地址,如果直接return回去,假如我们最开始就找到了这个地址,但是在后续的递归过没找到。而这个地址被NULL值覆盖了怎么办?
    -解决办法就是:加个判断,当ret不等于NULL时才return,这样子及时在函数最开始root == NULL时(即到了叶子结点)返回了NULL,但在后续对ret的判断时也不会让NULL覆盖真正的地址
BTNode* BinaryTreeFind(BTNode* root, int x)
{if (root == NULL)return NULL;else if (root->val == x)return root;BTNode* ret = NULL;//通过判空的方式很好的解决了如果后续的值不符合时返回的null值如何规避ret = BinaryTreeFind(root->left,x);if (ret != NULL)return ret;ret = BinaryTreeFind(root->right,x);if (ret != NULL)return ret;
}

5.二叉树的高度

  • 有了上面这些递归事例的基础,看这个问题就很简单了
  • 思路就是二叉树的高度等于左右子树的中较高的高度+1,然后再把子树给向下递归即可
int BinaryTreeHeight(BTNode* root)
{if (root == NULL)return 0;int LeftHeight = BinaryTreeHeight(root->left);int RightHeight = BinaryTreeHeight(root->right);return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
}
  • 不过这里要注意一个问题
  • 很多同学为了偷懒而像下面这样简写是不对的
  • 这样子看似代码简洁,但是这个代码的效率很低,你看不论较高的子树是左子树还是右子树,比较完之后还需要计算一遍左右子树的高度,效率很低!
int BinaryTreeHeight(BTNode* root)
{if (root == NULL)return 0;return BinaryTreeHeight(root->left) >  BinaryTreeHeight(root->right) ? BinaryTreeHeight(root->left) + 1 : BinaryTreeHeight(root->right)+ 1;
}

二、与二叉树相关的练习题(点击标题即可跳转至对应题目)

1.单值二叉树

  • 思路:判断二叉树是不是单值二叉树,就是看其左右子树是不是单值二叉树
  • 在这个递归的题目中,有一点很重要的就是,既然是判断是否,那肯定就是有些条件下是return false,有些条件是return true,所以代码中第二个if处不能写成
    if(root->left = = NULL && root->left->val == root->val)
        return true;
    这个条件其实是继续递归的条件,你在这里就return了,如果后面还有不等的情况怎么办?
bool isUnivalTree(struct TreeNode* root){if(root == NULL)return true;if(root->left != NULL && root->left->val != root->val)return false;if(root->right != NULL && root->right->val != root->val)return false;return isUnivalTree(root->left) && isUnivalTree(root->right);
}

2.判断两棵二叉树是否相同

这个题相对来说就比较简单了,但是这个题是下个题的基础

bool isSameTree(struct TreeNode* p, struct TreeNode* q){if(p == NULL && q == NULL)return true;//走到这里肯定只有一个会为空,一空一非空肯定不相等if(p == NULL || q == NULL)return false;//走到这里肯定两个都不为空if(p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}

3.对称二叉树

把上一题的两棵树合成一棵树了

bool isSame(struct TreeNode* p,struct TreeNode* q)
{if(p == NULL && q == NULL)return true;if(p == NULL || q == NULL)return false;if(p->val != q->val)return false;return isSame(p->left,q->right) && isSame(p->right,q->left);
}bool isSymmetric(struct TreeNode* root){return isSame(root->left,root->right);    
}

4.另一棵树的子树

思路:让root及其子树依次的去和subRoot比较,用于比较的函数就是上上一题所写的

bool isSameTree(struct TreeNode* p, struct TreeNode* q){if(p == NULL && q == NULL)return true;//走到这里肯定只有一个会为空if(p == NULL || q == NULL)return false;//走到这里肯定两个都不为空if(p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}//subRoot不动root动bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){if(root == NULL)return false;if(root->val == subRoot->val){//为什么不直接return isSameTree,因为这里如果isSameTree结果是false,并不能直接return回去,因为还要去子树继续比较,唯一返回false的条件就是走到空了if(isSameTree(root,subRoot))return true;}//这里用‘或’就行,只要子树有一个满足条件就OK了return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}

4.二叉树的前序遍历

这里题目要求,需要把遍历的结果放入数组中,所以我们需要先计算树中有多少个结点,然后构建多大的数组。

int TreeSize(struct TreeNode* root)
{if (root == NULL)return 0;return TreeSize(root->left) + TreeSize(root->right) + 1;
}// static int i = 0;
void PreTree(struct TreeNode* root, int* a,int* pi)
{if (root == NULL)return;a[(*pi)++] = root->val;//如果直接定义普通的i,那么在两路递归里这俩i是形参,值改变了对另外一个i没有影响PreTree(root->left, a,pi);PreTree(root->right,a, pi);
}//returnSize是返回数组的元素个数,要返回的还是数组!!!
int* preorderTraversal(struct TreeNode* root, int* returnSize) {int n = TreeSize(root);int* a = (int*)malloc(sizeof(int) * n);int i = 0;PreTree(root, a,&i);*returnSize = n;return a;
}

这俩就留着你们来砍瓜切菜了
二叉树的中序遍历
二叉树的后序遍历

6.二叉树的构建及遍历

  • 实现是构建,思路也是用递归,这里我用前序遍历构建,既然是前序遍历,那我们是按照根-左-右的顺序来的,所以先构建根结点,然后递归调用CreateTree,最后别忘了终止条件——在遇到’#'代表着空节点,记得return NULL
    -遍历的话,就按照题目要求的后序遍历即可
#include <stdio.h>
#include<stdlib.h>typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;char val;
}BTNode;BTNode* CreateTree(char* str,int* pi)
{if(str[*pi] == '#'){(*pi)++;return NULL;}BTNode* root = (BTNode*)malloc(sizeof(BTNode));root->val = str[*pi];(*pi)++;root->left = CreateTree(str,pi);root->right = CreateTree(str,pi);return root;
}void InOrder(BTNode* root)
{if(root == NULL){return;}InOrder(root->left);printf("%c ",root->val);InOrder(root->right);
}int main() {char str[100];scanf("%s",str);int i = 0;BTNode* root = CreateTree(str,&i);InOrder(root);return 0;
}

三、第一部分的全部代码(复制粘贴到vs一定能跑通)

BinaryTree.h

#pragma once#include<stdio.h>
#include<stdlib.h>typedef struct BinaryTreeNode
{struct BinaryTreeNode* left;struct BinaryTreeNode* right;int val;
}BTNode;BTNode* BuyNode(int x);void PreOrder(BTNode* root);
void InOrder(BTNode* root);
void PastOrder(BTNode* root);int TreeSize(BTNode* root);
int TreeLeafSize(BTNode* root);
int TreeKLevelSize(BTNode* root, int k);void LevelOrder(BTNode* root);//层序遍历BTNode* BinaryTreeFind(BTNode* root, int x);
void BinaryTreeDestroy(BTNode* root);
//是完全二叉树返回1,否则返回0
int BinaryTreeComplete(BTNode* root);
int BinaryTreeHeight(BTNode* root);

BinaryTree.c

#define _CRT_SECURE_NO_WARNINGS 1#include"BinaryTree.h"
#include"Queue.h"BTNode* BuyNode(int x)
{BTNode* node = (BTNode*)malloc(sizeof(BTNode));if (node == NULL){perror("malloc failed");exit(-1);}node->left = NULL;node->right = NULL;node->val = x;return node;
}void PreOrder(BTNode* root)
{if (root == NULL)return;printf("%d ", root->val);PreOrder(root->left);PreOrder(root->right);
}void InOrder(BTNode* root)
{if (root == NULL)return;InOrder(root->left);printf("%d ", root->val);InOrder(root->right);
}void PastOrder(BTNode* root)
{if (root == NULL)return;PastOrder(root->left);PastOrder(root->right);printf("%d ", root->val);
}//相当于二叉树的后序遍历
int TreeSize(BTNode* root)
{//if (root == NULL)//	return 0;划分为左树的节点数+右树的节点数+1//return TreeSize(root->left) + TreeSize(root->right) + 1;//更简洁的写法(不过有弊端,在下个函数提)return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}//这个写法ok
//int TreeLeafSize(BTNode* root)
//{
//	//这里也要记得判断
//	//当递归不断深入时,遇到NULL不能继续使用了
//	if (root == NULL)
//		return;
//
//	static count = 0;
//
//	if (root->left == NULL && root->right == NULL)
//		count++;
//	
//	TreeLeafSize(root->left);
//	TreeLeafSize(root->right);
//
//	return count;
//	//这里在递归里可以return是因为虽然随着递归的进行,每次递归分路回流的时候都会return一次count
//	//但是最后正确的count会覆盖之前的值
//}//递归写法
int TreeLeafSize(BTNode* root)
{//1.空节点返回0if (root == NULL)return 0;//为什么这里空节点 return; 时结果却是4呢//经测试,return ; 时默认就return 1回去,所以这样子求得的数值就是最后一层满载的时候的节点个数//2.叶子节点返回1if (root->left == NULL && root->right == NULL)return 1;//3.其他节点就递归到左右子树return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}int TreeKLevelSize(BTNode* root, int k)
{if (root == NULL)return 0;//从第一层看第k层等于第二层看第k-1层//k == 1时表示递归走到了该层,此时节点不会为空,,即表示这层有节点if (k == 1)return 1;//二叉树中双路递归的思想真的很重要!!!return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}BTNode* BinaryTreeFind(BTNode* root, int x)
{if (root == NULL)return NULL;else if (root->val == x)return root;BTNode* ret = NULL;//通过判空的方式很好的解决了如果后续的值不符合时返回的null值如何规避ret = BinaryTreeFind(root->left,x);if (ret != NULL)return ret;ret = BinaryTreeFind(root->right,x);if (ret != NULL)return ret;
}//后序遍历销毁
void BinaryTreeDestroy(BTNode* root)
{if (root == NULL)return;BinaryTreeDestroy(root->left);BinaryTreeDestroy(root->right);free(root);//在这里置空无用,因为是形参//root = NULL;
}//要利用队列先进先出的特点
//最高层先入队列,然后依次出队列,在出队列的过程中将左右子树带入队列,直到队列为空
void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root != NULL)QueuePush(&q, root);while (!QueueEmpty(&q)){printf("%d", QueueFront(&q)->val);if (QueueFront(&q)->left != NULL)QueuePush(&q,QueueFront(&q)->left);if (QueueFront(&q)->right != NULL)QueuePush(&q, QueueFront(&q)->right);QueuePop(&q);}QueueDestroy(&q);
}//思路类似层序遍历,只不过在入队列的时候要把左右子树全部入进去,即时是为空的情况下
//为什么呢,因为完全二叉树在物理结构上一定是连续的
//如果队列还没出完的情况下,就已经遇到空值了,说明就不是完全二叉树
int BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root == NULL)return 1;QueuePush(&q, root);while (!QueueEmpty(&q)){if (QueueFront(&q) == NULL)break;//if (QueueFront(&q)->left != NULL)QueuePush(&q, QueueFront(&q)->left);//if (QueueFront(&q)->right != NULL)QueuePush(&q, QueueFront(&q)->right);QueuePop(&q);}//走到这意味着遇到空,如果此时队列里都是空,则表示是完成二叉树while (!QueueEmpty(&q)){if (QueueFront(&q) == NULL){QueuePop(&q);continue;}QueueDestroy(&q);return 0;}QueueDestroy(&q);return 1;
}int BinaryTreeHeight(BTNode* root)
{if (root == NULL)return 0;int LeftHeight = BinaryTreeHeight(root->left);int RightHeight = BinaryTreeHeight(root->right);return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1#include"BinaryTree.h"
#include"Queue.h"int main()
{//手动构建一棵树BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);//BTNode* node7 = BuyNode(7);//BTNode* node8 = BuyNode(8);node1->left = node2;node2->left = node3;//node2->right = node7;node1->right = node4;node4->left = node5;node4->right = node6;//node3->right = node8;//前中后序// //PreOrder(node1);//printf("\n");////InOrder(node1);//printf("\n");//PastOrder(node1);//printf("\n");//总节点数、叶子节点数、第k层节点数// //printf("%d\n", TreeSize(node1));//printf("%d\n", TreeLeafSize(node1));//count = 0;//这里有个缺陷就是使用static局部变量之后这个函数只能调用一次//printf("%d\n", TreeLeafSize(node1));//printf("%d", TreeKLevelSize(node1,3));//BTNode* ret = BinaryTreeFind(node1,5);//printf("%d", ret->val);//BinaryTreeDestroy(node1);//node1 = NULL;//在外面置空一下就好了//LevelOrder(node1);//printf("%d", BinaryTreeComplete(node1));//printf("%d", BinaryTreeHeight(node1));return 0;
}

相关文章:

二叉树与递归的相爱相杀

数据结构之二叉树 一、基于二叉树的基础操作1.二叉树的构建2.二叉树的遍历①前序遍历&#xff08;深度遍历&#xff09;②中序遍历③后序遍历④层序遍历判断一棵二叉树是否是完全二叉树&#xff08;基于层序遍历的思想&#xff09; 3.二叉树的数量问题①求二叉树结点个数②求二…...

Docker 安装 reids

docker run -itd --name myredis -p 6379:6379 redis --requirepass “123456” --restartalways --appendonly yes...

opensl学习——base16编码解码、base64编码解码、ASCII码表、扩展ASCII码

文章目录 ASCII表概述base家族简单说明 Hex(十六进制)编码、Base32编码、Base64编码、base256编码base16编码与解码base64编码概述转换过程不足 3 字节处理方法例子一,不足3字节&#xff0c;只有一个字节例子二,不足3字节&#xff0c;只有两个字节 base64示例代码1代码分析 acl…...

gazebo各种插件

类别 libgazebo_ros_api_plugin.so&#xff1a;提供与Gazebo仿真环境进行通信的API接口。 libgazebo_ros_block_laser.so&#xff1a;模拟激光传感器的插件。 libgazebo_ros_bumper.so&#xff1a;模拟碰撞传感器的插件。 libgazebo_ros_camera.so&#xff1a;模拟相机传感器的…...

C语言Free空指针会怎样?

在C语言中&#xff0c;使用free函数释放一个空指针是安全的&#xff0c;不会引发任何错误或异常。具体来说&#xff0c;当使用free函数释放一个空指针时&#xff0c;free函数会忽略这个空指针&#xff0c;并且不会执行任何操作。这是因为free函数只对有效的指针进行内存释放操作…...

软件测试全套教程,软件测试自学线路图

软件测试&#xff1a; 软件测试是为了发现程序中的错误而执行程序的过程。 通俗的说&#xff0c;软件测试需要在发布软件之前&#xff0c;尽可能的找软件的错误&#xff0c;尽量避免在发布之后给用户带来不好的体验&#xff0c;并要满足用户使用的需求。 现在市面上这么多软…...

禁止浏览器缩放

禁止浏览器缩放 1. 页面中添加如下代码&#xff1a;2. css单位统一使用rem&#xff0c;如下&#xff1a; 两个条件即可实现&#xff1a; 1. 动态修改html属性fontsize的值&#xff1b; 2. css单位统一使用rem。 1. 页面中添加如下代码&#xff1a; // 定义基准字体 new functi…...

前端食堂技术周刊第 100 期:TS 5.3 Beta、ViteConf2023、Rspress 1.0、Fresh 1.5、Chrome 118

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;乌龙金桂 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…...

汇川IT7000系列HMI使用脚本实现画面跳转时自动切换手自动模式

汇川IT7070E工业HMI使用实例(1) 用脚本切换模式 我们在使用工业HMI做画面时,可能会有这样的需求,希望切换画面时,可以根据不同的画面,自动切换相应的模式,比如有些画面是进行手动操作的,有些画面是进行自动操作的,当我们需要手动时,希望进入画面自动切换为“手动模…...

FDTD Solutions笔记

FDTD Solutions笔记 目录使用流程实例 目录 使用流程 实例 材料条件 步骤 基底 2. 添加规则膜层 3. 添加仿真区 解释&#xff1a; 仿真区为&#xff08;0,0&#xff09;&#xff0c;x方向为0.4&#xff0c;y方向是1 解释&#xff1a; 一般先用低精度进行计算 解释&#xff1a…...

SQL SELECT DISTINCT(选择不同) 语法

SQL SELECT DISTINCT 语法 SELECT DISTINCT语法用于仅返回不同的&#xff08;different&#xff09;值。 在一张表内&#xff0c;一列通常包含许多重复的值; 有时你只想列出不同的&#xff08;different&#xff09;值。 SELECT DISTINCT语句用于仅返回不同的&#xff08;diffe…...

常见的数据结构及应用

文章目录 前言数据结构介绍数组链表队列和栈树堆 总结 前言 数据结构是计算机存储、组织数据的方式。在工作中&#xff0c;我们通常会直接使用已经封装好的集合API&#xff0c;这样可以更高效地完成任务。但是作为一名程序员&#xff0c;掌握数据结构是非常重要的&#xff0c;…...

基于模型预测人工势场的船舶运动规划方法,考虑复杂遭遇场景下的COLREG(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

【UE5 Cesium】19-Cesium for Unreal 建立飞行跟踪器(4)

遗留问题 在上一篇博客中&#xff08;【UE5 Cesium】18-Cesium for Unreal 建立飞行跟踪器&#xff08;3&#xff09;&#xff09;&#xff0c;我们实现了飞机变速飞行的功能&#xff0c;但是还存在两个问题&#xff0c;分别是&#xff1a; &#xff08;1&#xff09;由于UE的…...

TrustZone

TrustZone技术 让我们从最重要的问题开始&#xff1a;为什么存在TrustZone技术&#xff0c;它防御什么&#xff1f;保护用 C 和 C 编写的大型程序免受黑客攻击可能是一个挑战。内存损坏漏洞是一个常见问题&#xff0c;尽管消除它们是安全工程师的核心目标&#xff0c;但从操作…...

✔ ★【备战实习(面经+项目+算法)】 10.16学习时间表(总计学习时间:5h)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…...

React + Router

React Router 这个只是专门讲解 React Router 新开的例子。 教程来源&#xff1a;https://reactrouter.com/en/main/start/tutorial 创建新项目 yarn create vite my-react-router-app --template react-ts cd my-react-router-app yarn安装 React Router 依赖: yarn add…...

微信小程序设置动态变量设值

微信小程序设置动态变量设值 微信小程序如何动态变量设值&#xff1f; 示例代码如下&#xff1a; setValFunc() {const key this.data.currentPickerid; // 业务需求动态键值key&#xff0c;或者是上一界面获取的动态key值const value 变量值;this.setData({[${key}]: valu…...

闪站侠洗衣洗鞋多门店多用户管理系统,洗鞋店干洗店小程序开发;

闪站侠洗护软件是多分店多用户管理系统&#xff0c;一个分店可以同时关联多个用户。闪站侠洗护管理软件通过互联网为洗衣店/洗鞋店干洗店提供加盟或直营连锁管理&#xff1b; 实现会员洗衣的门店收衣->上门收衣->开单拍照->清洗护理/工厂洗涤->微&#xff5c;信/短…...

JDBC增删改查示例

数据库表 CREATE TABLE customers ( id int NOT NULL AUTO_INCREMENT, name varchar(15) DEFAULT NULL, email varchar(20) DEFAULT NULL, birth date DEFAULT NULL, photo mediumblob, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT39 DEFAULT CHARSETgb2312;…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...