【初阶数据结构与算法】二叉树链式结构的定义与实现万字笔记(附源码)

news2024/12/26 23:20:09

在这里插入图片描述

文章目录

  • 一、二叉树链式结构的定义
  • 二、二叉树链式结构功能的基本实现
    • 1.链式二叉树的手动创建
    • 2.链式二叉树的前中后序遍历
      • 前序遍历
      • 中序遍历
      • 后序遍历
    • 3.链式二叉树节点的个数
    • 4.链式二叉树叶子节点的个数
    • 5.链式二叉树的高度/深度
    • 6.链式二叉树第k层节点的个数
    • 7.链式二叉树的查找
    • 8.链式二叉树层序遍历
    • 9.判断链式二叉树是否为完全二叉树
    • 10.链式二叉树的销毁
  • 三、源码

一、二叉树链式结构的定义

   链式二叉树就是⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系,通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址,数据域则是当前节点存放的数据,其结构如下:

typedef int BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

   当我们使用链式结构来表示二叉树时,这个二叉树并不会有很多限制,它对节点的插入删除等操作要求并不高,所以我们后面会手动来创建链式二叉树,本文重要的不是创建二叉树的方法,而是二叉树各种功能的实现
   那么什么时候我们不能手动创建二叉树了呢?这个就要等我们后面在C++部分讲到二叉搜索树才能揭晓答案,二叉搜索树就只能通过特定的函数创建,而不能手动创建了,接着我们就再次回到我们今天的主题—二叉树链式结构功能的基本实现

二、二叉树链式结构功能的基本实现

1.链式二叉树的手动创建

   链式二叉树的手动创建很简单,就是创建一堆的节点,然后将它们互相连接起来,这里给出一个二叉树的图片,我们按照图片的结构手动创建一颗链式二叉树,如下:
在这里插入图片描述

   为了方便我们直接操作,我们先写一个申请二叉树节点的函数,可以通过传节点的值来帮我们申请一个具有该值的节点,如下:

//申请节点
BTNode* BTBuyNode(BTDataType x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("mallc");
		return NULL;
	}
	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}

   接下来我们就挨个的申请节点,然后使用左右指针将这些节点串联起来,如下:

//手动创建一颗链式二叉树
//返回根节点
BTNode* CreateBinaryTree()
{
	BTNode* nodeA = BTBuyNode('A');
	BTNode* nodeB = BTBuyNode('B');
	BTNode* nodeC = BTBuyNode('C');
	BTNode* nodeD = BTBuyNode('D');
	BTNode* nodeE = BTBuyNode('E');
	BTNode* nodeF = BTBuyNode('F');
	BTNode* nodeG = BTBuyNode('G');
	BTNode* nodeH = BTBuyNode('H');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeB->right = nodeE;
	nodeC->left = nodeF;
	nodeC->right = nodeG;
	nodeD->left = nodeH;

	return nodeA;
}

   这样我们手动创建一颗链式二叉树,返回了它的根节点,接下来我们就可以开始对它进行操作了,后面的内容才是今天的重点

2.链式二叉树的前中后序遍历

   链式二叉树的这个结构非常适合递归,它每一次函数调用创建的栈帧就可以看做我们要递归的一个节点,在链式二叉树的接口实现中,我们一定要有抽象能力,当然,我们后面也会给大家画图来解释,这里不再多说
   我们今天第一个重点就是完成链式二叉树的遍历,如果不能实现二叉树的遍历,那么我们连里面存放了什么数据都不知道,所以接下来我们就开始介绍二叉树的前中后序遍历
   首先我们来解释一下前中后序遍历,前序遍历就是根左右,也就是先打印当前子树中的根,再把左右孩子当成另外两颗子树,继续递归执行根左右的思想
   中序遍历就是左根右,也就是先递归打印当前根的左孩子这颗子树,然后再打印根节点,最后再递归打印当前根的右孩子这颗子树
   而后序遍历就是左右根,跟上面两种例子是同样的思想,就是先递归打印当前根的左子树,再递归打印当前根的右子树,最后再打印根
   听到这里你是不是懵了呢?这是很正常的,光听概念是很难学会递归的,我们要在实例中去应用才能掌握,所以下面实现前中后序遍历的过程和思想才是我们要重点掌握的,而不是这里前中后序遍历的概念,我们来一起学习吧!

