平衡二叉树

  • 平衡二叉树(C++) -- 左旋旋右旋旋
    • 平衡二叉树 -- 左单旋
    • 平衡二叉树 -- 右单旋
    • 平衡二叉树 -- 左右双旋
    • 平衡二叉树 -- 右左双旋
    • 平衡二叉树 -- 插入和删除实现

平衡二叉树(C++) – 左旋旋右旋旋

  平衡二叉树全称平衡二叉搜索树,所以首先具备了二叉搜索树的特性,因为二叉搜索树并未对树的高做限制,只要求了左小右大,这就可能再极端情况下,出现左斜树或右斜树,这是查找就从O(logn)的期望退化成了O(n),即使不那么极端,树的高度也是没办法控制在一个稳定值上,这就使得当数据量庞大时,查找耗时过多;这就产生了如何对一棵树进行优化,使其高度稳定,这就首先需要对树的高度进行获取和记录,通过左旋和右旋使二叉搜索树达到平衡,也就是使各节点左子树和右子树的高度差不超过1,这样的查找时间复杂度就能稳定在O(logn)级别;
  因为二叉搜索树中形成不平衡的情况有很多种,所以旋转方式有4种,分别是左单旋,右单旋,左右双旋,右左双旋,而左右双旋其实就是先右旋然后左旋,右左双旋就是先左旋然后右旋;还有就是左单旋与右单旋对称,左右双旋与右左双旋对称~~

平衡二叉树 – 左单旋

左单旋:需要左单旋时,节点的造型是tree节点有一个左子节点和一个左孙节点,将子节点调为父节点,原tree节点调为右节点即可,这个过程中,原左子节点可能会有右子节点,这个右子节点必定小于tree节点,所以可以作为tree节点的左子节点;注意更新高度~~

