数据结构——二叉树的基本操作及进阶操作

前言

介绍

 🍃数据结构专区:数据结构

参考

该部分知识参考于《数据结构(C语言版 第2版)》116 ~ 122页 及 《数据结构教程》201 ~ 213页

重点

树的基本实现并不难,重点在于对递归的理解才是树这部分知识带来的最大收获,因为树的逻辑结构就保证了使用递归思路来解决是最优的算法

由于树的基础知识太多,这里只进行简单解释,本篇主要是对基本操作代码的解释,后序的文章会跟进树的相关知识和性质的

🌈每一个清晨,都是世界对你说的最温柔的早安:ૢ(≧▽≦)و✨


目录

前言

1、二叉树的基础知识

1.1、定义与特点

1.2、基本形态与类型

1.3、性质与存储

1.4、遍历方法

2、链表树的操作

2.1 宏定义及结构体定义

2.2 前中后序遍历法

2.3 初始化树

2.4 创建新节点

2.5 创建树

2.6 销毁树

2.7 判空

2.8 求树的深度

2.9 根据值查找节点

2.10 求树根

2.11 求结点总数

2.12 输出叶子节点

2.13 计算值为x的节点所在的层数

2.14 计算第k层的节点数

2.15 复制二叉树

2.16 判断两棵树是否相似

2.17 查找某节点的双亲节点

2.18 其他一些不重要函数

2.19 整体代码(含测试代码)

结语


1、二叉树的基础知识

1.1、定义与特点

  • 定义:二叉树(Binary Tree)是树形结构的一个重要类型,是一种每个节点最多有两个子树(左子树和右子树)的有序树。

  • 特点

    • 每个节点最多只能有两棵子树,且有左右之分,次序不能颠倒。
    • 二叉树可以是空树,或者由根节点和左、右子树组成,其中左子树和右子树也同样是二叉树。

1.2、基本形态与类型

  • 基本形态

    • 空二叉树:没有任何节点的二叉树。
    • 只有一个根节点的二叉树。
    • 只有左子树或只有右子树的二叉树。
    • 完全二叉树:深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中编号从1到n的节点一一对应时,称为完全二叉树。
    • 满二叉树:如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,则这棵二叉树为满二叉树。
  • 特殊类型

    • 二叉查找树(Binary Search Tree):左子树上的所有节点值均小于根节点值,右子树上的所有节点值均大于根节点值。
    • 平衡二叉树(如AVL树):左右子树都是平衡二叉树,且所有节点左、右子树深度之差的绝对值不大于1。
    • 红黑树:除了具备二叉查找树的特性外,还有额外的特性以保持树的自平衡。
    • 哈夫曼树(Huffman Tree):带权路径长度最短的树,常用于通信中的压缩编码。

1.3、性质与存储

  • 性质

    • 二叉树的第i层上至多有2^(i-1)个节点。
    • 深度为h的二叉树中至多含有2^h-1个节点。
    • 在任意一棵二叉树中,若叶子节点数为n0,度为2的节点数为n2,则必有n0=n2+1。
  • 存储方式

    • 顺序存储:使用数组来存储二叉树的节点,通常适用于完全二叉树。
    • 链式存储:使用链表来存储二叉树的节点,每个节点包含数据域和指向左、右子节点的指针。

1.4、遍历方法

  • 遍历:是对树的一种最基本的运算,指按一定的规则和顺序走遍二叉树的所有节点,使每一个节点都被访问一次,而且只被访问一次。

  • 遍历方法

    • 先序遍历(Preorder Traversal):访问根节点,然后遍历左子树,最后遍历右子树。
    • 中序遍历(Inorder Traversal):遍历左子树,访问根节点,然后遍历右子树。中序遍历二叉排序树可得到一个关键字的有序序列。
    • 后序遍历(Postorder Traversal):遍历左子树,遍历右子树,最后访问根节点。

2、链表树的操作

一、由于二叉树的顺序存储结构具有顺序存储结构的固有缺陷,使得二叉树的插入、删除等运算十分麻烦,所以对于一般二叉树通常采用链式存储方式

