二叉树(二)———链式二叉树结构

news2024/11/19 11:30:29

链式结构

链式二叉树的存储结构是指用链表来表示一棵二叉树,即用链表来指示元素的逻辑关系。通常的方法是链表中的每个节点由三个域组成,数据域与左右指针域,左右指针分别用来存储该节点左孩子和右孩子所在链节点的存储位置,这种被称为二叉链。 还有一种实现方法就是三叉链,相比二叉链多了一个指示父节点的指针。

看待非空二叉树一定要把一个节点分成三个部分来开,根、左子树和右子树。每一个叶子结点的左右子树都是空树。

对于链式二叉树,最重要的是学好它的四种结构遍历,其中三种是递归遍历,还有一个层序遍历。

三种递归遍历

1.前序遍历,又称先序遍历——访问根节点的操作发生在遍历其左右子树之前

2.中序遍历——访问根节点的操作发生在遍历其左右子树之间(中)

2.后序遍历——访问根节点的操作发生在遍历其左右子树之后

由于被访问的节点必是某子树的根,所以N(Node)、L(Left subtree)、R(Right subtree)又可解释为根、根的左子树和根的右子树,NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

前中后序指的是根节点的访问次序

就拿下面这一棵简单的二叉树来举例

他的前序遍历的节点顺序是:1  2  4  null  null  null  3  5  null  null  6 null null

他的中序遍历的节点顺序是:null 4  null  2  null  1  null  5  null  3  null  6  null  

他的后序遍历的节点顺序是: null  null  4  null  2  null  null  5  null  null  6  3  1

学习这里我们要了解函数栈帧的创建和销毁的过程,学习之前的数据结构时,我们都是一上来就手撕一个,但是学习到这些复杂的数据结构时就不能再这么玩了,我们要先理解二叉树的各种操作以及用途意义。 而且对于普通的二叉树,我们是没必要实现他的增删查改的,因为普通二叉树本来就不是用来存储数据的,所以实现他的增删查改是没有意义的。二叉树的一个常见的应用就是搜索二叉树,对于我们来说实现这些复杂的二叉树结构目前来说还不太现实,而且C语言也不太适合这些操作,所以我们在这里就先理解好二叉树的遍历思路。

二叉树的前序遍历该如何实现呢?

对于每一棵二叉树,我们首先访问他的根节点,然后再访问他的左子树,左子树访问完了之后再访问它的右子树。 而访问他的左子树的时候,又是相同的过程,把左子树看成单独的一棵树,先访问他的根节点,再访问他的左右子树。以此类推,这就是一个递归的过程,那么递归的返回条件是什么呢?我们不妨画图来分析一下。以上面的二叉树来举例,

前序遍历(PreOrder(BTNode* root))

我们可以画出递归的栈帧图,当我们调用PreOrder去遍历空树时,这时候就应该返回上一层了,也就是 root==NULL 的时候就返回上一层栈帧。我们每一次调用PreOrder函数时首先要判断root是不是空,如果是空就返回,然后不是空,我们首先要访问根节点的数据,然后遍历左子树和右子树。

我们的代码大概就能写出来了。

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
		return;

	//访问根节点
	printf("%d ", root->data);

	//遍历左右子树
	PreOrder(root->left);
	PreOrder(root->right);
}

当遍历到 4 的左子树 NULL 时,返回到上一层,并销毁这一层的栈帧 ,然后再接着代码往下执行遍历 4 的右子树 ,而4 的右子树又是空树,返回到上一层,这时候 4 这棵树就访问完了,代码执行完之后销毁栈帧,返回上一层 ,当 4 返回之后也就意味着 2 的左子树访问完返回了,接着代码往下执行就开始遍历 2 的右子树,由于二的右子树为空树,返回之后 2 这棵树就执行完了,在返回上一层(1),这时候 1 的左子树就遍历完了 ,再往下执行去遍历 1  的右子树。以此类推,当 1的右子树遍历完返回之后,整棵树就遍历完了 ,最后销毁这一层栈帧 ,返回 main 函数的栈帧。 