// 左单旋
Node* __singleLeftRotation(Node* tree) {Node* newRoot = tree->left;tree->left = newRoot->right;newRoot->right = tree;// 高度的更新必须重新获取,再+1,更新时不能直接对高度进行+1或-1,因为当左单旋和右单旋在各节点的子节点情况不同下,// 高度的变换并非定值,而是由子节点情况决定的tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;newRoot->height = max(getHeight(newRoot->left),getHeight(tree->right)) + 1;return newRoot;}

平衡二叉树 – 右单旋

右单旋:需要右单旋时,因为与左单旋对称,所以就是对应上面的左单旋,左改右,右改左;注意更新高度~~

// 右单旋
Node* __singleRightRotation(Node* tree) {Node* newRoot = tree->right;tree->right = newRoot->left;newRoot->left = tree;tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;newRoot->height = max(getHeight(newRoot->left),getHeight(tree->right)) + 1;return newRoot;}

平衡二叉树 – 左右双旋

左右双旋:从下往上,先右旋再左旋

// 左单旋
Node* __doubleLeftRightRotation(Node* tree) {tree->left = __singleRightRotation(tree->left);return __singleLeftRotation(tree);}

平衡二叉树 – 右左双旋

右左双旋:从下往上,先左旋再右旋

// 右左双旋
Node* __doubleRightLeftRotation(Node* tree) {tree->right = __singleLeftRotation(tree->right);return __singleRightRotation(tree);}

平衡二叉树 – 插入和删除实现

  平衡二叉树在进行插入和删除时都可能使二叉搜索树中某节点上的左右子树高度不平衡(高度差大于1),所以在进行插入和删除操作时为了维护平衡二叉树的属性,就需要获取受影响节点的左右子树的高度,当高度差大于1时(因为一直在维护,所以高度差最大为2),进行左旋旋右旋旋,具体过程较为复杂,在__insert(插入方法)和__remove(删除方法)中有具体说明~~

// 平衡二叉树
class AVLTree {private:// 平衡二叉树中的节点struct Node {KeyType key;ValueType value;Node* left;Node* right;// 平衡二叉树因为需要维持高度平衡,所以节点中需要记录高度int height;Node(KeyType key, ValueType value) {this->key = key;this->value = value;this->left = this->right = NULL;}};// 根节点Node* root;// 节点数int count;// 获取当前树最大高度int getHeight(Node* tree) {// 节点为NULL时高度为0if(tree == NULL)return 0;return max(getHeight(tree->left),getHeight(tree->right)) + 1;}// 左单旋Node* __singleLeftRotation(Node* tree) {Node* newRoot = tree->left;tree->left = newRoot->right;newRoot->right = tree;// 高度的更新必须重新获取,再+1,更新时不能直接对高度进行+1或-1,因为当左单旋和右单旋在各节点的子节点情况不同下,// 高度的变换并非定值,而是由子节点情况决定的tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;newRoot->height = max(getHeight(newRoot->left),getHeight(tree->right)) + 1;return newRoot;}// 右单旋,需要右旋的情况是插入的节点在右子树的右节点Node* __singleRightRotation(Node* tree) {Node* newRoot = tree->right;tree->right = newRoot->left;newRoot->left = tree;tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;newRoot->height = max(getHeight(newRoot->left),getHeight(tree->right)) + 1;return newRoot;}// 左右双旋Node* __doubleLeftRightRotation(Node* tree) {tree->left = __singleRightRotation(tree->left);return __singleLeftRotation(tree);}// 右左双旋Node* __doubleRightLeftRotation(Node* tree) {tree->right = __singleLeftRotation(tree->right);return __singleRightRotation(tree);}// 内部实现,插入节点,插入完成则返回根节点,注意返回前要进行高度的更新// 通过递归返回子树的根节点,使树的左右节点指向得以绑定Node* __insert(Node* tree, KeyType key, ValueType value) {// 找到节点需要插入的位置了,插入if(tree == NULL) {tree = new Node(key, value);// 更新节点的高度,为根节点时,高度为1tree->height = 1;count++;return tree;}// 键值与当前键值相等,则更新值,也就是说这里没有相同键值的不同元素存在if(key == tree->key) {tree->value = value;// 键值比当前节点键值小} else if(key < tree->key) {// 左遍历,因为插入的是左边,所以如果出现不平衡一定是左子树高度过高tree->left = __insert(tree->left,key,value);// 对当前节点的左右子树高度进行比较,高度差为2时,说明要旋起来了if(getHeight(tree->left) - getHeight(tree->right) == 2) {// 当插入的值小于左子树根节点的值时,说明新节点插入在了左子树的左边,那么进行左单旋即可,返回更新后的子树根节点if(key < tree->left->key) {tree = __singleLeftRotation(tree);// 这边判断出的key > tree->left->key是可能涉及到3层的节点或4层节点的了,上面是涉及到3层节点的,// 是3层还是4层主要是看当前节点是否有右节点// 4层时:这里是插入节点插入的位置在当前节点左子树的左子树的右边节点位置// 3层时:或者插入的节点的位置在当前节点左子树的右节点} else {tree = __doubleLeftRightRotation(tree);}}// 键值比当前节点键值大} else {// 右遍历,因为插入的是右边,所以如果出现不平衡一定是右子树高度过高tree->right = __insert(tree->right,key,value);// 对当前节点的左右子树高度进行比较,高度差为2时,说明要旋起来了if(getHeight(tree->right) - getHeight(tree->left) == 2) {// 当插入的值大于右子树根节点的值时,说明新节点插入在了右子树的右边,那么进行右单旋即可,返回更新后的子树根节点if(key > tree->right->key) {tree = __singleRightRotation(tree);// 这边判断出的key < tree->right->key是可能涉及到3层的节点或4层节点的了,上面是涉及到3层节点的,// 是3层还是4层主要是看当前节点是否有左节点// 4层时:这里是插入节点插入的位置在当前节点右子树的右子树的左边节点位置// 3层时:或者插入的节点的位置在当前节点右子树的左节点} else {tree = __doubleRightLeftRotation(tree);}}}// 插入结束,更新高度,因为当前节点右高度1,所以高度要+1tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;// 返回执行完插入操作的平衡二叉树的根节点return tree;}// 内部实现,平衡二叉树是否包含键值为key的节点bool __contain(Node* tree, KeyType key) {// 递归结束条件,节点为NULL,说明找不到keyif(tree == NULL) {return false;}// 节点键值与待找键值的关系进行递归查找if(key == tree->key) {return true;} else if(key < tree->key) {return __contain(tree->left,key);} else {return __contain(tree->right,key);}}// 内部实现,平衡二叉树是否包含键值为key的节点,可能为NULl,用指针ValueType* __search(Node* tree, KeyType key) {// 递归结束条件,节点为NULL,说明找不到keyif(tree == NULL) {return NULL;}// 节点键值与待找键值的关系进行递归查找if(key == tree->key) {return &(tree->value);} else if(key < tree->key) {return __search(tree->left,key);} else {return __search(tree->right,key);}}// 释放平衡二叉树,用到的就是后序遍历,在析构函数中调用void __freeUp(Node* tree) {if(tree != NULL) {__freeUp(tree->left);__freeUp(tree->right);delete tree;count--;}}// 获取最左节点,也就是最小值所在的节点,给删除节点用Node* __getLeftmost(Node* tree) {if(tree == NULL)return NULL;Node* node = tree;while(node->left != NULL)node = node->left;return node;}// 获取最右节点,也就是最大值所在的节点,给删除节点用Node* __getRightmost(Node* tree) {if(tree == NULL)return NULL;Node* node = tree;while(node->right != NULL)node = node->right;return node;}// 内部实现,树的遍历:中序遍历,二叉搜索树中,中序遍历也就是从小到大遍历void __inOrderTraversal(Node* tree) {if(tree != NULL) {__inOrderTraversal(tree->left);cout << tree->key << " ";__inOrderTraversal(tree->right);}}// 内部实现,删除节点// 删除节点有2种情况会出现,找到要删除的节点和没找到要删除的节点// 找到要删除的节点,删除后会出现4种情况,被删除的节点左右都有子节点,被删除的节点只有左节点,删除的节点只有右节点,// 被删除的节点就是叶节点// 根节点tree代表了平衡二叉树Node* __remove(Node* tree, KeyType key) {// 根节点为NULL,说明平衡二叉树已经遍历结束,没有找到key,不需要进行删除,则返回NULLif(tree == NULL)return NULL;// 寻找要删除的节点// tree是根节点,当删除的节点是左节点或右节点时根节点不变,所以还是返回tree,但在传递时因为返回的都是根节点,// 所以要分别对应tree-left和tree-right// 因为是平衡二叉树,所以在删除节点后要判断一下此时以该节点为根的树(子树)是否平衡// 这里判断的是当删除的节点在左子树时,需要进行右旋或者进行左右旋if(key < tree->key) {tree->left = __remove(tree->left, key);// 判断高度,删除左节点后右高比左高大2if(getHeight(tree->right) - getHeight(tree->left) == 2) {// 右节点的右子树高度大于右节点的左子树高度时,说明当前节点和当前节点的右节点和右节点的右节点这3个节点是右斜的,// 这样就可以进行右旋使之平衡;这里也考虑了相等的情况,直接右旋就可以了if(getHeight(tree->right->right) > getHeight(tree->right->left)) {__singleRightRotation(tree);// 右节点的右子树高度小于右节点的左子树高度时,说明要先形成右斜然后再右旋,所以要进行左右旋} else {__doubleLeftRightRotation(tree);}}// 更新节点高度tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;return tree;} else if(key > tree->key) {tree->right = __remove(tree->right, key);// 判断高度,删除右节点后左高比右高大2if(getHeight(tree->right) - getHeight(tree->left) == 2) {// 左节点的左子树高度大于左节点的右子树高度时,说明当前节点和当前节点的左节点和左节点的左节点这3个节点是左斜的,// 这样就可以进行左旋使之平衡;这里也考虑了相等的情况,直接左旋就可以了if(getHeight(tree->right->right) > getHeight(tree->right->left)) {__singleLeftRotation(tree);// 左节点的左子树高度小于左节点的右子树高度时,说明要先形成左斜然后再右旋,所以要进行右左旋} else {__doubleRightLeftRotation(tree);}}// 更新节点高度tree->height = max(getHeight(tree->left),getHeight(tree->right)) + 1;return tree;// 找到要删除的节点,进行删除和平衡二叉树维护,维护时要分情况判断} else {// 待删除节点没有左右子节点时,直接删除节点if(tree->left == NULL && tree->right == NULL) {// 释放了空间之后,注意还需要将该节点置为NULL,因为delete后虽然空间释放了,但指针还在,防止乱指所以置NULLdelete tree;tree = NULL;count--;} else {Node* temp;// 待删除节点有右左右子节点时,根据平衡二叉树的性质可以用左子树的最右节点或右子树的最左节点替换掉被删除的节点,// 这样在节点被删除后,平衡二叉树的性质得以维护,因为这两个节点分别作为左子树的最大值和右子树的最小值,// 结合左子树节点均小于父节点,右子树节点军大于父节点的特性,// 就能确定这两个节点都必定大于所有左子树的节点并小于所有右子树的节点;// 因为要平衡,为了减少旋转次数,当左右均有子树时,删除高度较高的那边if(tree->left != NULL && tree->right != NULL) {if(getHeight(tree->left) > getHeight(tree->right)) {temp = __getRightmost(tree->left);} else {temp = __getLeftmost(tree->right);}// 将被删除节点的键值和值替换成右子树的最左节点的键值和值tree->key = temp->key;tree->value = temp->value;// 待删除节点只有左子树时,直接将左子树的第一个节点提上来,删掉待删除的节点} else if(tree->left != NULL) {temp = tree;tree = tree->left;// 待删除节点只有右子树时,直接将右子树的第一个节点提上来,删掉待删除的节点//  if(bst->right != NULL)} else {temp = tree;tree = tree->right;}// 减少计数和删除节点都在这进行,节省代码count--;delete temp;}}return tree;}// 内部实现,测试平衡二叉树是否平衡bool __testBalance(Node* tree) {if(tree == NULL) {return true;}int lh = getHeight(tree->left);int rh = getHeight(tree->right);//cout << "左:" << lh << "右:" << rh << endl;if((lh > rh && lh - rh > 1) || (lh < rh && rh - lh > 1)) {return false;}return __testBalance(tree->left) && __testBalance(tree->right);}public:// 构造函数AVLTree() {root = NULL;count = 0;}// 析构函数,用到了后序遍历删除平衡二叉树~AVLTree() {__freeUp(root);}// 平衡二叉树元素个数int size() {return count;}// 是否为空树bool isEmpty() {return count == 0;}// 外部调用,插入节点void insert(KeyType key, ValueType value) {// 设置平衡二叉树根节点,主要作用在插入唯一一个节点时获取根节点,因为这个平衡二叉树插入第一个节点后,根节点就不会动了root = __insert(root,key,value);}// 外部调用,平衡二叉树是否包含键值为key的节点bool contain(KeyType key) {return __contain(root,key);}// 外部调用,获取平衡二叉树中对应键值节点的值,可能为NULl,用指针ValueType* search(KeyType key) {return __search(root,key);}// 获取最大键值,最大键值在平衡二叉树最右边KeyType getMaxKey() {assert(count > 0);Node* node = root;while(node->right != NULL)node = node->right;return node->key;}// 获取最小键值,最小键值在平衡二叉树最左边KeyType getMinKey() {assert(count > 0);Node* node = root;while(node->left != NULL)node = node->left;return node->key;}// 外部调用,树的遍历:中序遍历,二叉搜索树中,中序遍历也就是从小到大遍历void inOrderTraversal() {__inOrderTraversal(root);cout << endl;}// 外部调用,删除节点,因为delete是内部关键字,所以方法名不能用delete,改用removevoid remove(KeyType key) {// 删除后可能会改变根节点,所以要重新获取下根节点root = __remove(root,key);}// 外部调用,测试平衡二叉树是否平衡bool testBalance() {return __testBalance(root);}};

平衡二叉树(C++) -- 左旋旋右旋旋相关推荐

  1. 图示讲解AVL平衡二叉树的左旋和右旋

    AVLTree 高度平衡的搜索二叉树 一棵平衡树,或是空树,或是具有以下性质的二叉搜索树:左子树和右子树都是AVL树,且左右子树的高度之差的绝对值不超过1. 该二叉树,根结点的右子树高度为3,左子树高 ...

  2. 平衡二叉树的左旋和右旋

    文章目录 复习树 1. 树的概念 2. 二叉树(Binary Tree) 3. 满二叉树 4. 完全二叉树 5. 二叉堆 6. 二叉搜索树 左旋和右旋 7. 平衡二叉树(平衡二叉搜索树) 本文章是我的 ...

  3. 平衡搜索树中的左单旋右单旋双旋

    本文要点: 平衡搜索树的左单旋.右单旋.左右双旋.右左双旋 在平衡搜索树中进行插入结点时,有可能会破坏整棵树的平衡.为了保证平衡不被破坏,就要对一些节点进行旋转,从而来降低树的高度,这样也能保证树的平 ...

  4. [ 数据结构 ] 平衡二叉树(AVL)--------左旋、右旋、双旋

    0 引出 数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在 回顾:二叉搜索树 左子树全部为空,从形式上看,更像一个单链表. 插入速度没有影响 查询速度明显降低(因为需 ...

  5. 平衡二叉树AVL左旋,右旋,双旋——java

    概念分析 平衡二叉树也叫平衡二叉搜索树,又被称为AVL树,它能保证查询效率较高 他具有以下特点:它是一颗空树或者它的左右两颗子树的高度差的绝对值不大于一,并且左右子树都是平衡二叉树,其也满足二叉排序树 ...

  6. Java 平衡二叉树之单旋(左旋,右旋)与双旋

    1.平衡二叉树 平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高. 具有以下特点:它是一 棵空树或它的左右两个 ...

  7. 平衡二叉树的左旋右旋详解 看不懂你打我

    平衡二叉树的左旋右旋 看不懂你打我 左旋右旋的操作 为什么要左旋右旋 左旋右旋能保持排序二叉排序树的性质吗 下次写平衡二叉树的LL.RR.LR.RL. 左旋右旋的操作 1.左旋:对X节点左旋,即以X的 ...

  8. 平衡二叉树(AVL)的左旋和右旋

    平衡二叉树(AVL)的左旋和右旋 最近算法和数据结构受虐,于是开始从二叉树学习,诸君共勉,同时也作为笔记.其实要理解思想不难,关键是要有耐心,不要觉得它难! 构建左平衡: bf :平衡度,可取值LH( ...

  9. day062:平衡二叉树——左旋、右旋

    二叉树.平衡二叉树的介绍:day061:二叉树.二叉查找树.平衡二叉树_ZQyyds:)的博客-CSDN博客 目录 一.平衡二叉树的左旋 1.为什么要左旋.右旋? 2.什么是左旋? 3.图解 二.平衡 ...

最新文章

  1. 【iCore4 双核心板_ARM】例程五:SYSTICK定时器 实验——定时点亮LED
  2. OpenCASCADE:Modeling Data之边界框
  3. java 监控 收集资料2(收集中)
  4. 表单隐藏域与display:none
  5. 关于PLSQL Developer报动态执行表不可访问,本会话的自动统计被禁止错的解决方法 .
  6. 【2020模拟考试T1】【PAT乙】1031 查验身份证 (15分)模拟
  7. Android usb audio调用流程(二)
  8. 知识分享:C语言语法总结,初学者可收藏
  9. android自定义wifi列表,Android开发之Wifi基础教程
  10. 推荐8个实用精美的在线网站,珍藏多年!
  11. 含泪推荐四款超级好用的电脑软件,值得收藏
  12. tp5——实践前台模板引入
  13. 零基础小白深度学习入门篇
  14. DHCP——分配固定IP地址
  15. 怎么做移动APP测试,移动应用测试有哪些?
  16. UVM学习整理——UVM整体介绍
  17. 2008-2020年上市公司环境信息披露质量指数EDI、环境绩效明细、环境排放明细、资源消耗明细
  18. PPT里面的背景音乐找不到?
  19. 程序员应该多久跳槽一次?为何贵圈跳槽如此频繁?
  20. mysql中查询的第一名_使用SQL查询每科第一名及最后一名(学科、姓名、分数)...

热门文章

  1. 云展网教程 | 关于个人中心相关功能
  2. 图片标注工具LabelImg安装及使用
  3. eclipse中如何调出提示?
  4. 【JTAG】1687协议详解
  5. Flutter报错总结:There are multiple heroes that share the same tag within a subtree.
  6. windows版redis安装使用
  7. 每个优秀的人,都有一段沉默的时光
  8. Phase Shift和PMP(相位测量轮廓法)的几个需要注意的细节
  9. 4-5:关注,取消关注
  10. 第三次全国土地调查业务培训考试试题分析