二、这里我采用左右孩子的存储方法

三、下面我会先把各函数代码进行简单介绍并粘贴出来,若有不懂的可以向下翻找会有函数的详细介绍

2.1 宏定义及结构体定义

#include<stdio.h>
#include<iostream>
using namespace std;

typedef char TElemType;
typedef struct BiTNode {
	TElemType data;
	struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;

2.2 前中后序遍历法

//前序递归遍历法
void PreOrder(BiTree T)
{
	if (T != NULL)
	{
		cout << T->data << " ";
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}

//中序递归遍历法
void InOrder(BiTree T)
{
	if (T != NULL)
	{
		InOrder(T->lchild);
		cout << T->data << " ";
		InOrder(T->rchild);
	}
}

//后序递归遍历法
void PostOrder(BiTree T)
{
	if (T != NULL)
	{
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		cout << T->data << " ";
	}
}

2.3 初始化树

//初始化树
void InitTree(BiTree& T)
{
	T = NULL;
}

2.4 创建新节点

//创建新节点
BiTNode* CreatNewNode(TElemType value)
{
	BiTNode* node = new BiTNode;
	//等价于
	//BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode));
	node->data = value;
	node->lchild = NULL;
	node->rchild = NULL;
	return node;
}

2.5 创建树

//创建树
void CreateTree(BiTree& T, char* definition)
{
	static int i = 0;  //静态变量,每次递归进入过程中都会i++
	char ch = definition[i++];
	if (ch == '#')
	{
		T = NULL;
	}
	else
	{
		T = CreatNewNode(ch);
		CreateTree(T->lchild, definition);
		CreateTree(T->rchild, definition);
	}
}

2.6 销毁树

//销毁树
void DestroyTree(BiTree& T)
{
	if (T != NULL)
	{
		DestroyTree(T->lchild);
		DestroyTree(T->rchild);
		free(T);
		T = NULL;
	}
}

2.7 判空

//判空
int TreeEmpty(BiTree T)
{
	if (T == NULL)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

2.8 求树的深度

//求树的深度
int TreeDepth(BiTree T)
{
	//定义左右子树的高度,作为最后返回时的比较值
	int lchildh = 0, rchildh = 0;
	if (T == NULL)
	{
		return 0;
	}
	else
	{
		lchildh = TreeDepth(T->lchild);
		rchildh = TreeDepth(T->rchild);
		return lchildh > rchildh ? lchildh + 1 : rchildh + 1;
	}
}

2.9 根据值查找节点

//查找某个节点
BiTNode* FindNode(BiTree T, TElemType x)
{
	BiTNode* p;//作为遍历
	if (T == NULL)
	{
		return NULL;
	}
	if (T->data == x)
	{
		return T;
	}
	
	//左递归查找
	p = FindNode(T->lchild, x);
	if (p != NULL)
	{
		return p;
	}
	else
	{
		//否则进行右递归查找
		return FindNode(T->rchild, x);
	}
}

2.10 求树根

//求树根
BiTNode* Root(BiTree T)
{
	if (T == NULL)
	{
		printf("ERROR:Tree is NULL!");
	}
	return T;
}

2.11 求结点总数

//求结点总数
int Nodes(BiTree T)
{
	if (T == NULL)
	{
		return 0;
	}
	return Nodes(T->lchild) + Nodes(T->rchild) + 1;
}

2.12 输出叶子节点

//输出叶子结点
void DispLeaf(BiTree T)
{
	if (T != NULL)
	{
		if (T->lchild == NULL && T->rchild == NULL)
		{
			cout << T->data << "  ";   //访问叶子节点
		}
		DispLeaf(T->lchild);  //输出左子树中的叶子节点
		DispLeaf(T->rchild);  //输出右子树中的叶子节点
	}
}

2.13 计算值为x的节点所在的层数

//计算某个节点值为x的层次(若在根结点 h = 1,树为空h = 0)
int Level(BiTree T, TElemType x, int h)
{
	int l;
	if (T == NULL)
	{
		return(0);
	}
	if (T->data == x)
	{
		return(h);
	}
	l = Level(T->lchild, x, h + 1);
	if (l != 0)
	{
		return(l);
	}
	else
	{
		return(Level(T->rchild, x, h + 1));
	}
}

2.14 计算第k层的节点数

//计算第k层的结点数
void LnodeNum(BiTree T, int h, int k, int& n)
{
	if (T == NULL)
	{
		return;//空树直接返回
	}
	//处理非空树
	if (h == k)
		n++;  //当前访问的结点在第k层时,n++
	else if (h < k) //若当前节点层次小于k,递归处理左右子树
	{
		LnodeNum(T->lchild, h + 1, k, n);
		LnodeNum(T->rchild, h + 1, k, n);
	}
}

2.15 复制二叉树

//复制二叉树
void Copy(BiTree T, BiTree& NewT)
{
	//复制一棵和T完全相同的二叉树
	if (T == NULL)
	{
		NewT = NULL;
		return;
	}
	else
	{
		NewT = new BiTNode;
		NewT->data = T->data;
		Copy(T->lchild, NewT->lchild);
		Copy(T->rchild, NewT->rchild);
	}
}

2.16 判断两棵树是否相似

//判断两棵树是否相似
bool Like(BiTree T1, BiTree T2)
{
	bool like1, like2;
	if (T1 == NULL && T2 == NULL)
	{
		return true;
	}
	else if (T1 == NULL || T2 == NULL)
	{
		return false;
	}
	else
	{
		like1 = Like(T1->lchild, T2->lchild);
		like2 = Like(T1->rchild, T2->rchild);
		return(like1 && like2);
	}
}

2.17 查找某节点的双亲节点

//查找某节点的双亲节点
//这里需要用到两个函数来完成
//1.辅助函数
BiTNode* ParentHelper(BiTNode* root, BiTNode* cur_e)
{
	if (root == NULL || cur_e == NULL)
	{
		return NULL;
	}
	//如果root节点的左右节点中某一个是目标节点
	if (root->lchild == cur_e || root->rchild == cur_e)
	{
		return root;
	}
	//进行左递归查找
	BiTNode* leftResult = ParentHelper(root->lchild, cur_e);
	if (leftResult != NULL)
	{
		return leftResult;
	}
	//否则递归查找右子树
	return ParentHelper(root->rchild, cur_e);
}
//2.查询函数
BiTNode* Parent(BiTree& T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		return NULL;
	}

	//调用辅助函数查找双亲节点
	return ParentHelper(T, cur_e);
}

2.18 其他一些不重要函数

//不重要函数
//求某个节点的值
TElemType Value(BiTree& T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR:Tree or Node is NULL!");
		exit(1);
	}
	return cur_e->data;
}

//给某个节点赋值
void Assign(BiTree& T, BiTNode* cur_e, TElemType value)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR:Tree or Node is NULL!");
		exit(1);
	}
	cur_e->data = value;
}