二叉树的中序和后序遍历也是一样的思路,把每一个节点分成根节点,左子树和右子树三部分,分而治之,每一层递归只完成他这个节点该完成的事,其他的交给递归左子树和右子树。

我们可以手动创建一棵二叉树来验证上面的前序顺序是否正确


BTNode* CreatTree()
{
	BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
	assert(n1);
	n1->data = 1;
	n1->left = n1->right = NULL;

	BTNode * n2 = (BTNode*)malloc(sizeof(BTNode));
	assert(n2);
	n2->data = 2;
	n2->left = n2->right = NULL;

	BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
	assert(n3);
	n3->data = 3;
	n3->left = n3->right = NULL;

	BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
	assert(n4);
	n4->data = 4;
	n4->left = n4->right = NULL;

	BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
	assert(n5);
	n5->data = 5;
	n5->left = n5->right = NULL;

	BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
	assert(n6);
	n6->data = 6;
	n6->left = n6->right = NULL;



	n1->left = n2;
	n1->right = n3;
	n2->left = n4;
	n3->left = n5;
	n3->right = n6;

	return n1;

}
int main()
{
	BTNode* root = CreatTree();

	PreOrder(root);

	return 0;
}

如果想要看到NULL的话也可以在空树返回之前打印一个NULL

前序遍历会了中序和后序就是一样的思路,

中序

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
		return;
	//遍历左子树
	InOrder(root->left);

	//访问根节点
	printf("%d ", root->data);

	//遍历右子树
	InOrder(root->right);
}

后序

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
		return;
	
	//遍历左右子树
	PostOrder(root->left);
	PostOrder(root->right);

	//访问根节点
	prntf("%d ", root->data);
}

但是单纯的遍历一棵树意义是不大的,我们要结合具体的场景来遍历

下面我们就用一些具体的简单场景来练习一下遍历和分治的思维

1. 求二叉树的节点个数

求节点总个数是一个很简单的问题,我们前面遍历的代码是把数据打印了出来,而记录节点个数的话我们就只需要实现一个计数就行了。

