当前位置: 首页 > wzjs >正文

南京建设网站多少钱网店培训

南京建设网站多少钱,网店培训,网站建设seo优化推广,服务公司理念目录 前言 一、二叉搜索树的概念 二、二叉搜索树的效率 三、二叉搜索树的模拟实现 1.基本框架 2.二叉搜索树的插入 3.二叉搜索树的查找 4.二叉搜索树的删除 5.二叉搜索树的输出打印 四、完整代码和使用演示 总结 前言 学习二叉搜索树是为了给后面学习 AVL树 和 红黑树 打基础的…

  • 目录

    前言

    一、二叉搜索树的概念

    二、二叉搜索树的效率

    三、二叉搜索树的模拟实现

    1.基本框架

    2.二叉搜索树的插入

    3.二叉搜索树的查找

    4.二叉搜索树的删除

    5.二叉搜索树的输出打印

    四、完整代码和使用演示

    总结



前言

        学习二叉搜索树是为了给后面学习 AVL树红黑树 打基础的,因此还是比较重要的。


一、二叉搜索树的概念

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

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值
  • 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值
  • 它的左右子树也分别为二叉搜索树
  • 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等值,multimap/multiset支持插入相等值

因为这些性质,使得其在查找数据时效率较高

二叉搜索树示意图:

左:不支持相等值        右:支持相等值

简单来说:二叉搜索树的每一个节点,它的左子树节点全是小于等于它的,它的右子树节点全是大于等于它的。关于等于,取决于该二叉搜索树是否支持相等值。

关于排序:

前面所说,二叉搜索树又称二叉排序树,这是因为二叉搜索树输出时一般按照中序遍历输出

  • 如上图左边树按照中序遍历:1->3->4->6->7->8->10->13->14
  • 右边树按照中序遍历:1->3->3->6->7->8->10->10->13

很明显,二叉搜索树中的数据按照中序输出就是有序的。


二、二叉搜索树的效率

最优情况下:二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为: 。效率最好。

如:此时可以通过与根节点大小比较快速锁定目标节点


 最差情况下:二叉搜索树退化为单支树(或者类似单支),其高度为: N

如:此时效率最低,假如查找1,基本遍历了所有数据 


总结: 

  • 所以综合而言二叉搜索树增删查改时间复杂度为: O(N)
  • 根据上图很容易发现二叉搜索树的缺陷,这样的效率显然是无法满足我们需求的,我们后续课程需要继续讲解二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。

补充说明:

需要说明的是,二分查找也可以实现 级别的查找效率,但是二分查找有两大缺陷:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

这里也就体现出了平衡二叉搜索树的价值。


三、二叉搜索树的模拟实现

注:我们先主要实现不支持相等数据的二叉搜索树

1.基本框架

//定义节点
template<class K>
struct BSNode
{K _key;BSNode<K>* _left;BSNode<K>* _right;BSNode(const K& key)//构造:_key(key), _left(nullptr), _right(nullptr){}
};//二叉搜索树
template<class K>
class BSTree
{typedef BSNode<K> Node;//重命名方便书写
public:private:Node* _root = nullptr;
};
  • 关于节点,_key用于存储数据, _left 和 _right 分别是左子树和右子树的节点指针。
  • 搜索二叉树的成员变量只要一个,就是根节点。
  • 注意上面模版参数都是 K,因为该模板参数主要控制 _key 的类型。


2.二叉搜索树的插入

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给_root指针
  2. 树不为空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
  3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑一致性,插入相等的值不要⼀会往右走,一会往左走)

比如:在下面二叉搜索树中插入 9

9比8大,所以往8的右子树走,9比10小,所以往10的左子树走,10的左子树为空,所以9应该插入这里。

代码实现:

bool Insert(const K& key)
{//树为空,直接作为根节点if (_root == nullptr){_root = new Node(key);return true;}//树不为空,需要查找插入位置//查找需要两个指针,cur用于找到目标位置,perent用于标记cur的父节点//这样就可以链接新节点Node* perent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key)//比当前值大,往右子树找{perent = cur;//先更新父节点cur = cur->_right;//走到右孩子节点}else if (key < cur->_key)//比当前值小,往左子树找{perent = cur;//先更新父节点cur = cur->_left;//走到左孩子节点}else{return false;//如果是相等值,则插入失败,因为我们是实现不支持重复值的}}//走到这里,说明cur找到了cur = new Node(key);//先创建结点//使用父节点进行链接,因为不确定是父节点的左孩子还是右孩子//所以需要单独判断,比较插入值与父节点值的大小即可,大插右,小插左if (key > perent->_key){perent->_right = cur;}else{perent->_left = cur;}return true;//插入成功返回true
}
  • 代码实现中给出了详细注释,注意查看。
  • 插入操作相对较简单


3.二叉搜索树的查找

不知道你有没有发现,其实在插入操作中,我们同样进行了查找操作,我们需要根据参数找到其在二叉树中的位置。作业关于二叉树的查找操作,可以直接复制插入操作。

代码实现:

//查找
bool Find(const K& key)
{Node* cur = _root;//cur还是用于遍历查找while (cur)//查找策略与插入查找一致,不再赘述{if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return true;}}return false;
}
  • 查找简单点说就是,大往右,小往左
  • 因为查找就是寻找相等值,以上是不支持数据重复。如果支持重复数据,那么返回的就是重复数据在中序遍历中的第一个。


4.二叉搜索树的删除

注:删除应该是二叉搜索树中最复杂的一部分了

删除操作主要复杂在,当我们删除一个节点后,我们需要继续保持搜索二叉树的性质,那么删除一个节点后就需要对树进行调整。

示例1: 

  • 如上图,如果我们删除1,那么结构是不受影响的
  • 当我们删除10时,我们就需要进行调整,此时我们可以将10的右子树替代10原来的位置

删除后:

根据以上两种情况,可以总结两条情况:

  1. 如果删除节点的左右孩子都为空,那么该节点可以直接删除
  2. 如果删除节点的左孩子为空,那么可以使用该节点的右孩子代替原位置

示例2:

  • 删除14,我们可以用14的左子树代替原来位置
  • 所以,当删除节点的右子树为空时,我们可以将其左孩子代替原节点

示例3:

  • 当删除节点的左右子树都不为空时,这时候就比较棘手了
  • 解决方法就是,找出删除节点左子树中最大值,或者找出删除节点右子树中最小的值,只有满足这两种情况的值,才能替换删除节点,保持搜索二叉树的性质

如:找出 3 右子树中最小的值进行替换


总结:

  • 将以上情况的解决方法总结一下,情况1和2,3其实可以合并在一起
  1. 左子树为空:把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
  2. 右子树为空:把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点
  3. 左右子树都不为空:无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点 R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。

左右子树都为空的情况没有单独讨论,因为它可以在左子树为空或者右子树为空的情况中被解决。左右孩子都为空,那么也可以说用其左右任意空指针代替原位置然后删除。

代码实现:

//删除
bool Erase(const K& key)
{Node* cur = _root;//cur遍历查找目标位置Node* perent = nullptr;//跟踪记录cur的父节点while (cur){if (key > cur->_key){perent = cur;cur = cur->_right;}else if (key < cur->_key){perent = cur;cur = cur->_left;}else{//找到了//1.左子树为空if (cur->_left == nullptr){if (cur == _root)//如果删除节点为根节点{_root = cur->_right;//用不为空的右子树替代}else//不是根节点{//需要单独判断,确认cur是其父节点左右哪个孩子节点if (perent->_left == cur){perent->_left = cur->_right;//更新父节点的左孩子,变相删除了cur}else if (perent->_right == cur){perent->_right = cur->_right;}}delete cur;//替换后,释放节点return true;}else if (cur->_right == nullptr)//2.右子树为空{if (cur == _root)//根节点依旧要单独判断{_root = cur->_left;}else{if (perent->_left == cur){perent->_left = cur->_left;}else if (perent->_right == cur){perent->_right = cur->_left;}}delete cur;return true;}else//3.左右子树都不为空{//找右子树最小节点Node* minRight = cur->_right;//minRight查找最小节点Node* minRightPerent = cur;//跟踪记录minRight的父节点while (minRight->_left)//一直遍历左孩子就能找到最小节点{minRightPerent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;//找到节点后先替换目标值//依旧需要确认是父节点的哪个孩子if (minRightPerent->_left == minRight){minRightPerent->_left = minRight->_right;//替换,变相删除}else{minRightPerent->_right = minRight->_right;}delete minRight;//释放节点return true;}}}return false;//没有找到,返回false
}