前序遍历

   前序遍历就是左右根,打印当前子树中的根,左右孩子又分别成一颗子树,于是左右孩子又再次执行上面的根左右的遍历思想,最终这样递归下去就可以实现二叉树的前序遍历
   如果不理解可以先不管,现在我们先把代码写出来,等一下根据代码我们来画图才能将它说清楚,这也是一种学习方法,这种抽象的算法先看代码再理解,否则很难想到,之后我们才能根据理解写出类似的算法,如下:

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		//走到空节点开始返回
		return;
	}
	//打印根节点
	printf("%c ", root->data);
	//递归左孩子这颗子树
	PreOrder(root->left);
	//递归右孩子这颗子树
	PreOrder(root->right);
}

   接着我们来画图按照这段代码的逻辑来走一遍我们的前序遍历,要仔细理解这里的图,后面才能解决我们的中后序遍历,如图:
在这里插入图片描述
   那么我们分析完前序遍历之后,我们来看看我们前序遍历代码能不能实现我们画图所诉的打印,如图:
在这里插入图片描述
   可以看到代码的运行结果和我们分析的一模一样,这就是我们的前序遍历,是我们第一次接触到递归的暴力美学,所以我们讲的很仔细,要仔细理解
   后面的中后序遍历基本上都是一个思想,所以后面的中后序遍历就不再画图了,可以自行画图来理解,思路大致相同

中序遍历

   中序遍历就是先递归打印左孩子这颗子树,然后打印根节点,最后再递归打印右孩子这颗子树,和前序遍历的思想都很接近,只是说根节点的打印不同
   中序遍历的实现和前序遍历的思想差不多,也是通过递归实现,可以自己尝试画画中序遍历的图,看看自己能不能写出来中序遍历,再来看后面的代码,这里就直接给出中序遍历的实现代码了:

//中序遍历:左根右
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		//走到空节点开始返回
		return;
	}
	//递归左孩子这颗子树
	InOrder(root->left);
	//打印根节点
	printf("%c ", root->data);
	//递归右孩子这颗子树
	InOrder(root->right);
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到中序遍历和前序遍历代码的实现只有一点点不同,就是打印根节点的位置不同,但是其实结果已经发生了超级多的变化,如果感兴趣可以自行画画图理解,思路和前序遍历差不多

后序遍历

   后序遍历就是先递归打印当前根的左子树,再递归打印当前根的右子树,最后再打印根,跟上面前中序遍历的思路很像
   而现在我们有了前中序遍历思想的启迪,接下来我们实现后序遍历就很简单了,很多同学可能都能直接猜到代码该怎么写,那么在这之前,还是希望大家先画图分析一下,写出代码,再来看看自己写的对不对,代码如下:

//后序遍历:左右根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		//走到空节点开始返回
		return;
	}
	//递归左孩子这颗子树
	PostOrder(root->left);
	//递归右孩子这颗子树
	PostOrder(root->right);
	//打印根节点
	printf("%c ", root->data);
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到后序遍历和前中序遍历代码的实现基本上也是差别很小,也是打印根节点的位置不同,但是其实也发生了很多的变化,希望可以自己画图理解理解,这里就不再画了,接着学习链式二叉树的下一个接口

3.链式二叉树节点的个数

   在上面我们稍微体会到了一点递归的暴力美学,从现在开始,就是真正开始爽的地方,我们就根据之前前中后序的递归思想实现这些接口,代码量都很小,但是却可以实现我们的要求,我们一起来学习
   求二叉树节点的个数不就是求当前根节点的个数,再加上左右子树节点的个数吗?所以我们可以利用递归的思想,当前根节点算1,然后加上左右子树节点的个数
   当然,我们一旦要递归就要考虑返回条件,这里的返回条件就是根节点为空,如果根节点为空了,就直接返回0,于是代码就水灵灵出来了,如下:

//二叉树节点的个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftsize = BinaryTreeSize(root->left);
	int rightsize = BinaryTreeSize(root->right);

	return 1 + leftsize + rightsize;
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到成功算出了我们二叉树中所有节点的个数

4.链式二叉树叶子节点的个数

   求整颗二叉树叶子节点个数可以猜分为,左子树叶子节点个数加右子树叶子节点个数,这样就可以继续使用我们的递归思想了
   求叶子节点个数相当于比前面要求严格一些,不是只要根节点不为空就要算,而是根节点既不为空,并且这个根节点的左右孩子都为空,这样才算一个
   所以我们最后总结为,如果根节点为空直接返回,如果不为空并且左右孩子都为空就返回1,然后我们整颗二叉树叶子节点个数这个问题就可以拆成,左子树叶子节点个数加右子树叶子节点个数,如下:

//二叉树叶子节点的个数
int BinaryTreeLeaveSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	int leftsize = BinaryTreeLeaveSize(root->left);
	int rightsize = BinaryTreeLeaveSize(root->right);

	return leftsize + rightsize;
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到得出了我们想要的结果

5.链式二叉树的高度/深度

   在解决这个问题之前,我们要知道如何判断二叉树的高度,就是给我们二叉树,我们要能判断二叉树的高度,如图:
在这里插入图片描述
   我们肉眼可能一下就看出来,二叉树1的高度为4,二叉树2的高度为3,那么它们是怎么来的呢?其实二叉树的高度就是它左右子树中最高的那个,最后再加上根节点,所以我们就还是可以利用递归的算法思想,如下:

//二叉树的高度/深度
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDepth = 1 + BinaryTreeDepth(root->left);
	int rightDepth = 1 + BinaryTreeDepth(root->right);

	return leftDepth > rightDepth ? leftDepth : rightDepth;
}

   我们来看看代码运行结果:

在这里插入图片描述
   可以看到得出了我们想要的结果

6.链式二叉树第k层节点的个数

   这个需求就和上面求叶子节点个数类似,求叶子节点个数是如果左右孩子为空返回1,这里就是如果当前节点在k层就返回1
   那么如何判断这个节点是否在第k层呢?首先我们还是使用递归的算法思想,将求二叉树第k层节点的个数,转化为求二叉树左右子树的第k层节点个数的和
   具体判断方法就是,我们每递归一层就让k-1,当k变成了1,那么就说明当前节点就在第k层,这个可以自行尝试,这里就不再多说
   那么有了这个思路我们就可以直接写代码了,如下:

//⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k-1) + BinaryTreeLevelKSize(root->right, k-1);
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到程序帮我们算出了对应层次节点的个数

7.链式二叉树的查找

   这个功能我们还是可以使用递归的思想解决,我们在二叉树中查找某个节点,其实就是看当前根节点是不是要查找的节点,如果是就直接返回
   如果不是就去对应的左子树里面找,如果左子树里面找到了,就直接返回,如果左子树也没有找到,就去右子树中找,如果找到了就直接返回,如果最后左右子树都没有找到就直接返回空,如下:

//查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* left = BinaryTreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	BTNode* right = BinaryTreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	return NULL;
}

   我们来测试一下代码能否实现我们的查找功能,首先我们来找找二叉树中有的节点,如图:
在这里插入图片描述
   可以看到程序很好地完成了任务,接下来我们再测试一下如果二叉树没有这个节点会不会被找到,如图:
在这里插入图片描述
   可以看到程序最后没有找到,符合我们的预期,那么我们的查找功能就实现完毕啦

8.链式二叉树层序遍历

   接下来的二叉树的层序遍历和判断是否为完全二叉树相对于之前的结构就会难一些,因为这两个接口不能使用递归完成
   首先我们来介绍一下层序遍历,层序遍历就是按层次来访问各个节点,我们举一个例子:
在这里插入图片描述
   在这颗二叉树中,如果我们按照层序遍历打印节点,那么最后结果一定是ABCDEFGH,每个节点都按照对应的层次依次打印,这就是层序遍历
   那么怎么实现层序遍历呢?这里我也不再卖关子了,实现链式二叉树的层序遍历要使用我们之前学过的一个数据结构—队列,接下来我们就先把队列加入我们的项目
   我们可以直接将队列的头文件和实现文件添加到当前目录下,如果不会的话也可以麻烦一点去把之前写过的队列内容拷贝到我们的项目里,这里就不再多说了
   在讲解原理前,我们先把队列里面的数据类型改成我们的二叉树节点指针类型,这样才能让我们的队列存放我们的二叉树节点
   接着我们就来讲解如何使用队列实现层序遍历,方法就是从先让根节点入队列,随后我们就创建一个循环,只要队列不为空我们就循环执行以下的操作
   我们取出当前队列的头节点,然后打印里面的内容,随后将当前这个节点的左右孩子入队列,让当前节点出队列,当然,我们要注意一点的就是,如果当前节点左孩子或者右孩子为空,那么就没有必要入队列了,所以要进行一下判断
   以上就是我们层序遍历的所有思路,可以自行画图理解一下,这里我们直接根据上面的思路来写代码,如下:


//二叉树层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%c ", front->data);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到程序确实帮我们按层次打印了各个节点,那么我们的层序遍历就实现完毕啦

9.判断链式二叉树是否为完全二叉树

   首先我们自己要知道如何判断一颗二叉树是不是完全二叉树,判断方法就是,保证除了最后一层以外,其它层次满了,并且保证最后一层的节点是左右依次排列的,这样才是一颗完全二叉树
   那么我们怎么用程序来判断呢?这里我们需要用到上面我们层序遍历的思想,不同的是层序遍历时不把空节点入队列,而我们这里要把空节点也入队列
   因为我们可以将判断一颗二叉树是否为完全二叉树这个问题转化为,层序遍历时遍历到最后一个节点后,接下来队列中的节点只能是空,换句话说,如果在层序遍历时碰到了空节点,那么队列后面就都只能为空,这样才是完全二叉树
   如果在如果在层序遍历时碰到了空节点,但是队列后面有非空节点,说明这颗树就不是完全二叉树,不信的话可以自己画画图,这里就不再画了,我们直接根据这个思路来写代码,判断层序遍历碰到空节点后,队列后面的节点是否都为空,如下:

//是否为完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
		QueuePop(&q);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front)
		{
			return false;
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;
}

   我们来看看代码运行结果:
在这里插入图片描述
   可以看到程序成功帮我们判断出当前二叉树是一颗完全二叉树,也可以自行创建一颗非完全二叉树试试,也没有问题

10.链式二叉树的销毁

   由于我们链式二叉树的节点都是动态申请的,所以我们要将它们进行释放,否则会造成内存泄漏,要一个一个地销毁节点,说明我们要遍历整颗二叉树,我们上面学过的遍历方法就起到作用了
   我们就可以直接选择后序遍历来释放节点,先释放左右子树,最后释放掉我们的根节点,并且将根节点置空,如下:

//二叉树的销毁
void BinaryTreeDestroy(BTNode** proot)
{
	if (*(proot) == NULL)
	{
		return;
	}
	BinaryTreeDestroy(&(*proot)->left);
	BinaryTreeDestroy(&(*proot)->right);
	free(*proot);
	*proot = NULL;
}

三、源码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "Queue.h"

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

//前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

//二叉树叶子节点个数
int BinaryTreeLeaveSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeaveSize(root->left) + BinaryTreeLeaveSize(root->right);
}

//⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k-1) + BinaryTreeLevelKSize(root->right, k-1);
}

//二叉树高度
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDepth = 1 + BinaryTreeDepth(root->left);
	int rightDepth = 1 + BinaryTreeDepth(root->right);
	return leftDepth > rightDepth ? leftDepth : rightDepth;
}

//查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* left = BinaryTreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	BTNode* right = BinaryTreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	return NULL;
}

//二叉树的销毁
void BinaryTreeDestroy(BTNode** proot)
{
	if (*(proot) == NULL)
	{
		return;
	}
	BinaryTreeDestroy(&(*proot)->left);
	BinaryTreeDestroy(&(*proot)->right);
	free(*proot);
	*proot = NULL;
}