//求节点总个数
int TreeSize(BTNode* root)
{
	if (!root)//空树结点个数为0
		return 0;

	//非空树返回左右子树的节点数 加上当前节点
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

思路也很简单,首先就是判断是否为空树,如果是空树,节点个数就是0 ,如果是非空树,就是该节点加上他的左右子树的节点个数。

当然在这个遍历中选择前中后序随便哪一种都行,都不会对结果产生影响

2.求叶子节点的个数

叶子节点怎么计数?无非就是当他的左右子树都为空树的时候这个节点就是叶子节点,而当他是非叶子节点的时候,就递归他的左右子树,返回他的左右子树中的叶子节点之和。 那么这里要不要判断空树呢? 肯定是要的,一来 传参可能传的是空树,空树的节点数为0,二就是当一个节点左子树或右子树有一个为空的时候,我们是要递归他的左右子树的,会递归到空树,也是要有返回的。

//求叶子节点的个数

int TreeLeafSize(BTNode* root)
{
	//空树返回0 ,防止传参为NULL
	if (!root)
		return 0;

	//左右子树都为空树,说明是叶子节点,返回
	if (!root->left && !root->right)
		return 1;

	//走到这里说明这个节点是非叶子节点
	//返回左右子树的叶子节点之和
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
3. 求度为1的节点的个函数

度为1的节点怎么来的呢? 有了上面的经验,我们首先就是要对空树有一个返回值0.对于一个节点,如果他的左右子树有一个为空,他就是度为1 的节点。这时候就返回了吗?还没有,因为这个节点的非空子树上可能还有度为1的节点,所以我们还是要去递归他的左右子树。

//求度为1的节点个数
int TreeNodeof1Size(BTNode* root)
{
	if (!root)
		return 0;

	if(!root->left+!root->right==1)//度为1的节点
		return 1+ TreeNodeof1Size(root->left) + TreeNodeof1Size(root->right);

	//此节点不是度为1的节点,左右子树都不为空
		return TreeNodeof1Size(root->left) + TreeNodeof1Size(root->right);
}

4.求树的高度

求树的高度我们首先要知道空树的高度就是0,一个非空节点作为根节点的树的高度怎么算呢?首先根节点的高度就是1了,然后再加上他的左右子树的高度的较大值。

//求树的高度
int TreeHeight(BTNode*root)
{
	//空树返回0,同时也是递归结束条件
	if (!root)
		return 0;
	//求左右子树的高度
	int left = TreeHeight(root->left);
	int right = TreeHeight(root->right);

	return 1 + (left > right ? left : right);
}

5.求第k层的节点个数

第k层是相对于根节点来说的,如果 k 为1的非空树,第1 层就是一个节点。 而相对于根节点的第k层对于他的左右子树的根节点来说就是 k-1 层了,这就是我们递归的思路。

//求第k层的节点个数
int TreeKLevelSize(BTNode* root, int k)
{
	if (!root)
		return 0;
	//非空树的0层节点个数就是1
	if (k == 1)
		return 1;

	return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}

6.在二叉树中查找数据,返回节点

先不管数据是什么,如果是空树肯定就是返回NULL,如果根节点不是要找的数据,这时候返回什么呢?肯定是遍历左右子树去找,但是返回值怎么设置呢?如果左右子树都找不到该数据,那么肯定是返回NULL,但是如果左子树中某的节点的数据就是要找的数据,把这个节点root返回给他的上一层,他的上一层该怎么处理呢?这时候他的上一层还要继续去遍历他的右子树吗?当然不用,所以当他的左子树返回值不为空我们直接就返回了,不要去遍历右子树了。

//查找数据
BTNode* TreeFind(BTNode* root, int data)
{
	//空树和递归结束
	if (!root)
		return NULL;

	if (root->data == data)
		return root;

	//走到这里说明根节点不是要找的,遍历左子树
	BTNode* left = TreeFind(root->left, data);
	if (left)
		return left;

	//当左子树找不到时,如果右子树找到了,返回右子树的返回值,如果找不到,右子树的返回值为空,也可以返回
	return TreeFind(root->right,data);
}

7.二叉树的销毁

二叉树的销毁与前面有一点不一样,我们不能用前序和中序遍历,要等他的左右子树都销毁了再销毁根节点,也就是后序遍历。

//销毁二叉树
void TreeDestroy(BTNode** proot)
{
	if (*proot == NULL)
		return;
	
	//先销毁左右子树
	TreeDestroy(&(*proot)->left);
	TreeDestroy(&(*proot)->right);
	(*proot)->left = NULL;
	(*proot)->right = NULL;
	//最后释放根节点
	free(*proot);
	*proot = NULL;
	return;
}
8.等值二叉树

. - 力扣(LeetCode)

单值二叉树就是所有节点的值都相等的树,首先如果是空树的话返回true,对于一棵非空树,如果他的根节点与左右节点等值,这时候再去遍历他的左右子树。如果左右子树都是等值二叉树,这棵树才是等值二叉树,如果有一棵不是,则返回false

bool isUnivalTree(struct TreeNode* root) {
    //空树或递归到空节点都返回true
    if(!root)
    return true;
    //左树不为空
    if(root->left)
    {
        if(root->val!=root->left->val)
        return false;
    }
    //右树不为空
    if(root->right)
    {
        if(root->val!=root->right->val)
        return false;
    }
    //递归子树
    return isUnivalTree(root->left)&&isUnivalTree(root->right);
}

9.相同的树

. - 力扣(LeetCode)

这个题和上一个题的思路其实是差不多的。首先如果两棵树都是空树,则返回true,如果其中一棵为空树,就返回false,然后再去遍历他的左右子树。

bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
    //空树或者递归到空树返回真
    if(!p&&!q)
    return true;

    //一个为空一个非空
    if(!p||!q)
    return false;

    //值不相等返回假
    if(q->val!=p->val)
    return false;

    //递归子树
    return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}

10.另一棵树的子树

. - 力扣(LeetCode)

首先相同的子树就是在另一棵树中有完全相同的一棵子树,这里就能用到我们上面写的相同的树了,我们首先要遍历root二叉树,当遇到子树的根节点与subRoot的根节点相同时,我们就进入是否是相同二叉树的判断,如果为真就直接返回真,如果为假就继续遍历。如果遍历完了都没有返回,就说明找不到相同的子树,返回假。只要左右子树有一个返回真就是真。

bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    //subroot是空树时返回真
    if(!subRoot)
    return true;

    //遍历到空节点还没匹配就返回false
    if(!root)
    return false;

    //根节点相同的时候
    if(root->val==subRoot->val&&isSameTree(root,subRoot))
    {
        return true; 
    }

    return isSameTree(root->left,subRoot)||isSameTree(root->right,subRoot);
}

