23 二叉搜索树

本节目标

1.内容安排说明
2.二叉搜索树实现
3.应用分析
4.进阶题

1. 内容安排说明

二叉树在c数据结构已经说过了,本节内容是因为:

  1. map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构
  2. 二叉搜索树的特性了解,有助于更好的理解map和set的特性
  3. 二叉树中有部分题有难度,前面不容易接受,且容易遗忘
  4. oj题用c语言实现麻烦,有些地方要返回动态开辟的二维数组,非常麻烦

因此本节借二叉搜索树,对二叉树部分进行收尾总结

2. 二叉搜索树

2.1 二叉搜索树概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

在这里插入图片描述

二叉搜索树:一棵二叉树,可以为空;如果不为空,满足以下性质:
1.非空左子树的所有键值小于其根节点的键值
2.非空右子树的所有键值都大于其根节点的值
3.左、右子树都是二叉搜索树

在这里插入图片描述

2.2 二叉搜索树操作

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1.二叉搜索树的查找
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
b、最多查找高度次,走到到空,还没找到,这个值不存在

2.二叉搜索树的插入
插入的具体过程如下:
a、树为空,则直接新增节点,赋值给root指针
b、树不空,按二叉搜索树性质查找插入位置,插入新节点

插入9和16的过程
在这里插入图片描述
3.二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
a、要删除的结点无孩子节点
b、要删除的节点只有左孩子节点
c、要删除的结点只有右孩子节点
d、要删除的结点有左、右孩子节点

看起来有待删除节点有4种情况,实际情况a可以和情况b或者c结合起来,因此真正的删除过程如下:
情况b:删除该及诶点且被删除节点的双亲结点指向被删除节点的左孩子节点–直接删除
情况c:删除该节点且使被删除节点的双亲结点指向被删除节点的有孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个节点(关键码最小),用它的值填补到被删除节点中,再来处理该节点的删除问题–替换法删除

删除节点左右孩子有一个为空,只需要将它的父节点指向不为空的这个节点。如果删除节点的左右两边都有节点,则需要找一个可以替代这个删除节点的,也就是左子树里的最大值或右子树里的最小值,这个最值一定是叶子结点或者只有一个子节点的情况,交换删除节点和它的值后,就可以按上面的方法删除这个节点

2.3 二叉搜索树的实现

节点的结构,左右节点和值和构造
在这里插入图片描述
二叉树结构,保存根节点
在这里插入图片描述在这里插入图片描述
插入

bool insert(const K& key)
{if (_root == nullptr){node* newnode = new node(key);_root = newnode;return true;}node* cur = _root;node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}node* newnode = new node(key);if (key < parent->_key){parent->_left = newnode;}else{parent->_right = newnode;}return true;}

首先判断是不是空树,空树先创建根节点。当前节点用来寻找插入位置,父节点变量连接,小往左走,大往右走,值相等插入失败。空节点就是插入的位置,判断连接的是左还是右节点

中序遍历

void inorder()
{_inorder(_root);std::cout << std::endl;
}
void _inorder(node* root)
{if (root == nullptr){return;}_inorder(root->_left);std::cout << root->_key << " ";_inorder(root->_right);
}

递归需要一个初始参数,,根节点是私有的,实例化不能访问,所以套一层接口传入根节点。中序先左再根再右

删除

