【C++】二叉搜索树

ops/2024/10/11 3:19:31/

在本篇博客中,作者将会讲解普通的二叉搜索树,其目的是为后面的AVLTree红黑树进行铺垫。

一. 二叉搜索树

那么什么是二叉搜索树呢,二叉树应该大家都能理解,所以顾名思义,二叉搜索树就是一个用来搜索的二叉树二叉树搜索树也可以叫做一棵二叉排序树,因为它的中序遍历是有序的

1.性质

二叉搜索树的性质:

1.如果当前结点的左子树不为空,则左子树上的所有结点小于当前结点

2.如果当前结点的右子树不为空,则右子树上的所有结点大于当前结点

3.它的左右子树分别也是二叉搜索树

即一棵二叉树中序遍历是有序的就是二叉搜索树,也叫二叉排序树

如下就是一棵二叉搜索树。 

二.二叉搜索树的实现 

那么已经知道了二叉搜索树是什么,也知道了它的性质,那么现在我们来实现一下。

首先,二叉搜索树最主要的接口有下面这几个:

1.插入

2.删除

3.查找

4.修改(注意,不是所有二叉搜索树都能修改的,因为二叉搜索树要严格遵守它的性质,一旦它的性质被破坏,则这棵树就会无意义) 

1.树结点的定义

首先我们来看一下树结点的定义。 

template<class K>
struct Blog_TreeNode
{//成员变量K _key;Blog_TreeNode<K>* _left;Blog_TreeNode<K>* _right;//成员函数Blog_TreeNode(const K& key)//构造函数:_key(key),_left(nullptr),_right(nullptr){}
};

树结点的定义不是很难,就二叉树常规的定义即可。 

2.类的定义 

我们可以把树结点封装到一个Blog_Tree的类中,在类中实现它的成员函数。 

template<class K>
class Blog_Tree
{typedef Blog_TreeNode<K> Node;//typedef一下,这样在类中可以使用Node来代替Blog_TreeNode
public://构造函数Blog_Tree():_root(nullptr){}private:Node* _root;//成员变量为一个根节点的指针
};

3.构造函数与析构函数

 对于一个类,有四个比较重要的成员函数:构造函数、析构函数、拷贝构造、赋值=重载

在这里,拷贝构造和赋值=重载先不做讲解,先把构造函数析构函数进行讲解。

①构造函数

对于二叉搜索树的构造函数来说非常简单,直接把_root给nullptr即可。

	//构造函数Blog_Tree():_root(nullptr){}
②析构函数 

对于析构函数来说,我们可以使用后序遍历来完成。 

	//析构函数~Blog_Tree(){destory(_root);}void destory(Node* root){if (root == nullptr)return;destory(root->_left);destory(root->_right);delete root;}

 4.插入结点 

二叉搜索树插入结点的过程如下:

1.如果树为空,则直接将新结点给_root根结点。

2.如果树不为空,则将新结点当前结点进行比较,如果新结点大于当前结点,则当前结点向右走否则向左走,如果相等,则不插入。

如下图所示。 

 

接下来我们来编写代码来实现这个过程。 

	//插入结点bool insert(const K& data){//如果树为空,则直接将新结点给到_rootif (_root == nullptr)_root = new Node(data);//如果树不为空,这开始找空位,将新结点插入Node* cur = _root;Node* cur_parent = nullptr;//这里要提前保留cur的parent,因为后面插入时要用到while (cur != nullptr){if (data > cur->_key)//如果要插入的数据大于cur,则cur往右走{cur_parent = cur;//保留cur的parentcur = cur->_right;}else if (data < cur->_key)//如果要插入的数据小于cur,则cur往左走{cur_parent = cur;//保留cur的parentcur = cur->_left;}else//如果相等,说明树中已有,则不插入,返回失败{return false;}}//找到空位后,将新结点进行插入Node* newnode = new Node(data);if (cur_parent->_key > newnode->_key)cur_parent->_left = newnode;elsecur_parent->_right = newnode;return true;}

5.删除结点

删除结点是所有操作里面比较难的,但是不要怕,我们来分析一下。 


删除结点,有四种情况:

1.要删除的结点没有左子树和右子树

2.要删除的结点没有左子树,右子树

3.要删除的结点左子树,没有右子树

4.要删除的结点左子树和右子树

我们基于这四种情况分析一下。 

①要删除的结点没有左子树和右子树 