11.对称的树

. - 力扣(LeetCode)

判断是否是对称二叉树,主要就是判断两棵子树,他们的根节点相不相等,然后他们的左右节点是否对称且相等。然后再递归,直到递归到空树。

bool _isSymmetric(struct TreeNode*p,struct TreeNode*q)
{
    //递归到空节点
    if(!p&&!q)
    return true;
    if(!p||!q)
    return false;
    if(q->val!=p->val)
    return false;
    return _isSymmetric(p->left,q->right)&&_isSymmetric(p->right,q->left);


}


bool isSymmetric(struct TreeNode* root) {
    if(!root)
    return true;
    //两边都为空
    if(!root->left&&!root->right)
    return true;
    //只有一边为空时
    if(!root->left||!root->right)
    return false;

    return _isSymmetric(root->left,root->right);
}   

12.前序序列构建二叉树

二叉树遍历_牛客题霸_牛客网

二叉树的前序序列构建二叉树,我们首先要了解二叉树的前序遍历,只有当左子树遍历完了,才会遍历右子树,就拿这个实力来说,我们可以画出他的二叉树结构。首先 a 是整棵二叉树的根节点,而 b 则是他的左子节点,访问b之后会遍历b的左子树,所以 c 时b的左子节点,# 是c 的左子节点,这时会返回去遍历c的右子树,所以 # 的c的右子节点 。c遍历完了就回去遍历b的右子树,所以d是b的右节点,而 e 是d的左子节点,# 是e的左子节点,g是e的右子节点 g的左右子节点都是空,这时就返回到 d ,f是d的右子节点,而f的左右节点都是空,这时候 a的左子树就构建完了,所以#是a的右子节点。

 

代码的实现构建的思路就是,先构建节点的左子树,遇到 # 的时候就直接返回上一层,然后构建上一层的右子树。在这里传参str的时候,我们可以传str的地址,也可以传str和一个整形的地址,用来修改下标。

#include <stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef struct BTNode
{
    char data;
    struct BTNode*left;
    struct BTNode*right;
}BTNode;

BTNode* NewNode(char ch)
{
    BTNode*newnode=(BTNode*)malloc(sizeof(BTNode));
    assert(newnode);
    newnode->left=NULL;
    newnode->right=NULL;
    newnode->data=ch;
    return newnode;
}

BTNode* CreatTree(char*str,int*pi)
{
    if(str[*pi]=='#')
    {
        (*pi)++;
    return NULL;
    }
    BTNode*root=NewNode(str[*pi]);
    (*pi)++;
    root->left=CreatTree(str,pi);
    root->right=CreatTree(str,pi);
    return root;
}

void InOrder(BTNode*root)
{
    if(!root)
    return;
    InOrder(root->left);
    printf("%c ",root->data);
    InOrder(root->right);
}

int main()
{
    char str[101];
    scanf("%s",str);
    int i=0;
    BTNode*root=CreatTree(str,&i);
    InOrder(root);

    return 0;
}