bool earse(const K& key)
{node* del = _root;node* parent = nullptr;while (del){if (key < del->_key){parent = del;del = del->_left;}else if (key > del->_key){parent = del;del = del->_right;}else{//删除//左结点为空或右节点为空,父节点领养子节点if (del->_left == nullptr){//删除节点是根节点,父节点为空,子节点成为根节点if (parent == nullptr){_root = del->_right;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_right;}else{parent->_right = del->_right;}}delete del;del = nullptr;}else if (del->_right == nullptr){if (parent == nullptr){_root = del->_left;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_left;}else{parent->_right = del->_left;}}delete del;del = nullptr;}else{//两个节点都不为空,从左子树找最大的替换node* max = del->_left;//parent设置为空,如果删除根节点会出错,所以赋初始值node* parent = del;while (max->_right){parent = max;max = max->_right;}std::swap(max->_key, del->_key);//不能再调用一遍函数删除,因为不是二叉搜索树了,找不到delif (parent == del){parent->_left = max->_left;}else{//一般情况,右节点断开连接parent->_right = max->_left;}delete max;}return true;}}return false;
}

和插入一样,先寻找删除的位置,相等就是需要删除了。
分两种情况:
1.左右只有一个子节点。先判断是不是删除根节点,删除根节点就要改变_root的指向。不是根节点就判断左右哪个不为空,父节点连接到不为空的结点
2.左右都有节点。从左子树中找最大结点,记录父节点用来删除。交换max节点和删除节点的值,之后需要删除的就变成了max节点

父节点初始值给成删除节点,方便后面删除,无需更多判断
如果父节点就是删除节点,max节点就是删除节点的左节点,父节点连接到max的左节点,就删除了max。这里右节点一定为空,不然父节点就不会和删除节点一样。下图内只需要8和5交换,parent的左连接到max的左
在这里插入图片描述
如果父节点不是删除节点,那么max节点一定是右节点,且它的右节点肯定为空,因为没有比它大的了。只需要parent的右连接到max的左。下图只需要8和7交换,parnt的右连接到max的左在这里插入图片描述
查找

bool find(const K& key)
{if (_root == nullptr){return false;}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;
}

相等返回找到

构造
default是c++11的特性,会生成没有定义的默认的构造函数
拷贝构造调用copy函数,copy函数递归复制二叉树每个节点,需要前序遍历,最后返回根节点

BinaryTree(const BinaryTree<K>& x)
{_root = copy(x._root);
}
//递归前链接
node* copy(node* root)
{if (root == nullptr){return nullptr;}node* newnode = new node(root->_key);newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);return newnode;
}

赋值构造只需要交换临时对象的根节点,函数调用完自动释放临时对象

BinaryTree<K>& operator=(BinaryTree<K> x)
{std::swap(_root, x._root);return *this;
}

析构

~BinaryTree()
{destory(_root);
}void destory(node*& root)
{if (root == nullptr){return;}destory(root->_left);destory(root->_right);delete root;root = nullptr;
}

析构函数调用destory函数,后续遍历删除所有节点,这里用引用作为参数,可以直接删除传入的二叉树

插入递归

bool insertx(const K& key)
{return _insertx(_root, key);
}bool _insertx(node*& root, const K& key)
{if (root == nullptr){root = new node(key);return true;}if (key < root->_key){_insertx(root->_left, key);}else if (key > root->_key){_insertx(root->_right, key);}else{return false;}
}

因为根节点外部无法获取所以套一层,如果是空,就创建节点。比key小就递归左边插入,大就递归右边插入,这里有一个链接的问题,创建节点后如何和父节点连接。只需要传入引用,递归的每一层就是父节点的子节点了,创建后自动连接

查找递归

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

和插入一样

bool earsex(const K& key)
{return _earsex(_root, key);
}bool _earsex(node*& root, const K& key)
{if (root == nullptr){return false;}if (key < root->_key){_earsex(root->_left, key);}else if (key > root->_key){_earsex(root->_right, key);}else{node* del = root;//左或右为空,直接改变当前节点,父节点自动连接if (root->_left == nullptr){root = root->_right;}else if (root->_right = nullptr){root = root->_left;}else{//两个节点都不为空,从左子树找最大的替换node* max = root->_left;while (max->_right){max = max->_right;}std::swap(root->_key, max->_key);return _earsex(root->_left, key);}delete del;del = nullptr;}
}

删除同样传递引用,可以改变实参的值,递归寻找删除值,等于后开始删除。因为父节点自动连接,所以不需要保存父节点,如果一个节点为空的情况,直接将节点改变为不为空的子节点。如果都不为空,寻找左子树的最大节点,交换两个节点的值,再调用一次删除函数,传入删除节点的左子树就可以转换为一个节点为空的情况

template <typename K>
struct TreeNode
{struct TreeNode<K>* _left;struct TreeNode<K>* _right;K _key;TreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};template <class K>
class BinaryTree
{
public:typedef struct TreeNode<K> node;BinaryTree() = default;BinaryTree(const BinaryTree<K>& x){_root = copy(x._root);}BinaryTree<K>& operator=(BinaryTree<K> x){std::swap(_root, x._root);return *this;}bool insert(const K& key){if (_root == nullptr){node* newnode = new node(key);_root = newnode;return true;}node* cur = _root;node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}node* newnode = new node(key);if (key < parent->_key){parent->_left = newnode;}else{parent->_right = newnode;}return true;}void inorder(){_inorder(_root);std::cout << std::endl;}void _inorder(node* root){if (root == nullptr){return;}_inorder(root->_left);std::cout << root->_key << " ";_inorder(root->_right);}bool find(const K& key){if (_root == nullptr){return false;}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 earse(const K& key){node* del = _root;node* parent = nullptr;while (del){if (key < del->_key){parent = del;del = del->_left;}else if (key > del->_key){parent = del;del = del->_right;}else{//删除//左结点为空或右节点为空,父节点领养子节点if (del->_left == nullptr){//删除节点是根节点,父节点为空,子节点成为根节点if (parent == nullptr){_root = del->_right;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_right;}else{parent->_right = del->_right;}}delete del;del = nullptr;}else if (del->_right == nullptr){if (parent == nullptr){_root = del->_left;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_left;}else{parent->_right = del->_left;}}delete del;del = nullptr;}else{//两个节点都不为空,从左子树找最大的替换node* max = del->_left;//parent设置为空,如果删除根节点会出错,所以赋初始值node* parent = del;while (max->_right){parent = max;max = max->_right;}std::swap(max->_key, del->_key);//不能再调用一遍函数删除,因为不是二叉搜索树了,找不到delif (parent == del){parent->_left = max->_left;}else{//一般情况,右节点断开连接parent->_right = max->_left;}delete max;}return true;}}return false;}//递归bool insertx(const K& key){return _insertx(_root, key);}bool _insertx(node*& root, const K& key){if (root == nullptr){root = new node(key);return true;}if (key < root->_key){_insertx(root->_left, key);}else if (key > root->_key){_insertx(root->_right, key);}else{return false;}}bool findx(const K& key){return _findx(_root, key);}bool _findx(node* root, const K& key){if (root == nullptr){return false;}if (key < root->_key){_findx(root->_left, key);}else if (key > root->_key){_findx(root->_right, key);}else{return true;}}bool earsex(const K& key){return _earsex(_root, key);}bool _earsex(node*& root, const K& key){if (root == nullptr){return false;}if (key < root->_key){_earsex(root->_left, key);}else if (key > root->_key){_earsex(root->_right, key);}else{node* del = root;//左或右为空,直接改变当前节点,父节点自动连接if (root->_left == nullptr){root = root->_right;}else if (root->_right = nullptr){root = root->_left;}else{//两个节点都不为空,从左子树找最大的替换node* max = root->_left;while (max->_right){max = max->_right;}std::swap(root->_key, max->_key);return _earsex(root->_left, key);}delete del;del = nullptr;}}~BinaryTree(){destory(_root);_root = nullptr;}void destory(node*& root){if (root == nullptr){return;}destory(root->_left);destory(root->_right);delete root;root = nullptr;}private://前序遍历先创建节点,返回最上层的节点就是根节点//递归前链接node* copy(node* root){if (root == nullptr){return nullptr;}node* newnode = new node(root->_key);newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);return newnode;}
private:node* _root = nullptr;
};

3. 二叉搜索树的应用

1.K模型:K模型即只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一颗二叉搜索树
在二叉搜索树中检索该单词是否存在,存爱则拼写正确,不存在则拼写错误

门禁系统等都是key,检测在不在

2.kv模型:每一个关键码,都有与之对应的值value,即<key, value>的键值对。这种在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与之对应的中文<word,chinese>就构成一种键值对
再比如统计单词个数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word,count>就构成一种键值对

例如字典,统计单词个数等就是kv,根据单词查找翻译

改造kv二叉树
模板参数加V类型,存储vlaue值,查找返回节点指针,就可以访问value了

template <typename K, typename V>
struct TreeNode
{struct TreeNode<K, V>* _left;struct TreeNode<K, V>* _right;K _key;V _value;TreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}
};template <class K, class V>
class BinaryTree
{
public:typedef struct TreeNode<K, V> node;BinaryTree() = default;BinaryTree(const BinaryTree<K, V>& x){_root = copy(x._root);}BinaryTree<K, V>& operator=(BinaryTree<K, V> x){std::swap(_root, x._root);return *this;}bool insert(const K& key, const V& value){if (_root == nullptr){node* newnode = new node(key, value);_root = newnode;return true;}node* cur = _root;node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}node* newnode = new node(key, value);if (key < parent->_key){parent->_left = newnode;}else{parent->_right = newnode;}return true;}void inorder(){_inorder(_root);std::cout << std::endl;}void _inorder(node* root){if (root == nullptr){return;}_inorder(root->_left);std::cout << root->_key << " " << root->_value << std::endl;_inorder(root->_right);}node* find(const K& key){if (_root == nullptr){return nullptr;}node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;}}return nullptr;}bool earse(const K& key){node* del = _root;node* parent = nullptr;while (del){if (key < del->_key){parent = del;del = del->_left;}else if (key > del->_key){parent = del;del = del->_right;}else{//删除//左结点为空或右节点为空,父节点领养子节点if (del->_left == nullptr){//删除节点是根节点,父节点为空,子节点成为根节点if (parent == nullptr){_root = del->_right;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_right;}else{parent->_right = del->_right;}}delete del;del = nullptr;}else if (del->_right == nullptr){if (parent == nullptr){_root = del->_left;}else{//删除节点是父节点的左还是右if (parent->_left == del){parent->_left = del->_left;}else{parent->_right = del->_left;}}delete del;del = nullptr;}else{//两个节点都不为空,从左子树找最大的替换node* max = del->_left;//parent设置为空,如果删除根节点会出错,所以赋初始值node* parent = del;while (max->_right){parent = max;max = max->_right;}std::swap(max->_key, del->_key);//不能再调用一遍函数删除,因为不是二叉搜索树了,找不到delif (parent == del){parent->_left = max->_left;}else{//一般情况,右节点断开连接parent->_right = max->_left;}delete max;}return true;}}return false;}//递归bool insertx(const K& key, const V& value){return _insertx(_root, key, value);}bool _insertx(node*& root, const K& key, const V& value){if (root == nullptr){root = new node(key, value);return true;}if (key < root->_key){_insertx(root->_left, key);}else if (key > root->_key){_insertx(root->_right, key);}else{return false;}}node* findx(const K& key){return _findx(_root, key);}node* _findx(node* root, const K& key){if (root == nullptr){return nullptr;}if (key < root->_key){_findx(root->_left, key);}else if (key > root->_key){_findx(root->_right, key);}else{return root;}}bool earsex(const K& key){return _earsex(_root, key);}bool _earsex(node*& root, const K& key){if (root == nullptr){return false;}if (key < root->_key){_earsex(root->_left, key);}else if (key > root->_key){_earsex(root->_right, key);}else{node* del = root;//左或右为空,直接改变当前节点,父节点自动连接if (root->_left == nullptr){root = root->_right;}else if (root->_right = nullptr){root = root->_left;}else{//两个节点都不为空,从左子树找最大的替换node* max = root->_left;while (max->_right){max = max->_right;}std::swap(root->_key, max->_key);return _earsex(root->_left, key);}delete del;del = nullptr;}}~BinaryTree(){destory(_root);_root = nullptr;}void destory(node*& root){if (root == nullptr){return;}destory(root->_left);destory(root->_right);delete root;root = nullptr;}private://前序遍历先创建节点,返回最上层的节点就是根节点//递归前链接node* copy(node* root){if (root == nullptr){return nullptr;}node* newnode = new node(root->_key, root->value);newnode->_left = copy(root->_left);newnode->_right = copy(root->_right);return newnode;}
private:node* _root = nullptr;
};

输入单词查询翻译

BinaryTree<string, string> dict;
dict.insert("sort", "排序");
dict.insert("left", "左边");
dict.insert("right", "右边");
dict.insert("insert", "插入");
dict.insert("key", "钥匙");string str;
while (cin >> str)
{TreeNode<string, string>* ret = dict.find(str);if (ret){cout << ret->_value << endl;}else{cout << "unknow" << endl;}
}

统计水果出现的次数

ring arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" , "南瓜"};BinaryTree<string, int> tree;for (auto ch : arr){TreeNode<string, int>* ret = tree.find(ch);if (ret == nullptr){
tree.insert(ch, 1);}else{
ret->_value++;}}tree.inorder();

性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
对有n个节点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是节点在二叉搜索树的深度的函数,即节点越深,则比较次数越多

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: f r a c N 2 frac{N}{2} fracN2

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?这就需要AVL树和红黑树

4. 进阶题

根据二叉树创建字符串

创建字符串
在这里插入图片描述
思路
在这里插入图片描述
首先得到没有省略括号版的。利用前序遍历,递归左右子树之前加上括号,递归后的变量都不一样,为了字符串内容能叠加,需要每次加上递归后的内容。然后总结出括号可以省略的情况
1.当左右都为空的时候,括号都可以省略
2.右边为空的时候,可以省略
3.左边为空不能省略,因为如果右边不为空,无法区分是左右哪个节点
反推,左子树需要加括号的情况有两种,左边不为空或者右边不为空都不能省略括号。右子树只有不为空的时候不能省略

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),* right(right) {}* };*/
class Solution {
public:string tree2str(TreeNode* root) {string str;if (root == nullptr) {return str;}str += to_string(root->val);// 省略括号if (root->left || root->right) {str += "(";str += tree2str(root->left);str += ")";}if (root->right) {str += "(";str += tree2str(root->right);str += ")";}return str;}
};

二叉树的最近公共祖先

公共祖先
在这里插入图片描述
思路
公共祖先即为相同的父节点,例如7和4的公共祖先就是2、5、3,最近的就是2。判断是不是最近的公共祖先可以遵循下面规则:
p和q节点分别在这个结点的一左一右,这个结点就是最近的公用祖先。如果p和q一个是另一个祖先,那么最近的公共节点就是祖先的这个结点

先弄一个函数,判断节点是不是在这棵树中,用来判断p和q在树中的左右情况。首先判断两个节点有一个是根节点,直接返回这个结点。用四个变量pleft,pright,qleft,qright表明节点情况,调用函数传入根的左树,如果pleft返回真,证明p在左树中,那么pright就是假,同样方法判断q。如果一左一右就找到了最近的公共节点,返回这个结点。如果p和q都在左子树,就递归到左子树,都在右子树就递归到右子树

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool isTree(TreeNode* node, TreeNode* root){if (root == nullptr){return false;}if (node == root){return true;}return isTree(node, root->left) ||isTree(node, root->right);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root == p || root == q){return root;}bool pleft = isTree(p, root->left);bool pright = !pleft;bool qleft = isTree(q, root->left);bool qright = !qleft;//一个在左 一个在右if ((pleft && qright) || (pright && qleft)){return root;}//都在左就递归左if (pleft && qleft){return lowestCommonAncestor(root->left, p, q);}else{return lowestCommonAncestor(root->right, p, q);}}};

上面的方法效率不高,时间复杂度是一个等差数列,也就是 N 2 N^2 N2。寻找最近的公共节点还有其他方法,可以记录节点到根节点的路径,有了路径就成了前面的相交问题,让长的先走,然后不断出到交点位置返回,就是最近的公共节点

用一个栈,先压入根节点,然后递归左边,不断压入节点,左边没有就递归右边。如果这个结点的左右都不是,就弹出这个结点,返回上一层递归,找到节点就返回,不用继续往下走了
在这里插入图片描述
比如找4的路径,先压入3,递归压入5,6,6的左右都不是,弹出6,返回到递归5的右边,压入2,7,7不是弹出,压入4,4找到了,栈中的内容就是4的路径

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}* };*/
class Solution {
public:bool findpath(TreeNode* root, TreeNode* node, stack<TreeNode*>& s){if (root == nullptr){return false;}s.push(root);if (root == node){return true;}if (findpath(root->left, node, s)){return true;}if (findpath(root->right, node, s)){return true;}s.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> s1;stack<TreeNode*> s2;findpath(root, p, s1);findpath(root, q, s2);while (s1.size() != s2.size()){if (s1.size() > s2.size()){s1.pop();}else{s2.pop();}}while (s1.top() != s2.top()){s1.pop();s2.pop();}return s1.top();}  };

