【C++】搜索二叉树
目录
1.搜索二叉树概念
1.1搜索二叉树认知
1.2搜索二叉树结构
1.3中序遍历
2.查找
2.1非递归实现:
2.2递归实现
3.插入实现(insert)
3.1非递归实现
3.2递归实现
4删除(Erase)
4.1非递归实现
4.2递归实现
5.二叉树的默认成员函数
5.1构造函数
5.2拷贝构造
5.3赋值运算符重载
5.4析构函数
6.二叉树的模型搜索树
6.1K模型搜索树
6.2KV模型搜索树
7.前序,中序,后序回顾
各位想看代码去我的码云仓库看吧,这里就不在粘贴了。
https://gitee.com/j-jun-jie/c---advanced.githttps://gitee.com/j-jun-jie/c---advanced.git
1.搜索二叉树概念
1.1搜索二叉树认知
二叉搜索树(搜索二叉树)又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
- 它的左右子树也分别为二叉搜索树。
也就是说左子树的值<根节点<右子树的值。
那我们再看一下这两个
都不行,第一个,16的左子树的任何值都要小于16,17>16不行。
第二个。19<28没问题。但是20的右子树要大于20,19<20也不行。
- 如果我们按左子树,根,右子树顺序排列,那这个一定是升序排列(左<根<右)所以我们进行中序排列。
1.2搜索二叉树结构
在C语言中我们实现二叉树首先是定义一个结构体存放根节点的左右指针和数值。然后将函数名和功能实现分开实现。
typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struct BinaryTreeNode* right; }BTNode;
但是现在我们学到了类我们换种花样。
我先定义一个结构体存放根节点的左右指针和数值。
第二,用类用来实现二叉树的功能函数(增删查改)。
template<class K> struct BSTreeNode {BSTreeNode<K>* _left; //左指针BSTreeNode<K>* _right;//右指针K _key;//节点值BSTreeNode(const K& key)//构造函数:_left(nullptr), _right(nullptr), _key(key){} }; template<class K> class BStree//树结构 {typedef BSTreeNode<K> Node; public://构造函数只需要将根初始化为空就行了BSTree():_root(nullptr){}private:Node* _root;//根//Node* _root(nullptr); 构造函数不用写了 };
这段代码使用了C++中的模板(template)机制,它允许我们编写通用的数据结构或算法,以支持多种数据类型。
第一次出现template是在定义BSTreeNode结构体时,它使用了一个类型参数K,表示节点的值的类型可以是任意类型。这样,我们可以在创建树的实例时,指定具体的节点值类型,例如int、double、string等。
第二次出现template是在定义BStree类时,它也使用了一个类型参数K,表示整个二叉搜索树中的节点值类型。这个类型参数和BSTreeNode结构体中的类型参数是一样的,它们是共享的。
在使用模板时,通常需要在定义和实现中都使用template关键字,并在尖括号中指定类型参数。这样,编译器就能够根据实际类型生成特定的代码。
1.3中序遍历
当我们写好插入删除等功能后,我们可以用中序遍历进行打印,因为他的遍历方式是左节点->根->右节点,而搜索二叉树正好是左,根,右升序排列,所以我们用中序遍历。
void _InOrder(Node* root) //中序遍历。但是这个必须传参,参数是私有的,不能用,那咋办?套一层{if (root == nullptr){return ;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}
但是要想调用这个函数,我们要给他传参就是传过去_root,但是这个_root是私有成员,没办法出了类使用,那我们咋办,
我们可以在共有区域里在写一个不需要参数的函数在这个函数里面调用这个函数。
void InOrder(){_InOrder(_root);cout << endl;}
这样就不需要传参了。就可以实现遍历了。
2.查找
2.1非递归实现:
思路清晰又简单:
- 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
- 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
- 若key值等于当前结点的值,则查找成功,返回true。
- 若遍历一圈cur走到nullptr了说明没有此结点,返回false
//Find bool Find(const K& key) {Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;//若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。}else if (cur->_key > key){cur = cur->_left;//若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。}else{return true;//若key值等于当前结点的值,则查找成功,返回true。}}return false;//没找到返回false }
2.2递归实现
递归实现也是需要注意前几步:
- 如果是空树查找失败,返回nullptr.
- 若key值小于当前结点的值,则递归到该结点的左子树当中进行查找。
- 若key值大于当前结点的值,则递归到该结点的右子树当中进行查找。
- 若key值等于当前结点的值,则查找成功,返回对应结点的地址。
我们在这也可以向上面的中序遍历一样再套一个函数,这样既能隐藏递归函数,也能在类外调用。
bool FindR(const K& key){return _FindR(_root, key);} private: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);elsereturn true;}
这里的_FindR定义在私有区里,FindR定义在共有区里面,我们到下面在解释。
3.插入实现(insert)
3.1非递归实现
- 要插入二叉树中就要找位置:
如果是空树:直接插入,把插入的节点作为根节点。
- 不是空树,待机而动。
要实现插入,首先做好准备工作,cur指针从节点开始进行移动,直到插入合适位置,parent在cur移动时到cur的位置(相当于他的父节点)起到插入后的连接作用。key是要插入的节点值。
- 节点key<当前节点 ,parent到cur位置,cur左移,继续遍历。
- 节点key>当前节点 ,parent到cur位置,cur右移,继续遍历。
- 节点key=当前节点,返回false,因为二叉树中不允许有重复值。
bool Insert(const K& key){if (_root == nullptr)//若二叉树树为空{_root = new Node(key);//创造一个值为key的新节点return true;}Node* parent = nullptr;Node* cur = _root;//1、找位置while (cur){if (cur->_key < key)//_key节点的值,key是要插入的值{parent = cur;cur = cur->_right;//让cur往右走继续遍历}else if (cur->_key > key)//若key小于当前结点值{parent = cur;cur = cur->_left;//让cur往左走}else{return false;//若key等于当前结点值,说明插入的值不合法,返回false}}//2、链接cur = new Node(key);if (parent->_key < key){parent->_right = cur;//比父亲的值大连接在右子树}else{parent->_left = cur;//比父亲的值小链接在左子树}return true;}
3.2递归实现
递归实现的思路和上面大差不差,递归到合适的位置,然后在链接。
步骤根上面的一样。
- 若key > root指向的结点值,让root递归到右子树继续遍历。
- 若key < root指向的结点值,让root递归到左子树继续遍历。
- 若key = root指向的结点值,说明待插入的结点值与此树当前结点值重合,插入结点失败。返回false。
//插入 bool _InsertR(Node*& root, const K& key){if (root == nullptr)//找到位置了{root = new Node(key);return true;}if (key < root->_key)//到左子树去找位置{_InsertR(root->_left, key);}else if (key > root->_key)//到右子树去找位置{_InsertR(root->_right, key);}else//已存在,无需插入{return false;}}
root为啥要传引用?不能直接用指针?
当我们经历一些步骤到14的右子树处准备插入时,root是_root的别名,而最后一步递归是root->right,也就是说我们修改也只会修改_root->右子树,所以直接链接起来了。
4删除(Erase)
4.1非递归实现
- 1.找位置 (小于节点值往左走,大于往右走)
- 2.删除 --链接(递归实现,非递归实现)
在进行链接时会有两种情况:
- 1.删除的时叶子节点,下面没有节点了。
- 2.删除的节点还有孩子节点。
1.有一个孩子节点如图
就把该节点的孩子节点的链接给该节点的父亲,顶替自己的位置。
2.有两个孩子节点:
那我们就要找左孩子节点中的最大值或者右孩子节点中的最小值进行替换。
替换步骤如下:
- 定义myParent指针为cur指针的位置(myParent指针用于链接要删除结点的孩子)。
- 定义minRight指针为cur的右孩子结点指针的位置(minRight用于找到右子树的最小值)。
- 遍历minRight找到待删结点右子树的最小值(或左子树的最大值结点),中途不断更新myParent。
- 找到后,利用swap函数交换此最小值结点的值(minRight->_key)和待删结点的值(cur->_key)。
- 交换后,链接父亲myParent指针与minRight结点的孩子。
- 最后记得delete删除minRight结点。
其实这个就是往一个孩子节点或者没有孩子节点上转换,为啥要找要删除节点的左孩子的最大右孩子节点或者右孩子的最小左孩子节点。
替换节点要满足两个条件:
- 1.替换节点要接近删除节点,要不不一定满足这个替换节点大于删除节点的左孩子,小于删除节点的右孩子。
- 2.例如最小左孩子节点要么他有叶子节点,要么它有一个右孩子节点,那这个就符合上面的有一个孩子节点或者没有孩子节点的情况了,这就带回去了。
如果遍历一遍都找不到要删除的值,就说明该数不存在,就返回false。
我们先写一下只有其中一个孩子节点的。但是会出现以下情况:
- 首先我们要先判断待删除节点位于父节点的左边还是右边,就比如上面这两种情况,我们不能把cur的右子节点随意链接父节点。
//删除函数非递归实现bool Erase(const K& key){Node* parent = nullptr; //起链接作用Node* cur = _root; //节点移动的while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;//若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。}else if (cur->_key > key){parent = cur;cur = cur->_left;//若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。}else// 找到了,要删除{//1.左孩子为空//2.右孩子为空//3.左右孩子都不为空if (cur->_left == nullptr) //删除节点的左孩子为空{if (cur == parent->left) //cur在父节点的左边{parent->_left = cur->_right; //把cur的右节点连接到父节点的左节点处。}else{parent->_right = cur->_right; //连接到右节点处}}//2.右孩子为空else if (cur->_right == nullptr){if (cur == parent->left) //cur在父节点的左边{parent->_left = cur->_left; //把cur的左节点连接到父节点的左节点处。}else{parent->_right = cur->_left; //连接到右节点处}}//3.左右孩子都不为空}}}
- 这是其中的普通情况,来个极端情况,删除父节点。
此时parent时空指针,在调用其他就错,所以我们还要再来一步,判断是不是删除根节点.
else// 找到了,要删除{//1.左孩子为空//2.右孩子为空//3.左右孩子都不为空if (cur->_left == nullptr) //删除节点的左孩子为空{//要删除根节点if (cur == _root){_root = cur->_right;}else //删除其他节点{if (cur == parent->left) //cur在父节点的左边{parent->_left = cur->_right; //把cur的右节点连接到父节点的左节点处。}else{parent->_right = cur->_right; //连接到右节点处}}delete cur;}//2.右孩子为空else if (cur->_right == nullptr){if(cur==_root){_root = cur->_left; //根节点右孩子为空,就指向他的左孩子}else{if (cur == parent->left) //cur在父节点的左边{parent->_left = cur->_left; //把cur的左节点连接到父节点的左节点处。}else{parent->_right = cur->_left; //连接到右节点处}}delete cur;}
- 那这两种情况弄完就差左右孩子都存在了。
但是删除时会出现这个情况:
我找到了最小左节点时,但是它下面还有一个右孩子。所以我们用minparent来连接这个右孩子。
//3.左右孩子都不为空else{Node* minparent = nullptr; Node* minright = cur->_right;while(minright->_left) //找最小左节点{minparent = minright;minright = minright->_left;}swap(minright->_key, cur->_key);minparent->_left = minright->_right; //把最小左节点的右节点给父节点的左节点delete minright;}
这个思路是找到最小左孩子后交换待删除数和这个左孩子,然后删除这个最小左孩子。
- 但是如果要删除根节点8呢?
此时10是最小左节点 ,,所以下面的while就不会进入,那minparen他就又是空指针了。所以我们可以minparent=cur。但是我们看倒数第二步,minright是10,minparent是根节点8,这一步直接把10的右节点连接到根节点的左边了,这就错了,他要连接到右边。之所以出现这一步是因为10并没有左节点,所以就不能连接到左节点处。所以我们要先判断一下到底在哪边。
else{Node* minparent = cur; //赋值为空时,删除根节点时就错了,一步到位赋值curNode* minright = cur->_right;while(minright->_left) //找最小左节点{minparent = minright;minright = minright->_left;}swap(minright->_key, cur->_key);if (minparent->_left == minright) //如果minright在minparent的左边{minparent->_left = minright->_right; //把他连接到左边}else{minparent->_right = minright->_right;}delete minright;
直接把没有孩子的情况当作情况2的一种特殊类型处理了。
我们来一份完整的删除代码:
//删除函数非递归实现bool Erase(const K& key){Node* parent = nullptr; //起链接作用Node* cur = _root; //节点移动的while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;//若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。}else if (cur->_key > key){parent = cur;cur = cur->_left;//若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。}else// 找到了,要删除{//1.左孩子为空//2.右孩子为空//3.左右孩子都不为空if (cur->_left == nullptr) //删除节点的左孩子为空{//要删除根节点if (cur == _root){_root = cur->_right;}else //删除其他节点{if (cur == parent->_left) //cur在父节点的左边{parent->_left = cur->_right; //把cur的右节点连接到父节点的左节点处。}else{parent->_right = cur->_right; //连接到右节点处}}delete cur;}//2.右孩子为空else if (cur->_right == nullptr){if(cur==_root){_root = cur->_left; //根节点右孩子为空,就指向他的左孩子}else{if (cur == parent->_left) //cur在父节点的左边{parent->_left = cur->_left; //把cur的左节点连接到父节点的左节点处。}else{parent->_right = cur->_left; //连接到右节点处}}delete cur;}//3.左右孩子都不为空else{Node* minparent = cur; //赋值为空时,删除根节点时就错了,一步到位赋值curNode* minright = cur->_right;while(minright->_left) //找最小左节点{minparent = minright;minright = minright->_left;}swap(minright->_key, cur->_key);if (minparent->_left == minright) //如果minright在minparent的左边{minparent->_left = minright->_right; //把他连接到左边}else{minparent->_right = minright->_right;}delete minright;}return true;}}return false;}
4.2递归实现
一,思路和非递归基本一致,多次递归找到合适的删除位置:
非递归找合适的删除位置时用到了遍历,我们在这用递归更方便。
- 若当前结点root为空,说明此删除的结点不存在,返回false
- 若key > root指向的结点值,让root递归到右子树继续遍历。
- 若key < root指向的结点值,让root递归到左子树继续遍历。
二,找到待删数值进行链接时我们也会遇到两种情况:
1.待删除数有一个子树--------左子树,右子树,左右为空。
- 我们先将待删除的root放在del中保存起来。
- 判断root的左孩子存在还是右孩子(rright)存在。_root是root父节点,我们再把rright连接在_root的右节点(_rright)处。只要root大于_root,我们就把root的子树连接到_root的右子树处。在连接时&会直接帮我们进行两个结点的链接,我们不需要操心。
- 如果root左子树为空:执行root = root->_right。
- 如果root右子树为空:执行root = root->_left。
我们删除真正实现的是,让root(待删除数)的父节点直接链接root的子节点,把root跳过就是删除操作了。
2.待删数值子树全部存在。
这个跟非递归实现几乎一毛一样。
- 先用del保存root的值,设置一个minright保存root的右子树的最小值。
- 遍历minright找到最小值。
- 利用交换函数swap交换minright->key和root->key。
- 交换后利用递归进行删除minrght。
就是最后那个交换后利用递归删除可能有点麻烦。
//递归实现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;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(minright->_key, root->_key);return _EraseR(key);}delete del;}}
那各位看一下我的左右节点都在那个情况,交换完之后利用递归删除minright思路对不对?
不对
按照刚才的思路,4,3交换完之后,我们重新调用递归删除,但是我们找一下3在哪,3<8,往左走,3<4往左走,3>1,往右走,那3现在在1的右孩子处,不是6的左孩子处了,所以我们应该所以调用下面的递归是我们要让它指向root的右孩子处。
//递归实现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;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; //让minright成为最小左节点}swap(minright->_key, root->_key);return _EraseR(root->_right,key);}delete del;return true;}}
5.二叉树的默认成员函数
5.1构造函数
构造函数我们可以让编译器直接默认生成即可。但是如果写了拷贝构造函数它就不在默认生成了。这里有两种解决方法。
1.我们强制让他默认生成:
//强制编译器自己生成构造函数,忽视拷贝构造带来的影响 BSTree() = default;//C++11才支持
2.我们自己写一个构造函数
public://构造函数需要将根初始化为空就行了BSTree():_root(nullptr){}
就比如我们节点的构造中写到的。
5.2拷贝构造
一般二叉树基本不用拷贝构造函数,效率低不说,空间浪费太大了。
此时我们直接用前序递归的方式创建一颗与原来一样的二叉树。再用CopyT进行一系列的封装实现。
Node* CopyT(Node* root) {if (root == nullptr)return nullptr;Node* copyNode = new Node(root->_key);//拷贝根结点//递归创建拷贝一棵树copyNode->_left = CopyT(root->_left);//递归拷贝左子树copyNode->_right = CopyT(root->_right);//递归拷贝右子树return copyNode; } //拷贝构造函数--深拷贝 BSTree(const BSTree<K>& t) {_root = t.CopyT(t._root); }
5.3赋值运算符重载
要实现t1,t2的赋值操作,那我们可以利用一下上面的拷贝构造函数。当t2传值传参时我们进行拷贝构造生出t,让后在交换t1和t的根节点即可。
//赋值运算符重载函数 BSTree<K>& operator=(BSTree<K> t) {//现代写法swap(_root, t._root);return *this; }
5.4析构函数
历经了数年,就连年年出现在英语中的李华都考上大学了,但是析构函数的功能还是没有任何变化。释放二叉树的所以结点。这里我们采用后序遍历方式进行展开 。
void ~DestoryTree(Node* root) {if (root == nullptr)return;//通过递归删除所有结点~DestoryTree(root->_left);//递归释放左子树中的结点~DestoryTree(root->_right);//递归释放右子树中的结点delete root; } //析构函数 ~BSTree() {~DestoryTree(_root);//复用此函数进行递归删除结点_root = nullptr; }
6.二叉树的模型搜索树
6.1K模型搜索树
咱上面的这个二叉搜索树就是K模型,所以我不在写代码了。
K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。在K模型中不存在重复值(本来树就复杂,你小子还重复)。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
- 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
- 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
6.2KV模型搜索树
KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
- <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key。
- 查询英文单词时,只需给出英文单词,就可快速找到与其对应的key。
KV模型可以插入重复值,在K模型的基础上,节点增加了_value成员,用来_key去查找_value,_value的类型不确定,再增加一个模板参数即可。
namespace key_value {void TestBSTree1(){BSTree<string, string> Dict;Dict.InsertR("zuozishu", "左子树");Dict.InsertR("二叉树", "二叉树");Dict.InsertR("left", "左边");Dict.InsertR("right", "右边");string str;while (cin >> str){//BSTreeNode<string, string>* ret = Dict.FindR(str);auto ret = Dict.FindR(str);if (ret != nullptr){cout << "对应的中文:" << ret->_value << endl;}else{cout << "未找到,请重新输入" << endl;}}} } int main() {key_value::TestBSTree1(); }
7.前序,中序,后序回顾
深度优先遍历有3种:
- 前序遍历(先根遍历) 根->左->右
- 中序遍历(中根遍历) 左->根->右
- 后序遍历(后根遍历) 左->右->根
广度优先遍历有1种:
- 层序遍历 :一层一层遍历
【C++】搜索二叉树相关推荐
- 链表问题15——将搜索二叉树转换成双向链表
题目 将一颗搜索二叉树按照中序遍历(左-中-右)的顺序,变成一个有序的双向链表. 所谓的先序遍历.中序遍历和后序遍历都针对的是中间的节点也就是根节点,先序遍历为中-左-右,后序遍历为左-右-中 思路 ...
- 判断一个数列是不是搜索二叉树后续遍历输出的结果
剑平面阿里被问到这个,刚开始画了下看有什么性质,乱蒙了几个都被推翻了,初始感觉就是要O(n)的,因为印象中BST的构树都可以O(nlogn)搞定.然后剑平说最后一个数肯定是根节点,一下反应过来了,就是 ...
- 调整搜索二叉树中两个错误的节点
一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树,请找到这两个错误节点并返回.已知二叉树中所有节点的 值都不一样,给定二叉树的头节点 head,返回一个长度为 ...
- 判断一棵二叉树是否为搜索二叉树、完全二叉树、平衡二叉树(java)
平衡二叉树的解法:主要是求出二叉树的高度,若根节点的左子树的高度与右子树的高度差小于等于1,则表示该二叉树为平衡二叉树 public static class Node{public int valu ...
- 找到二叉树中符合搜索二叉树条件的最大拓扑结构
找到二叉树中符合搜索二叉树条件的最大拓扑结构 给定一棵二叉树的头节点 head,已知所有节点的值都不一样,返回其中最大的.且符合搜索二叉树 条件的拓扑结构的节点数.这里的拓扑结构是指,你可以在二叉树中 ...
- 寻找搜索二叉树中两个错误的节点
题目:一颗二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这颗二叉树不再是搜索二叉树,请找到两个错误节点并返回.已知二叉树中所有节点的值都不一样,给定二叉树的头节点head,返回一个长度为2 ...
- 通过有序数组生成平衡搜索二叉树
题目:给定一个有序数组sortArr,已知其中没有重复值,用这个有序数组生成一颗平衡搜索二叉树,并且该搜索二叉树中序遍历的结果与sortArr一致 思路:用有序数组最中间的数生成搜索二叉树的头节点,然 ...
- 根据后续数组重建搜索二叉树
题目: 给定一个整型数组arr,已知其中没有重复值,判断arr是否可能是节点值类型为整型的搜索二叉树后续遍历的结果 思路:根据搜索二叉树的性质,比后续数组最后一个元素值小的数组会在数组的左边,比数组最 ...
- 将搜索二叉树转换成双向链表
题目: 对二叉树的节点来说,有本身的值域,有指向左孩子和右孩子的两个指针:对双向链表的节点来说,有本身的值域,有指向上一个节点和下一个节点的指针.在结构上,两种结构有相似性,现在有一棵搜索二叉树,请将 ...
- 判断一颗二叉树是否为搜索二叉树和完全二叉树
搜索二叉树:二叉树的中序遍历,在遍历的过程中,节点值都是递增的 class Node(object):def __init__(self,value):self.value = valueself.r ...
最新文章
- 强碱性食品 高嘌呤食物
- 网站运营之做到SEO操作视频教程【21讲】
- h3c GR5200路由器上如何设置公网ip可以访问
- 卷组删除pv_如何安全的删除Linux LVM中的PV物理卷(硬盘或分区)
- leetcode - 39. 组合总和
- ExtJS2.0实用简明教程 - Form布局
- 程序员如何保护自己?
- win10运行在哪里_90s安装新一代win10X!全新操作界面,完美兼容win7或win10程序
- 迷宫问题的通用解法C语言数据结构实现
- Centos配置github
- “差不多先生”姚劲波和不再神奇的58同城
- 电商项目——分布式基础概念和电商项目微服务架构图,划分图的详解——第二章——上篇
- 慧荣SM2258XT-主板CE跳线Toggle土狗跳线合集整理(持续更新)
- Java的图标和由来
- oracle12c安装过程
- 东北农业大学考研计算机大纲,东北农业大学339农业知识综合一考研大纲
- VLDB 历年最佳论文汇总
- strstr函数 C++
- Linux中 screen命令下 实现屏幕滚动
- 无线通信基础无线信道的统计描述(二)
热门文章
- 谷歌地图 替代_Google地图的替代品
- 看4D电影,挨了一顿毒打
- 骨龄预测代码学习(一)
- k8s滚动更新(六)--技术流ken
- 跳转指令: JMP、JECXZ、JA、JB、JG、JL、JE、JZ、JS、JC、JO、JP 等
- 《数据库原理》学生表,课程表,选课表的相关内容
- 我的世界工业时代java_我的世界:利用这些道具就能进入“工业时代”?萌新可以试一试!...
- <<算法很美>>——(三)十大排序算法(上)
- 【论文导读】-GCLSTM graph convolution embedded LSTM for dynamic network link prediction用于动态网络边预测的图卷积嵌入LSTM
- maven快速入门第十七讲——从私服下载jar包到本地仓库