层序遍历(非递归)

层序遍历就是一层遍历完再遍历下一层,如上图,先访问 1 ,再访问 1 的左右节点 ,再访问 2 的左右节点 ,3的左右节点... ...

层序遍历是利用队列来实现的。

如何实现呢?

第一步:将根节点入队列

注意,这里我们入的是节点的指针。

第二步:访问队头节点,同时将对头节点的左右子节点入队列

然后再访问头节点,然后将对头结点的左右子节点入队列。

直到队列为空,这时候就访问完了。

//层序遍历打印
void TreePrint(BTNode* root)
{
	assert(root);
	Queue q;
	QueueInit(&q);
	//首先根节点入队列
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* cur = QueueFront(&q);
		QueuePop(&q);
		printf("%d ", cur->data);
		//把非空的节点入队列
		if (cur->left)
			QueuePush(&q, cur->left);
		if (cur->right)
			QueuePush(&q, cur->right);
	}

	QueueDestroy(&q);
}

如果我们只是打印的话不用把NULL也入队列,但是有一些情况是要把NULL也入队列来判断的。比如下面的一个题

判断一棵树是不是完全二叉树

判断一棵树是否为完全二叉树,我们用递归就不好判断了,会很复杂,而如果我们用层序遍历就会很简单。

这里和上面的打印有一点不一样,判断完全二叉树时要把NULL节点也入队列,为什么呢?我们可以拿上面的简单的二叉树来分析一下。

当走到这一步时,再次进入循环,对头的节点就是空节点了,而这时候空节点的后面还有非空节点,这就说明这棵树不是完全二叉树。 因为对于完全二叉树,这样层序遍历的时候,遇到第一个空节点时,他的后面就不会再有非空节点了,完全二叉树在最后一层节点都是连续的,不可能出现最后一层非空节点的左边还有一个空节点。

这时候我们的循环结束条件就变了,变成了队头元素是否为NULL。而跳出循环之后还要判断此时队列内是否都是NULL,如果是,则说明是完全二叉树,如果还有非空节点,则说明不是完全二叉树。

//判断剩余队列是否都是NULL
bool isNULLQueue(Queue* pq)
{
	assert(pq);
	while (!QueueEmpty(pq))
	{
		//遇到非空就直接返回假
		if (QueueFront(pq) != NULL)
			return false;

		QueuePop(pq);
	}
	//都是NULL返回真
	return true;
}

//判断是否为完全二叉树
bool _isCompleteTree(BTNode* root)
{
	assert(root);
	Queue q;
	QueueInit(&q);

	//根节点入队列
	QueuePush(&q, root);
	while (QueueFront(&q))
	{
		BTNode* head = QueueFront(&q);
		QueuePush(&q, head->left);
		QueuePush(&q, head->right);
		QueuePop(&q);
	}
	//跳出循环之后判断队列是否还有非空节点
	if (isNULLQueue(&q))
	{
		QueueDestroy(&q);
		return true;
	}
	QueueDestroy(&q);
	return false;
}

 

当我们把6节点链接到2的右节点时,他就是一颗完全二叉树了。

这就是二叉树的层序遍历了。

递归遍历和层序遍历都用在什么地方呢?

递归更适合深度优先的遍历,而层序适合广度优先的遍历,但是我们在树里面一般是不谈深度优先遍历(DFS)和广度优先遍历(BFS)的,这两个概念一般用在图和二维数组中。从宽泛的角度来说,树的前中后序遍历只考虑遍历顺序的话都算是深度优先,先往深走到无路可走再退回来走其他的分支,这之中只有前序遍历算是严格意义上的深度优先,遍历顺序和访问顺序都符合深度优先。

层序就是广度优先遍历。

在之前我们再扫雷中用递归来展开一片的功能,其实也可以换成广度优先来遍历,队列中存的数据就是坐标。

扩展思考:

二叉树如果给了前序序列和中序序列能否确定唯一的一棵树?答案是能。