//二叉树层序遍历
void LevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%c ", front->data);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
}

//是否为完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
		QueuePop(&q);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front)
		{
			return false;
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return true;
}

   那么今天二叉树链式结构的定义与实现就结束啦,后面我们讲完链式二叉树的一些OJ题后初阶数据结构就差不多结束了,可以进入我们的排序算法了,敬请期待吧!
   bye~

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

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

相关文章

前端框架的选择与反思:在简约与复杂之间寻找平衡

在当今互联网时代&#xff0c;前端开发已经成为web应用构建中不可或缺的一环。从最初的静态HTML页面&#xff0c;到如今复杂的单页应用&#xff08;SPA&#xff09;&#xff0c;前端技术的发展让我们见证了Web应用的蓬勃发展。然而&#xff0c;伴随着技术的进步&#xff0c;一个…

SABO-CNN-BiGRU-Attention减法优化器优化卷积神经网络结合双向门控循环单元时间序列预测,含优化前后对比

SABO-CNN-BiGRU-Attention减法优化器优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比 目录 SABO-CNN-BiGRU-Attention减法优化器优化卷积神经网络结合双向门控循环单元时间序列预测&#xff0c;含优化前后对比预测效果基本介绍模型描述程序设计参…

SpringBoot期末知识点大全

一、学什么 IoC AOP&#xff1a;面向切面编程。 事物处理 整合MyBatis Spring框架思想&#xff01; 二、核心概念 问题&#xff1a;类之间互相调用/实现&#xff0c;导致代码耦合度高。 解决&#xff1a;使用对象时&#xff0c;程序中不主动new对象&#xff0c;转换为由外部提…

撰写技术文档的关键步骤和核心要点

编写项目的技术文档是一个重要且细致的任务&#xff0c;它不仅有助于项目的当前开发团队理解系统的结构和工作原理&#xff0c;还为未来的维护和扩展提供了宝贵的参考资料。以下是撰写技术文档时应遵循的几个关键步骤和组成部分&#xff1a; 1. 概述 项目简介&#xff1a;简要…

【人工智能】Transformers之Pipeline(二十八):视觉问答(visual-question-answering)

​​​​​​​ 目录 一、引言 二、视觉问答&#xff08;visual-question-answering&#xff09; 2.1 概述 2.2 dandelin/ViLT 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 2.3.3 pipeline对象返回参数 2.4 pipeline实战 2.5 模型…

【Vue3】详解Vue3的ref与reactive:两者的区别与使用场景

文章目录 引言Moss前沿AIVue 3响应式系统概述ref与reactive的基础概念ref与reactive的区别1. 数据类型2. 访问方式3. 响应式追踪机制4. 可变性5. 使用场景表格对比 ref与reactive的使用场景1. 选择ref的场景2. 选择reactive的场景 性能分析与优化建议1. 响应式系统的性能优势2.…

8. 一分钟读懂“代理模式”

8.1 模式介绍 代理模式是一种结构型设计模式&#xff0c;它通过提供一个代理对象来替代对另一个对象&#xff08;真实对象&#xff09;的访问。代理对象与真实对象实现相同的接口&#xff0c;并通过代理类对真实对象的访问进行控制&#xff0c;可以在调用前后执行附加操作&…

网络原理(HPPT/HTTPS)

应用层&#xff08;重点&#xff09; HTTP协议 HTTP 是⼀个⽂本格式的协议. 可以通过 Chrome 开发者⼯具或者 Fiddler 抓包, 分析 HTTP 请求/响应的细节. Fiddler 抓包 左侧窗⼝显⽰了所有的 HTTP请求/响应, 可以选中某个请求查看详情. • 右侧上⽅显⽰了 HTTP 请求的报⽂内容…

随时随地掌控数据:如何使用手机APP远程访问飞牛云NAS

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

计算机毕业设计Python+Spark医生推荐系统 医生门诊预测系统 医生数据分析 医生可视化 医疗数据分析 医生爬虫 大数据毕业设计 机器学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

golang实现单例日志对象