//求某节点的左孩子结点
BiTNode* LeftChild(BiTree T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR: Tree or cur_e is NULL!");
		exit(1);
	}
	return cur_e->lchild;
}

//求某节点的右孩子结点
BiTNode* RightChild(BiTree T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR: Tree or cur_e if NULL!");
		exit(1);
	}
	return cur_e->rchild;
}

2.19 整体代码(含测试代码)

#include<stdio.h>
#include<iostream>
using namespace std;

typedef char TElemType;
typedef struct BiTNode {
	TElemType data;
	struct BiTNode* lchild, * rchild;
}BiTNode, * BiTree;

//前序递归遍历法
void PreOrder(BiTree T)
{
	if (T != NULL)
	{
		cout << T->data << " ";
		PreOrder(T->lchild);
		PreOrder(T->rchild);
	}
}

//中序递归遍历法
void InOrder(BiTree T)
{
	if (T != NULL)
	{
		InOrder(T->lchild);
		cout << T->data << " ";
		InOrder(T->rchild);
	}
}

//后序递归遍历法
void PostOrder(BiTree T)
{
	if (T != NULL)
	{
		PostOrder(T->lchild);
		PostOrder(T->rchild);
		cout << T->data << " ";
	}
}

//初始化树
void InitTree(BiTree& T)
{
	T = NULL;
}