为什么呢?因为前序序列是能够确定树与子树的根节点的,而中序序列则能通过根节点来分出左右子树的区间,就拿下面这个来举例

前序的第一个元素是1 ,说明 1 是整棵树的根节点,而在中序序列中 ,1 的左边的元素就是左子树,1 的右边就是右子树

这时候再看前序的第二个元素,他在左子树中,说明 2 是左子树的根节点,这时在中序序列中又能以 2 划分出他的左右子树了。这时候左右子树都只有一个元素,则1 的左子树就已经确定了

再看前序序列 1 的左子树之后就是1的右子树的根节点,也就是7 的后面,所以右子树的根节点就是4,再看中序序列,以4 划分他的左右子树,这时候他的左子树就是 5 ,右子树就是空了,这就唯一确定一棵二叉树了。

同理,后序序列和中序序列也能唯一确定一棵二叉树,因为后序序列也能表示出根节点,而在中序序列中能够划分出左右子树。

而前序序列和后序序列就不能唯一确定一棵二叉树了,因为前序和后序都只能确定根节点,而无法确定子树。

这就是二叉树的基础知识了,后续在C++中会继续更新更高阶的二叉树以及更多数据结构

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

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

相关文章

用html写一个有趣的鬼魂动画

<!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>一个有趣的鬼魂动画</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.m…

性能测试-数据库优化一(配置参数)

性能测试-数据库优化 数据库的优化方向 1、数据库是安装在操作系统中&#xff0c;是非常追求磁盘的稳定性。MySQL的库是磁盘文件夹&#xff0c;表是磁盘文件所以数据库的优化&#xff1a; 磁盘的性能 1、磁盘的速度&#xff0c;减少同时读写&#xff0c;考虑读写分离&#x…

【Spring Boot】深入解密Spring Boot日志:最佳实践与策略解析

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring Boot】深入解密Spring Boot日志&#xff1a;最佳实践与策略解析 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 Spring Boot 日志一. 日志的概念?…