这时的时间复杂度就成了N

二叉搜索树与双向链表

二叉树转换双向链表
在这里插入图片描述
思路
这题不能用数组记录改变链接,因为空间复杂度是O(1),所以必须直接改变原链表的指向。这就是中序线索化的过程,用两个节点指针,一个cur当前节点,一个prev保存上一个节点,利用中序遍历,改变两个指针指向,prev的初始值是空,当prev不是空的时候,右结点指向cur,cur的左节点是prev,最后将cur赋值给prev。需要返回链表的头,根节点是中间位置,可以判断空不断取前驱,找到头返回

/*
struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {}
};*/
class Solution {
public:void pointconvert(TreeNode* cur, TreeNode*& prev){if (cur == nullptr){return;}pointconvert(cur->left, prev);cur->left = prev;if (prev){prev->right = cur;}prev = cur;pointconvert(cur->right, prev);}TreeNode* Convert(TreeNode* pRootOfTree) {TreeNode* pre = nullptr;pointconvert(pRootOfTree, pre);TreeNode* head = pRootOfTree;while (head && head->left){head = head->left;}return head;}
};

prev需要传引用,因为每层需要改变prev的指向位置

前序、中序遍历构建二叉树

构建二叉树
在这里插入图片描述
思路
前序和中序创建二叉树,需要不断用前序确定根,中序分割左右子树,前序记录一个下标,第一个数是二叉树的根,先创建节点,然后再中序中用根分割左右子树,所以还需要一个递归的左右区间,初始从数组范围开始,左边是0到根-1的位置,有边事根+1到最后一个数。根节点创建好,然后创建左子树的根节点,前序下标+1,就是第二个数,在左子树区间中继续查找分割左子树的左右区间。然后是右子树。如果区间不存在就直接返回

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),* right(right) {}* };*/
class Solution {
public:TreeNode* recbulid(vector<int>& preorder, vector<int>& inorder, int& prei,int begin, int end) {if (begin > end) {return nullptr;}int rooti = begin;while (rooti <= end) {if (preorder[prei] == inorder[rooti]) {break;}rooti++;}TreeNode* node = new TreeNode(preorder[prei++]);node->left = recbulid(preorder, inorder, prei, begin, rooti - 1);node->right = recbulid(preorder, inorder, prei, rooti + 1, end);return node;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int pflag = 0;return recbulid(preorder, inorder, pflag, 0, preorder.size() - 1);}
};