//创建新节点
BiTNode* CreatNewNode(TElemType value)
{
	BiTNode* node = new BiTNode;
	//等价于
	//BiTNode* node = (BiTNode*)malloc(sizeof(BiTNode));
	node->data = value;
	node->lchild = NULL;
	node->rchild = NULL;
	return node;
}

//创建树
void CreateTree(BiTree& T, char* definition)
{
	static int i = 0;  //静态变量,每次递归进入过程中都会i++
	char ch = definition[i++];
	if (ch == '#')
	{
		T = NULL;
	}
	else
	{
		T = CreatNewNode(ch);
		CreateTree(T->lchild, definition);
		CreateTree(T->rchild, definition);
	}
}

//销毁树
void DestroyTree(BiTree& T)
{
	if (T != NULL)
	{
		DestroyTree(T->lchild);
		DestroyTree(T->rchild);
		free(T);
		T = NULL;
	}
}

//清空树
//在C代码作为主要函数的代码下,清空树和销毁树是几乎一样的操作
//唯一差别在于,销毁树是树根也销毁,清空树保存其树根
//如果是C++代码下
//BinaryTree类中添加了一个析构函数(~BinaryTree()),它会在对象被删除时自动调用DestroyTree。
//这确保了即使用户忘记显式调用DestroyTree,内存也会被正确释放


//判空
int TreeEmpty(BiTree T)
{
	if (T == NULL)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

//求树的深度
int TreeDepth(BiTree T)
{
	//定义左右子树的高度,作为最后返回时的比较值
	int lchildh = 0, rchildh = 0;
	if (T == NULL)
	{
		return 0;
	}
	else
	{
		lchildh = TreeDepth(T->lchild);
		rchildh = TreeDepth(T->rchild);
		return lchildh > rchildh ? lchildh + 1 : rchildh + 1;
	}
}

//查找某个节点
BiTNode* FindNode(BiTree T, TElemType x)
{
	BiTNode* p;//作为遍历
	if (T == NULL)
	{
		return NULL;
	}
	if (T->data == x)
	{
		return T;
	}
	
	//左递归查找
	p = FindNode(T->lchild, x);
	if (p != NULL)
	{
		return p;
	}
	else
	{
		//否则进行右递归查找
		return FindNode(T->rchild, x);
	}
}

//求树根
BiTNode* Root(BiTree T)
{
	if (T == NULL)
	{
		printf("ERROR:Tree is NULL!");
	}
	return T;
}

//求结点总数
int Nodes(BiTree T)
{
	if (T == NULL)
	{
		return 0;
	}
	return Nodes(T->lchild) + Nodes(T->rchild) + 1;
}

//输出叶子结点
void DispLeaf(BiTree T)
{
	if (T != NULL)
	{
		if (T->lchild == NULL && T->rchild == NULL)
		{
			cout << T->data << "  ";   //访问叶子节点
		}
		DispLeaf(T->lchild);  //输出左子树中的叶子节点
		DispLeaf(T->rchild);  //输出右子树中的叶子节点
	}
}

//计算某个节点值为x的层次(若在根结点 h = 1,树为空h = 0)
int Level(BiTree T, TElemType x, int h)
{
	int l;
	if (T == NULL)
	{
		return(0);
	}
	if (T->data == x)
	{
		return(h);
	}
	l = Level(T->lchild, x, h + 1);
	if (l != 0)
	{
		return(l);
	}
	else
	{
		return(Level(T->rchild, x, h + 1));
	}
}

//计算第k层的结点数
void LnodeNum(BiTree T, int h, int k, int& n)
{
	if (T == NULL)
	{
		return;//空树直接返回
	}
	//处理非空树
	if (h == k)
		n++;  //当前访问的结点在第k层时,n++
	else if (h < k) //若当前节点层次小于k,递归处理左右子树
	{
		LnodeNum(T->lchild, h + 1, k, n);
		LnodeNum(T->rchild, h + 1, k, n);
	}
}

//复制二叉树
void Copy(BiTree T, BiTree& NewT)
{
	//复制一棵和T完全相同的二叉树
	if (T == NULL)
	{
		NewT = NULL;
		return;
	}
	else
	{
		NewT = new BiTNode;
		NewT->data = T->data;
		Copy(T->lchild, NewT->lchild);
		Copy(T->rchild, NewT->rchild);
	}
}

//判断两棵树是否相似
bool Like(BiTree T1, BiTree T2)
{
	bool like1, like2;
	if (T1 == NULL && T2 == NULL)
	{
		return true;
	}
	else if (T1 == NULL || T2 == NULL)
	{
		return false;
	}
	else
	{
		like1 = Like(T1->lchild, T2->lchild);
		like2 = Like(T1->rchild, T2->rchild);
		return(like1 && like2);
	}
}

//查找某节点的双亲节点
//这里需要用到两个函数来完成
//1.辅助函数
BiTNode* ParentHelper(BiTNode* root, BiTNode* cur_e)
{
	if (root == NULL || cur_e == NULL)
	{
		return NULL;
	}
	//如果root节点的左右节点中某一个是目标节点
	if (root->lchild == cur_e || root->rchild == cur_e)
	{
		return root;
	}
	//进行左递归查找
	BiTNode* leftResult = ParentHelper(root->lchild, cur_e);
	if (leftResult != NULL)
	{
		return leftResult;
	}
	//否则递归查找右子树
	return ParentHelper(root->rchild, cur_e);
}
//2.查询函数
BiTNode* Parent(BiTree& T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		return NULL;
	}

	//调用辅助函数查找双亲节点
	return ParentHelper(T, cur_e);
}