上位机图像处理和嵌入式模块部署(智能硬件的开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前&#xff0c;用上位机软件虽然可以部署项目&#xff0c;但是它本身有自己的缺点&#xff0c;那就是稳定性差、价格贵。稳定性这部分&#xff0…

c++修炼之路之vector模拟实现

目录 前言&#xff1a; 一&#xff1a;在STL的开源代码中的vector的实现 二&#xff1a;模拟实现 1.数据成员size()capacity() 2.resizereserve 3.构造函数析构函数赋值重载 4.迭代器[] 5.push_backinserterase迭代器失效问题 三&#xff1a;测试用例和全部代码 接下…

【Proteus】51单片机对步进电机的控制

步进电机&#xff1a;将电脉冲信号转变为角位移或线位移的开换控制系统。在非超载的情况下&#xff0c;电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数&#xff0c;而不受负载变化的影响&#xff0c;即给电机加一个脉冲信号&#xff0c;电机则转过一个步距角。 特点&am…

2024蓝桥A组A题

艺术与篮球&#xff08;蓝桥&#xff09; 问题描述格式输入格式输出评测用例规模与约定解析参考程序难度等级 问题描述 格式输入 无 格式输出 一个整数 评测用例规模与约定 无 解析 模拟就好从20000101-20240413每一天计算笔画数是否大于50然后天数&#xff1b; 记得判断平…

【STM32】西南交大嵌入式系统设计实验:环境配置

把走过的坑记录一下&#xff0c;希望后来人避坑 No ST-Link device detected.问题解决 如果跟着指导书出现这个问题&#xff1a; 直接跳过这一步不用再更新固件&#xff0c;后面直接创建项目写程序就行了。 在keil里配置成用DAP_link即可。 详细的可以看这篇文章&#xff1a…

如何进行宏观经济预测

理性预期经济学提出了理性预期的概念&#xff0c;强调政府在制定各种宏观经济政策时&#xff0c;要考虑到各行为主体预期对政策实施有效性的影响&#xff0c;积极促成公众理性预期的形成&#xff0c;从而更好地实现宏观调控的目标。政府统计要深入开展统计分析预测研究&#xf…

实战项目——智慧社区(四)之 系统管理

1、用户管理 提供查询和搜索用户、根据id查询用户信息、添加用户、修改用户、删除用户的功能 界面 添加用户 修改用户信息 2、角色管理 提供查询和搜索角色、根据id查询角色信息、添加角色、修改角色、删除角色的功能 界面 添加角色 修改角色 3、菜单管理 提供查询和搜索菜…

JavaScript(七)-高级技巧篇

文章目录 深浅拷贝浅拷贝深拷贝 异常处理thorw抛异常try/catch捕获异常debugger 处理thisthis指向改变this 性能优化防抖lodash实现防抖手写防抖函数 节流 - throttle 深浅拷贝 浅拷贝 深拷贝 深拷贝有三种方式 通过递归实现深拷贝 一定先写数组再写对象 lodash/cloneDeep …

Gradle 在 Spring 中的使用-ApiHug准备-工具篇-006

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace ApiHug …

Linux(Ubuntu) 查看并删除使用【dpkg】安装的软件

目录 ■前言 ■查看安装的软件 ■删除安装的软件 正常删除&#xff08;dpkg -r xxxxName&#xff09; 问题解决&#xff1a;use --purge to remove them too ■其他调查信息 命令 图片1 图片2 图片3 ■前言 安装Mysql8.3失败 我的服务器-CSDN博客 ■查看安装的软…

Linux的学习之路:10、进程(2)

摘要 本章主要是说一下fork的一些用法、进程状态、优先级和环境变量。 目录 摘要 一、fork 1、fork的基本用法 2、分流 二、进程状态 三、优先级 四、环境变量 1、常见环境变量 2、和环境变量相关的命令 3、通过代码如何获取环境变量 五、导图 一、fork 1、fork…

`Object3D.lookAt()` 是 Three.js 中 `Object3D` 类的一个方法,用于让一个对象朝向指定的目标点。

demo案例 方法签名 object.lookAt(target)参数 target&#xff1a;目标点的坐标&#xff0c;可以是一个 Three.js 的 Vector3 对象&#xff0c;也可以是包含 x、y、z 坐标的普通 JavaScript 对象。 返回值 该方法没有返回值。 功能 该方法将当前对象的 z 轴指向目标点&am…

GitHub repository - Pulse - Contributors - Network

GitHub repository - Pulse - Contributors - Network 1. Pulse2. Contributors3. NetworkReferences 1. Pulse 显示该仓库最近的活动信息。该仓库中的软件是无人问津&#xff0c;还是在火热地开发之中&#xff0c;从这里可以一目了然。 2. Contributors 显示对该仓库进行过…

【Golang学习笔记】从零开始搭建一个Web框架(四)

文章目录 模板渲染静态文件支持HTML 模板渲染 错误恢复完结撒花~~ 前情提示&#xff1a; 【Golang学习笔记】从零开始搭建一个Web框架&#xff08;一&#xff09;-CSDN博客 【Golang学习笔记】从零开始搭建一个Web框架&#xff08;二&#xff09;-CSDN博客 【Golang学习笔记】从…

【C++成长记】C++入门 | 类和对象(上) |类的作用域、类的实例化、类的对象大小的计算、类成员函数的this指针

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;C❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的作用域 二、类的实例化 三、类对象模型 四、this指针 1、this指针的引出 2 this指针的特…

Linux学习笔记之9(消息队列)

Linux learning 1、引言2、创建一个消息队列3、发送和接受消息3.1、发送消息3.1、接收消息 4、删除一个消息队列5、例程 1、引言 消息队列&#xff08;message queue&#xff09;也是进程之间通信的一种方式&#xff0c;相比于共享内存的通信方式&#xff0c;消息队列也有类型…

如何更好地理解 Vue 3 watch 侦听器的用法

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…