下面是部分递归展开图
在这里插入图片描述

二叉树的前序遍历

前序遍历
在这里插入图片描述

思路
循环方法实际上就是将递归改为循环。将每一个树都看做左节点和左节点的右子树。从根开始,先访问所有的左路节点,到叶子结点返回,用同样的方法访问每一个叶子结点的右子树,将右子树也看做左节点+左节点的右子树形式

用栈保存所有左路节点,cur记录当前节点,左路节点添加访问完,弹出一个访问右子树的左路

下面的树可以看做:
在这里插入图片描述

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),* right(right) {}* };*/
class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> s;TreeNode* cur = root;while (cur || !s.empty()) {// 遍历左路节点入栈while (cur) {v.push_back(cur->val);s.push(cur);cur = cur->left;}// 子问题方式访问左路节点的右子树TreeNode* top = s.top();s.pop();cur = top->right;}return v;}
};

二叉树的中序遍历

中序遍历
在这里插入图片描述

思路
中序和前序差不多,区别只是在于访问的顺序,中序是先左再根再右,所以访问应该在这个节点被弹出的时候

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),* right(right) {}* };*/
class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> s;TreeNode* cur = root;while (cur || !s.empty()) {while (cur) {s.push(cur);cur = cur->left;}TreeNode* top = s.top();s.pop();v.push_back(top->val);cur = top->right;}return v;}
};