//不重要函数
//求某个节点的值
TElemType Value(BiTree& T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR:Tree or Node is NULL!");
		exit(1);
	}
	return cur_e->data;
}

//给某个节点赋值
void Assign(BiTree& T, BiTNode* cur_e, TElemType value)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR:Tree or Node is NULL!");
		exit(1);
	}
	cur_e->data = value;
}

//求某节点的左孩子结点
BiTNode* LeftChild(BiTree T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR: Tree or cur_e is NULL!");
		exit(1);
	}
	return cur_e->lchild;
}

//求某节点的右孩子结点
BiTNode* RightChild(BiTree T, BiTNode* cur_e)
{
	if (T == NULL || cur_e == NULL)
	{
		printf("ERROR: Tree or cur_e if NULL!");
		exit(1);
	}
	return cur_e->rchild;
}


int main() {
	BiTree T, NewT;
	char definition[] = "ABD##E##CF##G#H##";

	// 测试初始化和创建树
	InitTree(T);
	CreateTree(T, definition);

	cout << "初始化树和创建树的测试:" << endl;

	// 测试遍历
	cout << "\n前序遍历: ";
	PreOrder(T);
	cout << "\n中序遍历: ";
	InOrder(T);
	cout << "\n后序遍历: ";
	PostOrder(T);

	// 测试TreeEmpty和TreeDepth
	cout << "\n\n树是否为空? " << (TreeEmpty(T) ? "Yes" : "No");
	cout << "\n树深: " << TreeDepth(T);

	// 测试FindNode
	TElemType searchValue = 'D';
	BiTNode* foundNode = FindNode(T, searchValue);
	cout << "\n\n查找节点 '" << searchValue << "': "
		<< (foundNode ? "Found" : "Not found");

	// 测试Root
	BiTNode* root = Root(T);
	cout << "\n树根: " << (root ? root->data : ' ');

	// 测试Nodes和DispLeaf
	cout << "\n所有节点数: " << Nodes(T);
	cout << "\n叶子结点: ";
	DispLeaf(T);

	// 测试Level
	cout << "\n\n'E'所在的层数: " << Level(T, 'E', 1);

	// 测试LnodeNum
	int nodeCount = 0;
	LnodeNum(T, 1, 3, nodeCount);
	cout << "\n第3层的结点数: " << nodeCount;

	// 测试Copy
	Copy(T, NewT);
	cout << "\n\n复制一棵树,并进行中序遍历: ";
	InOrder(NewT);

	// 测试Like
	cout << "\n上面复制的树和原树是否相似? " << (Like(T, NewT) ? "Yes" : "No");

	// 测试Parent
	BiTNode* nodeE = FindNode(T, 'E');
	BiTNode* parentOfE = Parent(T, nodeE);
	cout << "\n\n'E'的双亲节点: " << (parentOfE ? parentOfE->data : ' ');

	// 清理内存
	DestroyTree(T);
	DestroyTree(NewT);

	cout << "\n\nAll tests completed." << endl;

	return 0;
}

