【C++、数据结构】二叉排序树(二叉查找树、二叉搜索树)(图解+完整代码)

目录

[⚽1.什么是二叉排序树]

[🏐2.构建二叉排序树]

[🏀3.二叉排序树的查找操作]

[🥎4.二叉排序树的删除]

[🎱5.完整代码]

⚽1.什么是二叉排序树

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    在这里插入图片描述

🏐2.构建二叉排序树

  1. 二叉搜索树的模拟实现

2.1 结点的声明

//描述二分查找树的一个结点
template<class K>
struct BSTNode
{K _key;						//数据域struct BSTNode* _left;		//指向左子树指针struct BSTNode* _right;		//指向右子树指针BSTNode(K key):_key(key),_left(nullptr),_right(nullptr){}
};

2.2 基本的几个成员函数

template<class K>class BSTree
{               typedef BSTreeNode<K> Node;
private://没有参数是不能递归的void DestroyTree(Node* root){if (root == nullptr)return;DestroyTree(root->_left);DestroyTree(root->_right);delete root;}Node* CopyTree(Node* root){if (root == nullptr)return nullptr;Node* copyNode = new Node(root->_key);copyNode->_left = CopyTree(root->_left);copyNode->_right = CopyTree(root->_right);return copyNode;}
public://强制编译器自己生成构造函数 -- C++11BSTree() = default;/*BSTree():_root(nullptr){}*///前序遍历递归拷贝BSTree(const BSTree<K>& t){_root = CopyTree(t._root);}//t1 = t2; -- 任何赋值重载都可以用现代写法BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){DestroyTree(_root);_root = nullptr;}

构造函数

  • 这里我们可以采用传统的方法
  • 直接初始化成员变量
  • 也可以用C++11的语法default
  • 强制编译器自己生成构造函数

拷贝构造

  • 这里我们用了递归的方式进行拷贝
  • 采用根 - 左 - 右 的前序遍历的递归方式对整个二叉树拷贝
  • 最后将跟结点返回

析构函数

  • 析构函数我们这里也是采用递归的方式进行一个一个结点析构
  • 同样的我们再嵌套一个子函数
  • 也是采用类似前序遍历的方法将整个二叉树释放掉

采用递归方式的缺点就是如果数的结点个数足够多的时候,就会有爆栈的风险!!

2.3 插入操作

假设我们有以下数据,我们按从左到右的顺序来构建二叉排序树:

  1. 首先,将8作为根节点
  2. 插入3,由于3小于8,作为8的左子树
  3. 插入10,由于10大于8,作为8的右子树
  4. 插入1,由于1小于8,进入左子树3,1又小于3,则1为3的左子树
  5. 插入6,由于6小于8,进入左子树3,6又大于3,则6为3的右子树
  6. 插入14,由于14大于8,进入右子树10,14又大于10,则14为10的右子树
  7. 插入4,由于4小于8,进入左子树3,4又大于3,进入右子树6,4还小于6,则4为6的左子树
  8. 插入7,由于7小于8,进入左子树3,7又大于3,进入右子树6,7还大于于6,则7为6的右子树
  9. 插入13,由于13大于8,进入右子树10,又13大于10,进入右子树14,13小于14,则13为14的左子树

经过以上的逻辑,这棵二叉排序树构建完成。

我们可以看出:

  • 只要左子树为空,就把小于父节点的数插入作为左子树
  • 只要右子树为空,就把大于父节点的数插入作为右子树
  • 如果不为空,就一直往下去搜索,直到找到合适的插入位置

没错,这棵二叉树中序遍历结果为:

  • 二叉树中序遍历结果为升序,左节点<根节点<右节点

插入思路:

  • 从根结点开始遍历。(不能相等哦,直接结束就好
  • key<遍历结点的值,则遍历其左子树;key>遍历结点的值,则遍历其右子树
  • 直到遍历到某个叶子结点
  • 插入:比叶子节点小,插入左子树,反之,右子树

根据以上思路,我们其实就可以写出代码了,构建的过程其实就是插入的过程:

//插入函数
bool Insert(const K key)
{Node* newnode = new Node(key);//空树时if (_root == NULL){_root = newnode;return true;}Node* cur = _root;			//用来遍历Node* parent = nullptr;		//记录上一个节点while (cur){parent = cur;//key< 结点值,遍历其左子树if (key < cur->_key){cur = cur->_left;}//key> 结点值,遍历其右子树else if (key > cur->_key){cur = cur->_right;}//不能插入相同的else{return false;}}if (key < parent->_key){parent->_left = newnode;}if (key > parent->_key){parent->_right = newnode;}return true;
}

🏀3.二叉排序树的查找操作

它既然也叫二叉查找树,那想必会非常方便我们查找吧!它的操作并不是把中序遍历的结果存入数组,然后在有序数组里查找,而是直接在树上查找。

  1. 首先,访问根节点8
  2. 根据性质,7<8访问8的左子树
  3. 访问到了3,7>3,访问3的右子树
  4. 访问到了6,继续访问6的右子树
  5. 访问到了7,刚好找到啦!

显然,它的效率会比在无序数组中挨着查找快多了吧!我们直接上代码。

	//查找bool Find(const K& key){Node* cur = _root;while (cur){//往左子树找if (key<cur->_key){cur = cur->_left;}//往右子树找else if (key>cur->_key){cur = cur->_right;}//找到了else{return true;	}}return false;	//没有找到}

🥎4.二叉排序树的删除(动图演示)

二叉搜索树的删除函数是最难实现的,若是在二叉树当中没有找到待删除结点,则直接返回 false 表示删除失败即可,但若是找到了待删除结点,此时就有以下三种情况:

  • 待删除结点的左子树为空(因为放第一个,所以包含左子树和无子树)
  • 待删除结点的右子树为空
  • 待删除结点的左右子树均不为空
    下面进行一对一处理:

情况一:左子树为空

(1)待删除结点的左子树为空,即右子树不为空,或者不为空

  • 待删除结点的左子树为空(因为放第一个,所以包含左子树和无子树)
  • 若待删除结点的左子树为空,那么当我们在二叉搜索树当中找到该结点后,只需先让其父结点指向该结点的右孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍保持二叉搜索树的特性

注意:如果删除的节点为叶子节点,即待删除节点左右子树均为空,这个情况包含在这种情况里面

动图演示(演示其中一种情况):删除10
在这里插入图片描述

  • 待删除结点不是根结点,此时parent不为nullptr
  • 待删除结点是其父结点的左孩子,父结点的左指针指向待删除结点的右子树即可
  • 反之,父结点的右指针指向待删除结点的右子树即可

下面只是删除部分的代码哦,看下面完整代码

//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)
//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况
if (cur->_left == nullptr)
{//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;		//删除节点return true;
}

情况二:右子树为空

(2)待删除结点的右子树为空,即左子树不为空

  • 待删除结点的右子树为空,那么当我们在二叉搜索树当中找到该结点后
  • 只需先让其父结点指向该结点的左孩子结点,然后再将该结点释放便完成了该结点的删除,进行删除操作后仍需要保持二叉搜索树的特性

动图演示(演示其中一种情况):删除14
在这里插入图片描述

  • 待删除结点不是根结点,此时parent不为nullptr
  • 待删除结点是其父结点的右孩子,父结点的左指针指向待删除结点的左子树即可
  • 反之,父结点右指针指向待删除结点的左子树即可

下面只是删除部分的代码哦,看下面完整代码

//(2) 删除节点只有左孩子
else if (cur->_right == nullptr)
{//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;		//删除节点return true;
}

情况三:两个左右子树都不为空

(3)待删除结点的左右子树均不为空

若待删除结点的左右子树均不为空,那么当我们在二叉搜索树当中找到该结点后,可以使用替换法进行删除

有两种替换方法

(1)删除结点左子树当中值最大的结点
(2)删除结点右子树当中值最小的结点

关于两种替换方法
替换方法(1):要删的结点(8)的左子树的最大结点
那就是用7来顶替位置

此时7从叶子结点“升迁”到了根节点(只是刚好要删除的结点为根节点,如果删除3,就替换3的位置)

替换方法(2):要删的结点(8)的右子树的最小结点
那就是用10来顶替位置


下面演示我们用删除结点右子树当中值最小的结点来替换。

动图演示(演示其中一种情况):删除3

显然删除结点右子树当中值最小的结点为4

  • 怎么找到(删除结点右子树当中值最小的结点)
    • 1.用minParent记录minRight的父节点
    • 2.minRight从删除节点的右子树开始
    • 3.一直往左找
//这里找右子树的最小节点替换cur
Node* minParent = cur;
Node* minRight = cur->_right;//从右子树开始找
while (minRight->_left)		//当然一直往左找
{minParent = minRight;minRight = minRight->_left;
}

下面只是删除部分的代码哦,看下面完整代码

//(3)删除节点左右孩子都有
//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)
else
{//这里找右子树的最小节点替换curNode* minParent = cur;Node* minRight = cur->_right;//从右子树开始找while (minRight->_left)		//当然一直往左找{minParent = minRight;minRight = minRight->_left;}//开始替换//1.替换值cur->_key = minRight->_key;//2.连接minParent和minRight(把minRight右边连上就OK)//minRight是minParent的左节点if (minRight == minParent->_left)minParent->_left = minRight->_right;//minRight是minParent的右节点elseminParent->_right = minRight->_right;//3.删除节点delete minRight;return true;
}

删除代码

//删除
bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){//往左走if (key < cur->_key){parent = cur;cur = cur->_left;}//往右走else if(key>cur->_key){parent = cur;cur = cur->_right;}//找到了,开删else{//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况if (cur->_left == nullptr){//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;		//删除节点return true;}//(2) 删除节点只有左孩子else if (cur->_right == nullptr){//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;		//删除节点return true;}//(3)删除节点左右孩子都有//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)else{//这里找右子树的最小节点替换curNode* minParent = cur;Node* minRight = cur->_right;//从右子树开始找while (minRight->_left)		//当然一直往左找{minParent = minRight;minRight = minRight->_left;}//开始替换//1.替换值cur->_key = minRight->_key;//2.连接minParent和minRight(把minRight右边连上就OK)//minRight是minParent的左节点if (minRight == minParent->_left)minParent->_left = minRight->_right;//minRight是minParent的右节点elseminParent->_right = minRight->_right;//3.删除节点delete minRight;return true;}}}return false;
}

🎱5.完整代码(非递归)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//描述二分查找树的一个结点
template<class K>
struct BSTNode
{K _key;				//数据域struct BSTNode* _left;		//指向左子树指针struct BSTNode* _right;		//指向右子树指针BSTNode(K key):_key(key),_left(nullptr),_right(nullptr){}
};
template<class K>
class BSTree
{typedef BSTNode<K> Node;
private:Node* _root;void Destory(Node* root){if (root == nullptr) return;Destory(root->_left);Destory(root->_right);delete(root);}//拷贝函数(递归)Node* Copy(Node* root){if (_root == nullptr){return nullptr;}Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_key<< endl;_InOrder(root->_right);}
public://构造函数BSTree() {};//拷贝构造BSTree(const BSTree<K>& t) {_root = Copy(t._root);}//析构~BSTree(){Destory(_root);		//调用销毁函数_root = nullptr;}//插入函数bool Insert(const K key){Node* newnode = new Node(key);//空树时if (_root == NULL){_root = newnode;return true;}Node* cur = _root;			//用来遍历Node* parent = nullptr;		//记录上一个节点while (cur){parent = cur;//key< 结点值,遍历其左子树if (key < cur->_key){cur = cur->_left;}//key> 结点值,遍历其右子树else if (key > cur->_key){cur = cur->_right;}//不能插入相同的else{return false;}}if (key < parent->_key){parent->_left = newnode;}if (key > parent->_key){parent->_right = newnode;}return true;}//查找bool Find(const K& key){Node* cur = _root;while (cur){//往左子树找if (key<cur->_key){cur = cur->_left;}//往右子树找else if (key>cur->_key){cur = cur->_right;}//找到了else{return true;	}}return false;	//没有找到}//删除bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){//往左走if (key < cur->_key){parent = cur;cur = cur->_left;}//往右走else if(key>cur->_key){parent = cur;cur = cur->_right;}//找到了,开删else{//(1)删除节点只有0-1个孩子的时候(只有右孩子或者没有孩子)//  这里包括了  cur->_left=nullptr&&cur->_right=nullptr的情况,就是没有孩子的情况if (cur->_left == nullptr){//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的右子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;		//删除节点return true;}//(2) 删除节点只有左孩子else if (cur->_right == nullptr){//删除的是根节点//parent==nullptr;if (cur == _root){_root = cur->_right;}else{//cur是parent的左子树,把cur的左子树托孤给parent的左/右子树if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;		//删除节点return true;}//(3)删除节点左右孩子都有//替换法,然后删除(找右子树的最小节点或者找到左子树的最大节点)else{//这里找右子树的最小节点替换curNode* minParent = cur;Node* minRight = cur->_right;//从右子树开始找while (minRight->_left)		//当然一直往左找{minParent = minRight;minRight = minRight->_left;}//开始替换//1.替换值cur->_key = minRight->_key;//2.连接minParent和minRight(把minRight右边连上就OK)//minRight是minParent的左节点if (minRight == minParent->_left)minParent->_left = minRight->_right;//minRight是minParent的右节点elseminParent->_right = minRight->_right;//3.删除节点delete minRight;return true;}}}return false;}//中序遍历-有序void InOrder(){_InOrder(_root);cout << endl;}};int main()
{BSTree<int> bst;//插入int arr[] = { 8,3,10,1,6,14,4,7,13 };int n = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < n; i++){bst.Insert(arr[i]);}//中序遍历bst.InOrder();//查找//int p = 0;//cout << "输入要找的数:";//cin >> p;	//if (bst.Find(p))//{//	cout << "找到了" << endl;//}//else//{//	cout << "没有找到" << endl;//}//删除//1.测试没有孩子//bst.Erase(10);//2.测试一个孩子//bst.Erase(14);//3.测试有两个孩子//bst.Erase(3);bst.InOrder();return 0;
}

递归版本(扩展)

递归版本理解起来就相对与非递归版本更好理解了,直接看代码

(1)查找:

bool FindR(const K& key)
{  return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}
}

逐层递归查找即可…

(2)插入:(重点)

bool InsertR(const K& key)
{return _InsertR(_root, key);
}bool _InsertR(Node*& root, const K& key)
{//没有父指针,胜似父指针if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}
}

该如何链接上树呢?

  • 可以在递归的参数中多一个父亲结点,每次递归都更新一下Parent,然后再带到下一层递归
  • 显然这样在学过C++之后就麻烦了

用了一个指针的引用就解决了问题

  • 因为root的值此时是空,但是root同时是这个结点里的_left这个指针的别名
  • 相当于当前结点的父节点的左指针的别名
  • 意味着此时再去给root赋值就是去给该结点父亲结点的_left赋值
  • 那么此时就链接起来了

(3)删除:

bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{//递归是用来找要删除的结点if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;//root是要删除结点的左结点/右结点的别名if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* minRight = root->_right;while (minRight->_left){minRight = minRight->_left;}swap(root->_key, minRight->_key);return _EraseR(root->_right, key);//转换成在root->_right(右子树)中去删除key//这里删除这个key一定会走左为空的场景(找最小)}delete del;return true;}
}

相等时就开始删除了(递归只是用来查找要删除的数的位置)

  • root是要删除结点的左结点 / 右结点的别名

分三种情况删除:

  1. 要删除的结点左为空
  2. 要删除的结点右为空
  3. 要删除的结点左右都为空(替换法)

总的来说递归版本比非递归版本更容易理解,删除过程参考非递归删除过程……(有异曲同工之妙)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java避坑案例 - 消除代码重复_模板方法与工厂模式的最佳实践

文章目录 需求基础实体类BadVersion优化&#xff1a; 利用工厂模式 模板方法模式&#xff0c;消除 if…else 和重复代码优化一&#xff1a; 模板方法的应用AbstractCart 类&#xff08;抽象类&#xff09;各种购物车实现&#xff08;继承抽象类&#xff09;普通用户购物车 (No…

【JavaScript】Javascript基础Day02:运算符、分支、循环

Javascript——Day02 01. 赋值运算符02. 自增运算符03. 比较运算符04. 逻辑运算符以及优先级05. if单分支语句06. if双分支语句07. if多分支语句08. 三元运算符09. 数字补0案例10. switch分支语句11. 断点调试12. while循环13. 退出循环 01. 赋值运算符 02. 自增运算符 03. 比较…

【java】抽象类和接口(了解,进阶,到全部掌握)

各位看官早安午安晚安呀 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连&#xff0c;小编尽全力做到更好 欢迎您分享给更多人哦 大家好我们今天来学习Java面向对象的的抽象类和接口&#xff0c;我们大家庭已经来啦~ 一&#xff1a;抽象类 1.1:抽象类概念 在面向对象的概念中…

Python|基于Kimi大模型,实现上传文档并进行“多轮”对话(7)

前言 本文是该专栏的第7篇,后面会持续分享AI大模型干货知识,记得关注。 假设有这样的需求,需要你通过python基于kimi大模型,上传对应的文档并根据对应的prompt提示词,进行多轮对话。此外,还需要将kimi大模型生成的内容进行存储。具体场景,如下图所示: 也就是说,当我们…

这种V带的无极变速能用在新能源汽车上吧?

CVT的无极变速器的结构能用在电动汽车上吗&#xff1f;

【优选算法篇】在分割中追寻秩序:二分查找的智慧轨迹

文章目录 C 二分查找详解&#xff1a;基础题解与思维分析前言第一章&#xff1a;热身练习1.1 二分查找基本实现解题思路图解分析C代码实现易错点提示代码解读 1.2 在排序数组中查找元素的第一个和最后一个位置解题思路1.2.1 查找左边界算法步骤&#xff1a;图解分析C代码实现 1…

LeetCode94:二叉树的中序遍历

文章目录 &#x1f60a;1.题目&#x1f609;2.解法1.递归2.迭代 &#x1f60a;1.题目 尝试一下该题 &#x1f609;2.解法 1.递归 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* …

企业级 RAG 全链路优化关键技术

2024 云栖大会 - AI 搜索企业级 RAG 全链路优化关键技术 在2024云栖大会上&#xff0c;阿里云 AI 搜索研发负责人之一的邢少敏先生带领大家深入了解如何利用 RAG 技术优化决策支持、内容生成、智能推荐等多个核心业务场景&#xff0c;为企业数字化转型与智能化升级提供强有力的…

【Linux】了解pthread线程库,清楚并没有线程创建接口,明白Linux并不存在真正意义的线程(附带模型图详解析)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Liunx系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

ECHO-GL:盈利电话驱动的异质图学习股票 走势预测

目录 简单概括1 背景知识相关的工作图学习在股票预测中的应用股票预测中的收益电话会议 方法异质股票图结构建造时间分配机制滑动窗机构库存空间关系模块异构边缘类型消息传递音频引导的注意聚合财报电话会议后股票动态模块预测和优化 实验消融研究 (for Q2)模拟交易 (for Q3) …

vue组件传值之$attrs

1.概述&#xff1a;$attrs用于实现当前组件的父组件&#xff0c;向当前组件的子组件通信&#xff08;祖-》孙&#xff09; 2.具体说明&#xff1a;$attrs是一个对象&#xff0c;包含所有父组件传入的标签属性。 注意&#xff1a;$attrs会自动排除props中声明的属性&#xff0…

【不要离开你的舒适圈】:猛兽才希望你落单,亲人总让你回家,4个维度全面构建舒适圈矩阵

单打独斗的英雄时代已经落幕 抱团取暖才是社会寒冬的良策 自然界中&#xff0c;每个物种都占据着自己的领地和生存空间。 生态位的差异决定了它们的生存方式&#xff0c;一旦离开领地&#xff0c;失去群体的庇护&#xff0c;就会沦为野兽的美餐。 人类社会同样存在隐形圈层…

【C++】踏上C++学习之旅(三):“我“ 与 “引用“ 的浪漫邂逅

文章目录 前言1. "引用"的概念1.1 "引用"的语法 2. "引用"的特性3. "引用"的使用场景3.1 "引用"做参数3. 2 "引用"做返回值3.2.1 "引用"做返回值时需要注意的点 4. 常引用5. "引用"在底层的实…

自动化数据处理:使用Selenium与Excel打造的数据爬取管道

随着互联网信息爆炸式增长&#xff0c;获取有效数据成为决策者的重要任务。人工爬取数据不仅耗时且效率低下&#xff0c;因此自动化数据处理成为一种高效解决方案。本文将介绍如何使用Selenium与Excel实现数据爬取与处理&#xff0c;结合代理IP技术构建一个可稳定运行的数据爬取…

RocketMQ快速开始

前置推荐阅读&#xff1a;RocketMQ简介-CSDN博客 本地部署 RocketMQ 这一节介绍如何快速部署一个单节点单副本 RocketMQ 服务&#xff0c;并完成简单的消息收发。 系统要求 64位操作系统&#xff0c;推荐 Linux/Unix/macOS64位 JDK 1.8 1.下载安装Apache RocketMQ​ RocketMQ…

aws 把vpc残留删除干净

最近忘了把vpc 删干净导致又被收了冤大头钱 在删除vpc 的收发现又eni在使用&#xff0c;但是忘了是哪个资源在占用 先用命令行把占用的资源找出来停掉 使用 AWS 命令行界面&#xff08;CLI&#xff09;来查看 VPC 的使用情况 列出子网&#xff1a; aws ec2 describe-subnets …

抖音列表页采集-前言

准备工作&#xff1a; 1.关于selenium介绍&#xff1a; python自动化入门的话&#xff0c;selenium绝对是最方便的选择&#xff0c;基本逻辑即为&#xff1a;程序模拟人的行为操作浏览器&#xff0c;这样的操作需要借用浏览器驱动&#xff0c;我选用的是chrome浏览器&#xff…

浮动练习(3)

##每台电脑分辨率不同&#xff0c;数值高度宽度需要自己调&#xff0c;仅供参考 <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title></title> <style> div{ …

港大和字节提出长视频生成模型Loong,可生成具有一致外观、大运动动态和自然场景过渡的分钟级长视频。

HKU, ByteDance&#xff5c;⭐️ 港大和字节联合提出长视频生成模型Loong&#xff0c;该模型可以生成外观一致、运动动态大、场景过渡自然的分钟级长视频。选择以统一的顺序对文本标记和视频标记进行建模&#xff0c;并使用渐进式短到长训练方案和损失重新加权来克服长视频训练…

MATLAB(Octave)混电动力能耗评估

&#x1f3af;要点 处理电动和混动汽车能耗的后向和前向算法模型(simulink)&#xff0c;以及图形函数、后处理函数等实现。构建储能元数据信息&#xff1a;电池标称特性、电池标识符等以及静止、恒定电流和恒定电压等特征阶段。使用电流脉冲或要识别的等效电路模型类型配置阻抗…