二叉树后续遍历

后序遍历
在这里插入图片描述
后序顺序为先左再右,最后才是根,依然先访问左,要判断返回来的弹出节点能不能访问,有两种情况,如果右等于空。那么可以访问这个结点。可以用prev记录上一个访问了的结点,这个结点可以访问只有当右节点访问过了,右节点访问过了那prev一定是右节点

/*** Definition for a binary tree node.* struct TreeNode {*     int val;*     TreeNode *left;*     TreeNode *right;*     TreeNode() : val(0), left(nullptr), right(nullptr) {}*     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),* right(right) {}* };*/
class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> s;TreeNode* prev = nullptr;TreeNode* cur = root;while (cur || !s.empty()) {while (cur) {s.push(cur);cur = cur->left;}// 空或者右访问过了,可以访问当前节点TreeNode* top = s.top();if (top->right == nullptr || prev == top->right) {s.pop();v.push_back(top->val);prev = top;} else {cur = top->right;}}return v;}
};

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

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

相关文章

Linux:动态库和静态库的编译与使用

目录 1.前言 2.静态链接库 3.静态链接库生成步骤 4.静态链接库的使用 5.动态链接库 6.动态链接库生成步骤 7.动态链接库的使用 8.动态链接库无法加载 9.解决动态链接库无法加载问题 前言 在《MinGW&#xff1a;从入门到链接库》博客中简单介绍了如何编译动态链接库和静态链接库…