原文地址&#xff1a;golang实现单例日志对象 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 介绍 golang有很多日志包&#xff0c;通过设置和修改都能实现日志轮转和自定义日志格式。例如&#xff1a;log、zap、golog、slog、log4go 等等。 …

SpringBoot该怎么使用Neo4j - 优化篇

文章目录 前言实体工具使用 前言 上一篇中&#xff0c;我们的Cypher都用的是字符串&#xff0c;字符串拼接简单&#xff0c;但存在写错的风险&#xff0c;对于一些比较懒的开发者&#xff0c;甚至觉得之间写字符串还更自在快速&#xff0c;也确实&#xff0c;但如果在后期需要…

【Calibre-Web】Calibre-Web服务器安装详细步骤(个人搭建自用的电子书网站,docker-compose安装)

文章目录 一、Calibre-Web和Calibre的区别是什么&#xff1f;使用场景分别是什么&#xff1f;二、服务器安装docker和docker-compose三、服务器安装Calibre-Web步骤1、安装完成后的目录结构2、安装步骤3、初始配置4、启动上传 四、安装Calibre五、docker-compose常用命令 最近想…

easyexcel 导出日期格式化

1.旧版本 在新的版本中formate已经被打上废弃标记。那么不推荐使用这种方式。 2.推荐方式 推荐使用另外一种方式【 Converter 】代码如下&#xff0c;例如需要格式化到毫秒【yyyy-MM-dd HH:mm:ss SSS】级别 创建一个公共Converter import com.alibaba.excel.converters.Conv…

ABAP - 系统集成之SAP的数据同步到OA(泛微E9)服务器数据库

需求背景 项目经理说每次OA下单都需要调用一次SAP的接口获取数据&#xff0c;导致效率太慢了&#xff0c;能否把SAP的数据保存到OA的数据库表里&#xff0c;这样OA可以直接从数据库表里获取数据效率快很多。思来想去&#xff0c;提供了两个方案。 在集群SAP节点下增加一个SQL S…

40分钟学 Go 语言高并发:【实战】分布式缓存系统

【实战课程】分布式缓存系统 一、整体架构设计 首先&#xff0c;让我们通过架构图了解分布式缓存系统的整体设计&#xff1a; 核心组件 组件名称功能描述技术选型负载均衡层请求分发、节点选择一致性哈希缓存节点数据存储、过期处理内存存储 持久化同步机制节点间数据同步…

w~视觉~合集27

我自己的原文哦~ https://blog.51cto.com/whaosoft/12715639 #视频AIGC~论文 1、Pix2Video: Video Editing using Image Diffusion 基于大规模图像库训练的图像扩散模型已成为质量和多样性方面最为通用的图像生成模型。它们支持反转真实图像和条件生成&#xff08;例如&…

MYSQL中的增删改查操作(如果想知道MYSQL中有关增删改查操作的知识,那么只看这一篇就足够了!)

前言&#xff1a;在 MySQL 中&#xff0c;增、删、改、查&#xff08;CRUD&#xff09;操作是基本的数据库操作&#xff0c;增操作&#xff08;INSERT&#xff09;用于插入数据&#xff0c;删操作&#xff08;DELETE&#xff09;用于删除数据&#xff0c;改操作&#xff08;UPD…

Ansible的yum和saltstack的哪个功能相似

Ansible的yum和saltstack的哪个功能相似 在 Ansible 和 SaltStack 中&#xff0c;Ansible 的 yum 模块 和 SaltStack 的 pkg 模块 功能相似。它们都用于管理软件包&#xff0c;支持安装、升级、删除和查询等操作。 Ansible 的 yum 模块 用途&#xff1a; 专门用于基于 Red Hat …

在做题中学习(76):颜色分类

解法&#xff1a;三指针 思路&#xff1a;用三个指针&#xff0c;把数组划分为三个区域&#xff1a; for循环遍历数组&#xff0c;i遍历数组&#xff0c;left是0区间的末尾&#xff0c;right是2区间的开头&#xff0c;0 1 2区间成功被划分 而上面的图画是最终实现的图样&…