结语

数据结构树这里的相关操作和知识点非常多,并且一环套一环,这里需要大家拥有较多的耐心来一步步走下去,相信一定会更好的!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jmeter学习(8)界面的使用

1、新建test plan 3、 打开文件 4、保存 5、剪切 6、复制 7、粘贴 8、所有线程组展开 9、所有线程组收缩 10、置灰&#xff0c;操作后无法使用 11、执行 13、清空当前线程组结果 14、清空所有线程组结果 15、函数助手 搜索&#xff0c;可以用于搜索某个请求&#x…

Java基于微信小程序的健身小助手打卡预约教学系统(源码+lw+部署文档+讲解等)

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

使用OpenCV进行视频边缘检测:案例Python版江南style

1. 引言 本文将演示如何使用OpenCV库对视频中的每一帧进行边缘检测&#xff0c;并将结果保存为新的视频文件。边缘检测是一种图像处理技术&#xff0c;它可以帮助我们识别出图像中不同区域之间的边界。在计算机视觉领域&#xff0c;这项技术有着广泛的应用&#xff0c;比如物体…

登录时用户名密码加密传输(包含前后端代码)

页面输入用户名密码登录过程中&#xff0c;如果没有对用户名密码进行加密处理&#xff0c;可能会导致传输过程中数据被窃取&#xff0c;就算使用https协议&#xff0c;在浏览器控制台的Request Payload中也是能直接看到传输的明文&#xff0c;安全感是否还是不足。 大致流程&a…

redis—cluster集群

一&#xff1a;Redis Cluster特点 多主多从&#xff0c;去中心化&#xff1a;从节点作为备用&#xff0c;复制主节点&#xff0c;不做读写操作&#xff0c;不提供服务不支持处理多个key&#xff1a;因为数据分散在多个节点&#xff0c;在数据量大高并发的情况下会影响性能&…

Columns Page “列”页面

“列”页提供了列管理工具&#xff0c;其中包括用于添加和删除列的按钮、显示绑定数据源中字段名称的列表框以及网格列、提供对所选列属性的访问的属性网格。 Columns 页面提供 Column properties &#xff08;列属性&#xff09;、Column options &#xff08;列选项&#xff…

Electron-(三)网页报错处理与请求监听

在前端开发中&#xff0c;Electron 是一个强大的框架&#xff0c;它允许我们使用 Web 技术构建跨平台的桌面应用程序。在开发过程中&#xff0c;及时处理网页报错和监听请求是非常重要的环节。本文将详细介绍 Electron 中网页报错的日志记录、webContents 的监听事件以及如何监…

如何使用JMeter进行性能测试的保姆级教程

性能测试是确保网站在用户访问高峰时保持稳定和快速响应的关键环节。作为初学者&#xff0c;选择合适的工具尤为重要。JMeter 是一个强大的开源性能测试工具&#xff0c;可以帮助我们轻松模拟多用户场景&#xff0c;测试网站的稳定性与性能。本教程将引导你通过一个简单的登录场…

微信小程序canvas 生成二维码图片,画图片,生成图片,将两个canvas结合并保存图片