对于这种情况的处理,非常简单。

我们只需要将该结点删除,让它的parent指向空即可。如下图所示。 

注意!!!在进行删除操作时,要注意一种情况

1.被删除的结点是根结点 

②要删除的结点没有左子树,有右子树 

对于这种情况,也还好,直接将要删除结点parent指向被删除结点的右孩子即可,如下图所示。 


 


这里也一样,要判断被删除的cur是否时根结点。 

③要删除的结点有左子树,没有右子树

 这种情况与第二种一样,只不过相反了而已。这里就不多解释。

④ 要删除的结点有左子树和右子树

对于这种情况,我们的解决方法是,去找被删除结点(cur)右子树的最小结点(即被删除结点的右子树的最左结点)(cur_right_min),后将cur_right_min赋值给cur,这样我们就可以将删除cur转换完删除cur_right_min。如下图所示。


  


  


  

此时删除cur变为删除cur_right_min,那么这个时候删除cur_right_min,也就是第一种情况,或者第二种情况。 

注意!!!这里也可能会有特殊情况,例如,下面这种情况。 

所以最后要进行一个判断。


删除代码如下: 

	//删除结点bool erase(const K& data){if (_root == nullptr)return false;//先找到要删除的结点Node* cur = _root;Node* cur_parent = nullptr;while (cur != nullptr){//先找到要删除的结点if (data > cur->_key){cur_parent = cur;cur = cur->_right;}else if (data < cur->_key){cur_parent = cur;cur = cur->_left;}else//找到了cur要删除的结点{//判断是那种删除情况if (cur->_left == nullptr && cur->_right == nullptr)//第一种情况{if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = nullptr;}else{if (cur_parent->_left == cur)//这里要判断cur时cur_parent左还是右{cur_parent->_left = nullptr;}else{cur_parent->_right = nullptr;}}delete cur;}else if (cur->_left == nullptr && cur->_right != nullptr)//第二种情况{if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = cur->_right;}else{if (cur_parent->_left == cur)//这里要判断cur时cur_parent左还是右{cur_parent->_left = cur->_right;}else{cur_parent->_right = cur->_right;}}delete cur;}else if (cur->_left != nullptr && cur->_right == nullptr)//第三种情况{if (cur_parent == nullptr){_root = cur->_left;}else{if (cur_parent->_left == cur)//这里要判断cur时cur_parent左还是右{cur_parent->_left = cur->_left;}else{cur_parent->_right = cur->_left;}delete cur;}}else//第四种情况{Node* cur_right_min = cur->_right;//cur右子树的最小左结点Node* cur_right_min_parent = cur;while (cur_right_min->_left != nullptr)//去找cur右子树的最小左结点{cur_right_min_parent = cur_right_min;cur_right_min = cur_right_min->_left;}cur->_key = cur_right_min->_key;//找到后将其赋值给cur,后转变为删除cur_right_minif (cur_right_min_parent->_key > cur_right_min->_key){cur_right_min_parent->_left = cur_right_min->_right;}else{cur_right_min_parent->_right = cur_right_min->_right;}delete cur_right_min;}return true;}}return false;}

6.查找结点

接下来我们来实现查找结点的函数,查找的过程与插入的过程类似。

1.要查找的值比cur大,则cur往右走。 

2.要查找的值比cur小,则cur往左走。