注释解释了大部分代码,但还有一些问题在这里解释:

  • 1.为什么在左子树为空的情况下,或者右子树为空的情况下,如果查找到的是根节点需要单独判断,这是因为此时父节点perent=nullptr,因为cur是根节点所以导致perent无法更新,因此需要单独判断这种情况。
  • 2.为什么左右子树都不为空的情况下,又不需要单独判读根节点情况,因为我们在初始化时,让 minRightPerent=cur,所以不存在父节点为空的情况。
  • 3.那为什么 perent 不初始化为 _root,你可以自己代入查看一下,行不通。
  • 4.perent 和 minRightPerent 都是为了记录要删除节点的父节点,这样方便删除和链接节点,但是我们并不能确认究竟是父节点的哪一个节点是要删除的节点,即便有一个节点为空,我们也不能提前得知,所以在涉及更新父节点的孩子节点时,都需要判断一下。


5.二叉搜索树的输出打印

  • 因为二叉搜索树又称二叉排序树嘛,所以输出是按照中序输出。
  • 关于中序遍历有任何问题可以查看我以前关于二叉树的文章。

问题:

  • 中序遍历是需要根节点的,而根节点 _root 属于私有成员,外界不能访问。

解决方案:

  • 为了确保类的封装性,我们不能将 _root 公开,这里有两种方法解决:
  1. 增加一个 GetRoot 函数获取根节点。
  2. 将中序打印函数写在私有限定符里,并在公有限定符中再封装一层,利用类成员函数可以直接访问私有成员变量,这样外面调用中序打印时,就可以不用传参直接使用了。

很明显,第二种方案更有优势。

代码实现:

//二叉搜索树
template<class K>
class BSTree
{typedef BSNode<K> Node;
public://中序遍历void InOrder(){_InOrder(_root);cout << endl;}private://中序打印void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};
  • 经过一层封装解决,就是这么巧妙。


四、完整代码和使用演示

SerchBinaryTree.h

#pragma once//定义节点
template<class K>
struct BSNode
{K _key;BSNode<K>* _left;BSNode<K>* _right;BSNode(const K& key)//构造:_key(key), _left(nullptr), _right(nullptr){}
};//二叉搜索树
template<class K>
class BSTree
{typedef BSNode<K> Node;//重命名方便书写
public:bool Insert(const K& key){//树为空,直接作为根节点if (_root == nullptr){_root = new Node(key);return true;}//树不为空,需要查找插入位置//查找需要两个指针,cur用于找到目标位置,perent用于标记cur的父节点//这样就可以链接新节点Node* perent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key)//比当前值大,往右子树找{perent = cur;//先更新父节点cur = cur->_right;//走到右孩子节点}else if (key < cur->_key)//比当前值小,往左子树找{perent = cur;//先更新父节点cur = cur->_left;//走到左孩子节点}else{return false;//如果是相等值,则插入失败,因为我们是实现不支持重复值的}}//走到这里,说明cur找到了cur = new Node(key);//先创建结点//使用父节点进行链接,因为不确定是父节点的左孩子还是右孩子//所以需要单独判断,比较插入值与父节点值的大小即可,大插右,小插左if (key > perent->_key){perent->_right = cur;}else{perent->_left = cur;}return true;//插入成功返回true}//查找bool Find(const K& key){Node* cur = _root;//cur还是用于遍历查找while (cur)//查找策略与插入查找一致,不再赘述{if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return true;}}return false;}//删除bool Erase(const K& key){Node* cur = _root;//cur遍历查找目标位置Node* perent = nullptr;//跟踪记录cur的父节点while (cur){if (key > cur->_key){perent = cur;cur = cur->_right;}else if (key < cur->_key){perent = cur;cur = cur->_left;}else{//找到了//1.左子树为空if (cur->_left == nullptr){if (cur == _root)//如果删除节点为根节点{_root = cur->_right;//用不为空的右子树替代}else//不是根节点{//需要单独判断,确认cur是其父节点左右哪个孩子节点if (perent->_left == cur){perent->_left = cur->_right;//更新父节点的左孩子,变相删除了cur}else if (perent->_right == cur){perent->_right = cur->_right;}}delete cur;//替换后,释放节点return true;}else if (cur->_right == nullptr)//2.右子树为空{if (cur == _root)//根节点依旧要单独判断{_root = cur->_left;}else{if (perent->_left == cur){perent->_left = cur->_left;}else if (perent->_right == cur){perent->_right = cur->_left;}}delete cur;return true;}else//3.左右子树都不为空{//找右子树最小节点Node* minRight = cur->_right;//minRight查找最小节点Node* minRightPerent = cur;//跟踪记录minRight的父节点while (minRight->_left)//一直遍历左孩子就能找到最小节点{minRightPerent = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;//找到节点后先替换目标值//依旧需要确认是父节点的哪个孩子if (minRightPerent->_left == minRight){minRightPerent->_left = minRight->_right;//替换,变相删除}else{minRightPerent->_right = minRight->_right;}delete minRight;//释放节点return true;}}}return false;//没有找到,返回false}//中序遍历void InOrder(){_InOrder(_root);cout << endl;}private://中序打印void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root = nullptr;
};

使用演示:

#include <iostream>
using namespace std;
#include "SerchBinaryTree.h"int main()
{BSTree<int> b;b.Insert(6);b.Insert(9);b.Insert(1);b.Insert(3);b.InOrder();b.Erase(1);b.InOrder();return 0;
}

运行结果:


总结

        以上就是本文的全部内容了,感谢你的支持!

http://www.dtcms.com/wzjs/243769.html

相关文章:

  • 网站开发asp.net保定seo推广公司
  • 我们是谁 网站运营公司网站设计方案
  • 舟山市住房和城乡建设局网站西地那非
  • 政府网站 建设问题搜狗网站收录入口
  • 重庆百度网站排名seo报价单
  • 绵阳网站建设推广产品推广计划方案
  • 宁波建设局网站上海网站设计
  • 公司网站优化一键优化表格
  • 小米官方网站开发版在哪里宁波seo托管公司
  • 做网站需要多大带宽营销推广方案包括哪些内容
  • 了解做房产广告的网站宣传软文怎么写
  • 网站开发流程属于制作与开发河南郑州网站顾问
  • 网站运营工资婚恋网站排名前三
  • 网站建设中网站功能描述书功能谷歌首页
  • 深圳手机网站公司百度关键词怎么刷上去
  • 小说网站建设教程seo网站推广与优化方案
  • 盐田区网站建设2023b站推广大全
  • 做flash网站框架引擎刷网站软件
  • 系统开发工具有哪些关键词优化seo优化排名
  • 客户管理系统排名seo综合查询爱站
  • 美国哪个网站做diy电脑版免费创建网站的平台
  • 个人做动漫资源网站有哪些推广平台排名前十名
  • 免费个人网站模板下载武汉seo
  • 深圳网站建设-中国互联网络精准推广
  • 潮汕17网站一起做网店官网软文是什么意思通俗点
  • 那个网站做贷款的客源真实百度指数代表什么
  • 免费wap网站制作线上营销工具
  • 原创文章的网站被降权或无排名的原因有哪些教育培训机构营销方案
  • 重庆建设银行网站网络推广与营销
  • 郑州中小企业网站制作seo推广seo技术培训