YOLOv5车流量监测系统研究

一. YOLOv5算法详解 YOLOv5网络架构 上图展示了YOLOv5目标检测算法的整体框图。对于一个目标检测算法而言&#xff0c;我们通常可以将其划分为4个通用的模块&#xff0c;具体包括&#xff1a;输入端、基准网络、Neck网络与Head输出端&#xff0c;对应于上图中的4个红色模块。Y…

Attendance Machine (KPI)

Attendance Machine &#xff08;KPI&#xff09; QQ机考勤机数据KPI

SecureCRT[po破] for Mac SSH终端操作工具[解] 安装教程

文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、 应用程序显示软件图标&#xff0c;表示安装成功 三、输入对应参数1、解决“软件已损坏&#xff0c;无法打开&#xff0c;要移到废纸篓”问题解决步骤…

【Python】pip 使用方法详解

目录 0 简介 1 pip 基本使用 1.1 安装 pip 1.2 卸载 pip 1.3 更新 pip 1.4 查看帮助 2 安装包 2.1 安装单个包 2.2 批量安装多个包 3 卸载包 4 使用镜像源 4.1 国内常用镜像源 4.1 单次安装设置镜像源 4.2 设置默认镜像源 0 简介 pip 是 python 官方的包管理工具…

一起学大模型 - 一起动笔练习prompt的用法