需求实现步骤如下 先定义两个canvas一个canvas myQrcode画二维码的图片另一个canvas mycanvas画一个背景图&#xff0c;并把二维码画到这个canvas上&#xff0c;mycanvas这个canvas生成一张图片&#xff0c;返回图片的临时路径最后保存图片到手机 首先wxml,新版微信小程序can…

Java之继承抽象类用法实例(三十一)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

使用Matplotlib绘制箱线图:详细指南与示例

在数据分析和可视化领域&#xff0c;箱线图&#xff08;Box Plot&#xff09;是一种强大的工具&#xff0c;用于展示数据的分布特征&#xff0c;包括中位数、四分位数、异常值等。本文将详细介绍如何使用Matplotlib库在Python中绘制箱线图&#xff0c;并通过一个实际的血压数据…

从0开始linux(13)——进程(4)进程空间地址(1)

欢迎来到博主的专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 进程空间地址 还记得博主在之前介绍子进程时说过的话吗&#xff1f;子进程与父进程共享代码&#xff0c;而数据却不共享&#xff1b;这很好理解&#xff0c;因为子进程和父进程是不同的进程&a…

Java线程安全集合之COW

概述 java.util.concurrent.CopyOnWriteArrayList写时复制顺序表&#xff0c;一种采用写时复制技术&#xff08;COW&#xff09;实现的线程安全的顺序表&#xff0c;可代替java.util.ArrayList用于并发环境中。写时复制&#xff0c;在写入时&#xff0c;会复制顺序表的新副本&…

K8S调度不平衡问题分析过程和解决方案

不平衡问题排查 问题描述&#xff1a; 1、业务部署大量pod(据反馈&#xff0c;基本为任务型进程)过程中&#xff0c;k8s node内存使用率表现不均衡&#xff0c;范围从80%到百分之几&#xff1b; 2、单个node内存使用率超过95%&#xff0c;仍未发生pod驱逐&#xff0c;存在node…

Janus:开创统一的多模态理解和生成框架

Janus是DeepSeek开源的多模式自回归框架&#xff0c;统一了多模态理解和生成&#xff0c;既可以理解图片内容又可以生成图片。 1.简介 Janus 是一种新颖的自回归框架&#xff0c;它将多模态理解和生成统一起来。它通过将视觉编码解耦为单独的路径来解决以前方法的局限性&…

jmeter发送post请求

在jmeter中&#xff0c;有两种常用的请求方式&#xff0c;get和post.它们两者的区别在于get请求的参数一般是放在路径中&#xff0c;可以使用用户自定义变量和函数助手等方式进行参数化&#xff0c;而post请求的参数不能随url发送&#xff0c;而是作为请求体提交给服务器。而在…

OpenWRT 和 Padavan 路由器配置网络打印机 实现远程打印

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 之前有给大家介绍过 Armbian 安装 CUPS 作为打印服务器&#xff0c;像是 N1 盒子、玩客云&#xff0c;甚至是随身 WiFi 都可以通过 CUPS 来进行打印。但是有些朋友不想专门为打印机添置一个设备&#xff0…

Spring AI 1.0.0 M1版本新特性!

Spring AI 1.0.0 M1版本新特性介绍 前言一、在1.0.0 M1版本中&#xff0c;主要有以下新特性&#xff1a;1.ChatModel2.ChatClient3.多模态的支持4.模型评估RequestResponseAdvisor接口MessageChatMemoryAdvisorPromptChatMemoryAdvisorQuestionAnswerAdvisor动态过滤表达式 Vec…

爬虫逆向-js进阶(续写,搭建网站)

1.搭建简单网站1 from flask import Flask,render_template import requests import json app Flask(name)# **location**的温度是**temp**度&#xff0c;天气状况&#xff1a;**desc**app.route(/) # 绑定处理函数 def index_url():location 101010100data get_weather(lo…

黑马JavaWeb-day02

什么是JavaScript&#xff1f; JavaScript&#xff1a;简称Js,是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互 JavaScript和Java是完全不同的语言&#xff0c;无论是概念还是设计。但是基础语法类似。 JavaScript JavaScript引入方式…