3.如果找到,则返回该结点的指针,如果没找到,则返回nullptr

	Node* find(const K& data){Node* cur = _root;while (cur != nullptr){if (data > cur->_key){cur = cur->_right;}else if (data < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}

三. 二叉搜索树的应用

那么二叉搜索树有什么应用了,由名字可以得知,二叉搜索树主要就是用来搜索的。

二叉搜索树的搜索又分为两种模型:K模型,和KV模型

K模型 

 什么是K模型,K指的是key的意思,如上面我们实现的二叉搜索树就是一种K模型的搜索树,就是在树的结点中,只存储一个K类型的值,剩下的是两个孩子指针。

template<class K>
struct Blog_TreeNode
{//成员变量K _key;Blog_TreeNode<K>* _left;Blog_TreeNode<K>* _right;//构造函数Blog_TreeNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};

K模型的主要用途是:查找这个值在不在

比如说:学校宿舍的门禁系统,在这个门禁系统里面,就有着一棵二叉搜索树,每个树结点K都是一个string类型,存的是该宿舍学生的名字,那么这棵二叉搜索树里面存着你们宿舍里每个学生的姓名,当有一个人想要进入该宿舍的时候,他需要进行刷卡识别,这个时候,程序就会去该宿舍的二叉搜索树中去进行查找,查查这个人的姓名在不在这个二叉搜索树中,如果在就能进入,不在不能进入。

KV模型 

什么是KV模型,K是keyV是value的意思,在树结点中,不仅仅有一个K类型,还有一个V类型,意思就是说,每一个key都对应的一个value的意思,即<key,value>键值对,但是注意的是,在KV模型中,我们依然是使用K来进行比较来插入。

比如说,用来存储单词,又或者说统计次数。下面我会来演示一下。


在演示之前,我们需要需要把我们的K模型的二叉搜索树改造成KV模型的二叉搜索树。

改造非常简单,在树结点结构体中增加一个V类型的值,后将所有成员函数涉及到的K,V都小改一下,例如,插入中,要多传一个参数。改造完后如下所示。 

template<class K, class V>
struct Blog_TreeNode
{//成员变量K _key;V _val;Blog_TreeNode<K>* _left;Blog_TreeNode<K>* _right;//构造函数Blog_TreeNode(const K& key, const V& val):_key(key),_val(val),_left(nullptr),_right(nullptr){}
};template<class K, class V>
class Blog_Tree
{typedef Blog_TreeNode<K, V> Node;
public://构造函数Blog_Tree():_root(nullptr){}//析构函数~Blog_Tree(){destory(_root);}//插入结点bool insert(const K& key, const V& val){//如果树为空,则直接将新结点给到_rootif (_root == nullptr){_root = new Node(key, val);}//如果树不为空,这开始找空位,将新结点插入Node* cur = _root;Node* cur_parent = nullptr;//这里要提前保留cur的parent,因为后面插入时要用到while (cur != nullptr){if (key > cur->_key)//如果要插入的数据大于cur,则cur往右走{cur_parent = cur;//保留cur的parentcur = cur->_right;}else if (key < cur->_key)//如果要插入的数据小于cur,则cur往左走{cur_parent = cur;//保留cur的parentcur = cur->_left;}else//如果相等,说明树中已有,则不插入,返回失败{return false;}}//找到空位后,将新结点进行插入Node* newnode = new Node(key, val);if (cur_parent->_key > newnode->_key){cur_parent->_left = newnode;}else{cur_parent->_right = newnode;}return true;}//删除结点bool erase(const K& key){if (_root == nullptr){return false;}//先找到要删除的结点Node* cur = _root;Node* cur_parent = nullptr;while (cur != nullptr){//先找到要删除的结点if (key > cur->_key){cur_parent = cur;cur = cur->_right;}else if (key < cur->_key){cur_parent = cur;cur = cur->_left;}else//找到了cur要删除的结点{//判断是那种删除情况if (cur->_left == nullptr && cur->_right == nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = nullptr;}else{if (cur_parent->_left == cur){cur_parent->_left = nullptr;}else{cur_parent->_right = nullptr;}}delete cur;}else if (cur->_left == nullptr && cur->_right != nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = cur->_right;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_right;}else{cur_parent->_right = cur->_right;}}delete cur;}else if (cur->_left != nullptr && cur->_right == nullptr){if (cur_parent == nullptr){_root = cur->_left;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_left;}else{cur_parent->_right = cur->_left;}delete cur;}}else{Node* cur_right_min = cur->_right;//cur右子树的最小左结点Node* cur_right_min_parent = cur;while (cur_right_min->_left != nullptr)//去找cur右子树的最小左结点{cur_right_min_parent = cur_right_min;cur_right_min = cur_right_min->_left;}cur->_key = cur_right_min->_key;//找到后将其赋值给cur,后转变为删除cur_right_minif (cur_right_min_parent->_key > cur_right_min->_key){cur_right_min_parent->_left = cur_right_min->_right;}else{cur_right_min_parent->_right = cur_right_min->_right;}delete cur_right_min;}return true;}}return false;}//查找Node* find(const K& key){Node* cur = _root;while (cur != nullptr){if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ":" << root->_val << endl;_InOrder(root->_right);}void destory(Node* root){if (root == nullptr)return;destory(root->_left);destory(root->_right);delete root;}
private:Node* _root;
};
存储单词

接下来我们来演示一下。

//字典树
void Blog_test1()
{Blog_Tree<string, string> root;root.insert("树", "tree");root.insert("排序", "sort");root.insert("水", "water");Blog_TreeNode<string, string>* tmp = root.find("排序");if (tmp == nullptr){cout << "无该单词" << endl;}else{cout << tmp->_val << endl;}
}

在上面的代码中,我们可以在树中存一个又一个的<中文,英文>键值对,通过这个中文可以找到对应的英文。 

统计次数 
//统计次数
void Blog_test2()
{string str[] = { "衣服","鞋子","衣服","裤子","衣服","衣服","裤子","鞋子","帽子","帽子","袜子" };Blog_Tree<string, int> root;for (auto& e : str){Blog_TreeNode<string, int>* tmp = root.find(e);//如果树中没有,则插入,并且val给1if (tmp == nullptr){root.insert(e, 1);}else//如果树中有,则val++{tmp->_val++;}}root.InOrder();
}

当我们要插入时,先判断在二叉搜索树中在不在,如果不在,则进行插入,并把val次数给1,如果存储,则对val进行++。 

四.二叉树搜索树的性能

对于二叉搜索树的性能来说,我们可以看到,它的插入和查找最多进行高度次的查找,例如下面这棵树,如果要进行查找最多查找3次,如果要插入,最多也就4次,所以可以得出二叉搜索树的性能为O(logN),其中N为树中结点的个数。

但是我们的树不一定是我们所想的那么理想的,如果要插入的数据完全有序或者接近有序的时候,二叉树会退化成单支树。如下图所示。 

这个时候二叉搜索树的性能就会退化成O(n),这显然不是我们想要的,所以这个时候又衍生出来的AVLTree红黑树,这两棵树会在后面的博客中进行讲解。 

五.所以源代码 

所以源代码分为K模型的和KV模型的。 

K模型 

BinarySearchTree.h
#pragma once
#include<iostream>
using namespace std;template<class K>
struct Blog_TreeNode
{//成员变量K _key;Blog_TreeNode<K>* _left;Blog_TreeNode<K>* _right;//构造函数Blog_TreeNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}};template<class K>
class Blog_Tree
{typedef Blog_TreeNode<K> Node;
public://构造函数Blog_Tree():_root(nullptr){}//析构函数~Blog_Tree(){destory(_root);}//插入结点bool insert(const K& data){//如果树为空,则直接将新结点给到_rootif (_root == nullptr){_root = new Node(data);}//如果树不为空,这开始找空位,将新结点插入Node* cur = _root;Node* cur_parent = nullptr;//这里要提前保留cur的parent,因为后面插入时要用到while (cur != nullptr){if (data > cur->_key)//如果要插入的数据大于cur,则cur往右走{cur_parent = cur;//保留cur的parentcur = cur->_right;}else if (data < cur->_key)//如果要插入的数据小于cur,则cur往左走{cur_parent = cur;//保留cur的parentcur = cur->_left;}else//如果相等,说明树中已有,则不插入,返回失败{return false;}}//找到空位后,将新结点进行插入Node* newnode = new Node(data);if (cur_parent->_key > newnode->_key){cur_parent->_left = newnode;}else{cur_parent->_right = newnode;}return true;}//删除结点bool erase(const K& data){if (_root == nullptr){return false;}//先找到要删除的结点Node* cur = _root;Node* cur_parent = nullptr;while (cur != nullptr){//先找到要删除的结点if (data > cur->_key){cur_parent = cur;cur = cur->_right;}else if (data < cur->_key){cur_parent = cur;cur = cur->_left;}else//找到了cur要删除的结点{//判断是那种删除情况if (cur->_left == nullptr && cur->_right == nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = nullptr;}else{if (cur_parent->_left == cur){cur_parent->_left = nullptr;}else{cur_parent->_right = nullptr;}}delete cur;}else if (cur->_left == nullptr && cur->_right != nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = cur->_right;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_right;}else{cur_parent->_right = cur->_right;}}delete cur;}else if (cur->_left != nullptr && cur->_right == nullptr){if (cur_parent == nullptr){_root = cur->_left;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_left;}else{cur_parent->_right = cur->_left;}delete cur;}}else{Node* cur_right_min = cur->_right;//cur右子树的最小左结点Node* cur_right_min_parent = cur;while (cur_right_min->_left != nullptr)//去找cur右子树的最小左结点{cur_right_min_parent = cur_right_min;cur_right_min = cur_right_min->_left;}cur->_key = cur_right_min->_key;//找到后将其赋值给cur,后转变为删除cur_right_minif (cur_right_min_parent->_key > cur_right_min->_key){cur_right_min_parent->_left = cur_right_min->_right;}else{cur_right_min_parent->_right = cur_right_min->_right;}delete cur_right_min;}return true;}}return false;}Node* find(const K& data){Node* cur = _root;while (cur != nullptr){if (data > cur->_key){cur = cur->_right;}else if (data < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}void destory(Node* root){if (root == nullptr)return;destory(root->_left);destory(root->_right);delete root;}
private:Node* _root;
};
test.c 
#include"BinarySearchTree.h"//测试插入和删除
void Blog_test1()
{Blog_Tree<int> root;int arr1[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };for (auto& e : arr1){root.insert(e);}root.InOrder();for (auto& e : arr1){root.erase(e);root.InOrder();}
}//测试查找
void Blog_test2()
{Blog_Tree<int> root;int arr1[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16,14 };for (auto& e : arr1){root.insert(e);}Blog_TreeNode<int>* tmp = root.find(20);if (tmp != nullptr){cout << tmp->_key << endl;}else{cout << "不存在给值" << endl;}
}int main()
{//Blog_test1();Blog_test2();return 0;
}

 KV模型

BinarySearchTree.h 
template<class K, class V>
struct Blog_TreeNode
{//成员变量K _key;V _val;Blog_TreeNode<K, V>* _left;Blog_TreeNode<K, V>* _right;//构造函数Blog_TreeNode(const K& key, const V& value):_key(key),_val(value),_left(nullptr),_right(nullptr){}};template<class K, class V>
class Blog_Tree
{typedef Blog_TreeNode<K, V> Node;
public://构造函数Blog_Tree():_root(nullptr){}//析构函数~Blog_Tree(){destory(_root);}//插入结点bool insert(const K& key, const V& val){//如果树为空,则直接将新结点给到_rootif (_root == nullptr){_root = new Node(key, val);}//如果树不为空,这开始找空位,将新结点插入Node* cur = _root;Node* cur_parent = nullptr;//这里要提前保留cur的parent,因为后面插入时要用到while (cur != nullptr){if (key > cur->_key)//如果要插入的数据大于cur,则cur往右走{cur_parent = cur;//保留cur的parentcur = cur->_right;}else if (key < cur->_key)//如果要插入的数据小于cur,则cur往左走{cur_parent = cur;//保留cur的parentcur = cur->_left;}else//如果相等,说明树中已有,则不插入,返回失败{return false;}}//找到空位后,将新结点进行插入Node* newnode = new Node(key, val);if (cur_parent->_key > newnode->_key){cur_parent->_left = newnode;}else{cur_parent->_right = newnode;}return true;}//删除结点bool erase(const K& key){if (_root == nullptr){return false;}//先找到要删除的结点Node* cur = _root;Node* cur_parent = nullptr;while (cur != nullptr){//先找到要删除的结点if (key > cur->_key){cur_parent = cur;cur = cur->_right;}else if (key < cur->_key){cur_parent = cur;cur = cur->_left;}else//找到了cur要删除的结点{//判断是那种删除情况if (cur->_left == nullptr && cur->_right == nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = nullptr;}else{if (cur_parent->_left == cur){cur_parent->_left = nullptr;}else{cur_parent->_right = nullptr;}}delete cur;}else if (cur->_left == nullptr && cur->_right != nullptr){if (cur_parent == nullptr)//如果cur_parent为空,说明删除的cur为_root{_root = cur->_right;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_right;}else{cur_parent->_right = cur->_right;}}delete cur;}else if (cur->_left != nullptr && cur->_right == nullptr){if (cur_parent == nullptr){_root = cur->_left;}else{if (cur_parent->_left == cur){cur_parent->_left = cur->_left;}else{cur_parent->_right = cur->_left;}delete cur;}}else{Node* cur_right_min = cur->_right;//cur右子树的最小左结点Node* cur_right_min_parent = cur;while (cur_right_min->_left != nullptr)//去找cur右子树的最小左结点{cur_right_min_parent = cur_right_min;cur_right_min = cur_right_min->_left;}cur->_key = cur_right_min->_key;//找到后将其赋值给cur,后转变为删除cur_right_minif (cur_right_min_parent->_key > cur_right_min->_key){cur_right_min_parent->_left = cur_right_min->_right;}else{cur_right_min_parent->_right = cur_right_min->_right;}delete cur_right_min;}return true;}}return false;}//查找Node* find(const K& key){Node* cur = _root;while (cur != nullptr){if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ":" << root->_val << endl;_InOrder(root->_right);}void destory(Node* root){if (root == nullptr)return;destory(root->_left);destory(root->_right);delete root;}
private:Node* _root;
};
 test.c
//字典树
void Blog_test1()
{Blog_Tree<string, string> root;root.insert("树", "tree");root.insert("排序", "sort");root.insert("水", "water");Blog_TreeNode<string, string>* tmp = root.find("排序");if (tmp == nullptr){cout << "无该单词" << endl;}else{cout << tmp->_val << endl;}
}//统计次数
void Blog_test2()
{string str[] = { "衣服","鞋子","衣服","裤子","衣服","衣服","裤子","鞋子","帽子","帽子","袜子" };Blog_Tree<string, int> root;for (auto& e : str){Blog_TreeNode<string, int>* tmp = root.find(e);if (tmp == nullptr){root.insert(e, 1);}else{tmp->_val++;}}root.InOrder();
}int main()
{//Blog_test1();Blog_test2();return 0;
}

http://www.ppmy.cn/ops/47462.html

相关文章

python_01

1、test # 方法1 不推荐使用&#xff0c;繁琐 # open("./1.txt",) # ./和不写&#xff0c;都代表从当前文件目录去找内容 file1 open(r".\1.txt","r",encoding"utf8") # "r" 读取 encoding"utf8" 设…

数据隐私重塑:Web3时代的隐私保护创新

随着数字化时代的不断深入&#xff0c;数据隐私保护已经成为了人们越来越关注的焦点之一。而在这个数字化时代的新篇章中&#xff0c;Web3技术作为下一代互联网的代表&#xff0c;正在为数据隐私保护带来全新的创新和可能性。本文将深入探讨数据隐私的重要性&#xff0c;Web3时…

地球科学SCI期刊,中科院2区,IF=14.6,自引率低,无预警风险!

一、期刊名称 IEEE Geoscience and Remote Sensing Magazine 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;地球科学 影响因子&#xff1a;14.6 中科院分区&#xff1a;2区 三、期刊征稿范围 IEEE地球科学和遥感杂志向读者介绍IEEE GRS协会及其技术委员…

idea2021版以上开启Dashboard功能

idea2021版以上开启Dashboard功能 1 在.idea/workspace.xml 文件中找2 增加option 1 在.idea/workspace.xml 文件中找 2 增加option ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/22a809700e3643d18e80dc37e5afe014.png)

做外贸是否需要代运营?

相信很多做外贸的小伙伴或者公司都有这样的一个困扰&#xff0c;尤其是做SEO以及平台的公司&#xff0c;会很纠结要不要将公司的运营承包出去。 而之所以有这样的困扰&#xff0c;一部分是公司的业务员可能并不擅长运营&#xff0c;或者是业务员抽不出时间去管理运营这块。 而…

AWVS+BP+XRAY三层联动扫描漏洞

1. 前言 本报告详细记录了使用AWVS&#xff08;Acunetix Web Vulnerability Scanner&#xff09;、Burp Suite和Xray进行的漏洞扫描结果。旨在帮助开发团队识别和修复系统中的安全漏洞&#xff0c;提升整体安全性。 2. 扫描工具简介 AWVS&#xff08;Acunetix Web Vulnerabi…

SRS、ZLMediakit音视频流媒体服务器

SRS、ZLMediakit都是做为webrtc的SFU&#xff08;selective forward unit&#xff09; WebRTC 开发实践&#xff1a;为什么你需要 SFU 服务器 https://mp.weixin.qq.com/s?__bizMzAxNTc1MjM0Mw&mid2652213442&idx1&sn33f0393a2dbc2b6a39c613bb238ec145&chksm…

前缀树的实现

前缀树的实现 何谓前缀树&#xff1f;实现前缀树节点的选择插入查找查找前缀完整代码 何谓前缀树&#xff1f; 前缀树&#xff08;字典树&#xff09;&#xff0c;使用树状的数据结构存储一个字典的所有单词。前缀树是一个多叉树&#xff0c;一个节点可能有多个子节点&#xf…