文章目录 前言一、代码演示二、代码解析1. 导入所需的库和模块&#xff1a;2. 设置日志记录和初始化模型&#xff1a;3. 定义一个函数用于清理GPU内存&#xff1a;4. 定义一个继承自LLM基类的QianWenChatLLM类&#xff0c;并实现对话生成的逻辑&#xff1a;5. 示例代码的主体部…

C++ | Leetcode C++题解之第137题只出现一次的数字II

题目&#xff1a; 题解&#xff1a; class Solution { public:int singleNumber(vector<int>& nums) {int a 0, b 0;for (int num: nums) {b ~a & (b ^ num);a ~b & (a ^ num);}return b;} };

安卓约束性布局学习

据说这个布局是为了解决各种布局过度前套导致代码复杂的问题的。 我想按照自己想实现的各种效果来逐步学习&#xff0c;那么直接拿微信主页来练手&#xff0c;用约束性布局实现微信首页吧。 先上图 先实现顶部搜索框加号按钮 先实现 在布局中添加一个组件&#xff0c;然后摆放…

2024 年最全的 21 款数据恢复工具软件汇总

使用其中任何一款免费数据恢复工具&#xff0c;您都可以找回那些您认为已经永远消失的文件。我根据这些程序对我而言的易用性和它们提供的功能对这些程序进行了排名。 这些应用程序从您的硬盘、USB 驱动器、媒体卡等恢复文档、视频、图像、音乐等。我建议每个计算机所有者都安装…

软件测试--Linux快速入门

文章目录 软件测试-需要掌握的Linux指令Linux命令操作技巧Linx命令的基本组成常用命令 软件测试-需要掌握的Linux指令 Linux命令操作技巧 使用Tab键自动补全上下键进行翻找之前输入的命令命令执行后无法停止使用CtrC,结束屏幕输出 Linx命令的基本组成 命令 [-选项] [参数] …

CANopen for Python

系列文章目录 前言 该软件包支持与 CANopen 节点网络交互。 注意 这里的大部分文档都是从 CANopen 维基百科页面上直接盗用的。 本文档正在编写中。欢迎反馈和修改&#xff01; CANopen 是用于自动化领域嵌入式系统的通信协议和设备配置文件规范。根据 OSI 模型&#x…

【Java】解决Java报错:ConcurrentModificationException

文章目录 引言1. 错误详解2. 常见的出错场景2.1 遍历过程中修改集合2.2 使用 Iterator 进行删除操作 3. 解决方案3.1 使用 Iterator 的 remove 方法3.2 使用 CopyOnWriteArrayList3.3 使用 synchronized 块 4. 预防措施4.1 使用线程安全的集合类4.2 使用合适的遍历和修改方法4.…

如何在没有密码的情况下解锁iPhone

通常&#xff0c;您可以使用密码、FaceID 或 Touch ID 轻松解锁 iPhone。但是&#xff0c;有时您可能会忘记密码、iPhone 已停用或您的二手手机已锁定。在这种情况下&#xff0c;您必须绕过 iPhone 密码才能访问您的设备。在本文中&#xff0c;我们将向您介绍 5 种经过测试的方…

【多模态/CV】图像数据增强数据分析和处理

note 多模态大模型训练前&#xff0c;图片数据处理的常见操作&#xff1a;分辨率调整、网格畸变、水平翻转、分辨率调整、随机crop、换颜色、多张图片拼接、相似图片检测并去重等 一、分辨率调整 from PIL import Image def resize_image(original_image_path, save_image_p…

mysql 8 linux7,8安装教程

选择自己对应的linux版本 cat /etc/os-release //查看自己linux系统版本 1.mysql下载地址 MySQL :: Download MySQL Community Server (Archived Versions) 拉到下面找到 选择自己linux指定的版本&#xff0c;否则会很麻烦 cat /etc/os-release //查看系统版本 2.查…

50etf期权怎么开户?期权懂有几种方式?

今天带你了解50etf期权怎么开户&#xff1f;期权懂有几种方式&#xff1f;50ETF期权开户可以通过证券公司、期权交易平台或期权交易应用进行。投资者需填写开户申请表格&#xff0c;提供身份证明和其他资料&#xff0c;完成开户手续。 50etf期权怎么开户&#xff1f; 满足资金…

欢乐钓鱼大师辅助:哪家云手机自动钓鱼更好操作!

在探索《欢乐钓鱼大师》的世界时&#xff0c;我们不得不提到一个强大的游戏辅助工具——VMOS云手机。通过VMOS云手机&#xff0c;你可以轻松实现自动钓鱼&#xff0c;让游戏体验更加便捷高效。 什么是VMOS云手机&#xff1f; VMOS云手机是一款基于虚拟机技术的云端工具&#…

ubuntu20.04中设置包含ros节点的文件自启动

若文件里包含了ros话题的发布和接收&#xff0c;那么设置自启动时&#xff0c;应该首先将roscore设置为自启动。 首先确保roscore有一个systemd服务文件。如果还没有&#xff0c;需要在/etc/systemd/system/下创建一个。例如&#xff0c;一个基本的roscore.service文件可能如下…

安徽代理记账公司的专业服务和创新理念

在当今竞争激烈的市场环境中&#xff0c;为了提升企业的运营效率&#xff0c;许多企业开始寻找专业的代理记账公司进行财务管理和记账&#xff0c;本文将介绍一家名为安徽代理记账公司的专业服务和创新理念。 安徽代理记账公司是一家专注于为企业提供全方位会计服务的公司&…

SwiftUI中Mask修饰符的理解与使用

Mask是一种用于控制图形元素可见性的图形技术&#xff0c;使用给定视图的alpha通道掩码该视图。在SwiftUI中&#xff0c;它类似于创建一个只显示视图的特定部分的模板。 Mask修饰符的定义&#xff1a; func mask<Mask>(alignment: Alignment .center,ViewBuilder _ ma…