AVL平衡二叉树C++代码实现

总结

  1. 什么是平衡二叉树

    • 基于二叉排序树
    • 左右子树的深度之差的绝对值不超过1
    • 左右子树都是平衡二叉树
  2. 为什么要修改二叉排序树为平衡二叉树:因为查找二叉树的比较次数和层数有关

  3. 在构造二叉排序树的过程中,会出现四种失衡现象

  4. 如何进行调整:找到最小不平衡子树,将其调平衡

    • 最小不平衡子树:离插入节点最近且平衡因子绝对值超过1的结点,以这个结点为根节点的子树
  5. LL型:右旋,原本橙点是root,右旋后,绿点是root,橙点为绿点的right

    • 注意:绿点的right可能有结点,所以要用橙点的left接上
    • 以上图第三个为例
  6. RR型:左旋,原本橙点是root,左旋后,绿点是root,橙点为绿点的left

    • 注意:绿点的left可能有结点,所以要用橙点的right接上
    • 以上图第三个为例
  7. LR型:双旋,先对绿点(LL中的橙点)和蓝点(LL中的绿点)进行RR左旋,在对橙点进行LL右旋

    • 解释:左旋使LR型变为LL型,右旋使LL型平衡
    • 注意:对两个点也能进行RR左旋

  8. RL型:和LR型对称操作

代码思路

  1. 注意过程中要对树进行操作的函数的参数一定要传入指针的引用
  2. 递归插入结点insert
    • 如果curr为空,即找到最终位置,申请内存新建节点
    • 如果传入参数num小于当前结点的data,递归进入左子树
    • 如果传入参数num大于当前节点的data,递归进入右子树
  3. 插入结点后,就要退出递归,进行平衡因子的更新
    • 通过getHeight即可得到左子树和右子树的高度
    • 若平衡因子<1,继续退出递归
    • 若平衡因子>1,即不平衡,就要判断不平衡的类型了
      • 由第一张图可得,RX型橙点-2,LX型橙点2
      • RL型绿点1,RR型绿点-1,LR型绿点-1,LL型绿点1
      • 每个类型都有自己的特征,进入if判断
  4. 右旋要注意绿点的right,左旋要注意绿点的left
  5. 插入节点后和调平衡后,要更新改变过的结点的高度,绿点和橙点

代码

网上找的代码,忘了链接了,添加头文件DrawATree.h

#ifndef DRAWTREE_HH
#define DRAWTREE_HH

#include <ostream>
#include <sstream>
#include <iostream>
#include <cmath>
#include <algorithm>

namespace DrawTree
{
#define lChild left  //使用前将 l_child_ 更换为 自己树节点的左孩子的名字
#define rChild right  //使用前将 r_child_ 更换为 自己树节点的右孩子的名字
#define MAXN (1000)      //这棵树的节点上限
#define PERID (2)        //打印两个节点的横坐标间隔
    int SUM;  //统计当前遍历到第几个节点

    // 将光标移动到 (X,Y)
    std::string AXIS(int X, int Y) {
        std::stringstream ss;
        ss << "\033[" << Y << ";" << X << "H";
        return ss.str();
    }

    struct DrawNode {
        int x, y, dataSize;
    }axisArray[MAXN];


    //计算节点数据输出的长度
    template <typename TreePtr>
    int dataSize(TreePtr const& root) {
        std::stringstream ss;
        //对应buildDrawTree中的打印,对应树结点的数据
        ss << root->data << "/" << root->height;
        return (ss.str()).length();
    }

    //中序遍历, 从左往右画节点(不连线)
    //横坐标通过全局变量SUM和上一个节点数据的输出长度算出
    //纵坐标通过递归深度判断
    //PERID 是两个节点间隔长度
    template <typename TreePtr>
    void buildDrawTree(TreePtr const& root, int deep) {
        if (!root) return;  //判断空节点,如果你的节点判空和我不一样,这里也要改, 比如我之前的判断空节点是(root->height_== -1).

        if (root->lChild)  buildDrawTree(root->lChild, deep + 1);

        axisArray[SUM] = { axisArray[SUM - 1].x + axisArray[SUM - 1].dataSize + PERID, deep, dataSize(root) };
        std::cout << AXIS(axisArray[SUM].x, axisArray[SUM].y) << root->data << "/" << root->height;
        ++SUM;

        if (root->rChild)  buildDrawTree(root->rChild, deep + 1);
    }

    template <typename TreePtr>
    void Draw(TreePtr const& t) {  //画树函数
        system("cls"); //清屏
        SUM = 1;
        int maxy = 0;

        buildDrawTree<TreePtr>(t, 2);   //每个结点画出来

        //画节点间连线,因为画的节点不会太多,所以就写了n^2的算法,比较好实现
        //每个节点只有一个父节点,所以画出每个节点和自己父节点的连线即可
        for (int i = 1; i < SUM; i++) {
            //x,y是子节点的坐标,p是父节点的axisArray数组的下标, px,py是父节点的坐标;
            int x = axisArray[i].x, y = axisArray[i].y, p = 0, px = 0, py = y - 1;

            if (y == 1) continue; // 根结点没有父节点,跳过

            for (int j = 1; j < SUM; j++) {  //循环找父节点
                if (i == j) continue;
                if ((!p || abs(axisArray[j].x - x) < abs(px - x)) && axisArray[j].y + 1 == y)
                    p = j, px = axisArray[j].x;
            }

            int s = (2 * x + axisArray[i].dataSize) >> 1;
            std::cout << AXIS(s, py) << '+';
            if (s < px)
                for (int i = s + 1; i < px; i++) std::cout << AXIS(i, py) << '-';
            else
                for (int i = px + axisArray[p].dataSize; i < s; i++) std::cout << AXIS(i, py) << '-';
            maxy = std::max(maxy, y);
        }
        std::cout << AXIS(1, maxy + 1);  //打印完把光标移到最下边.
    }
}
#endif
#include <iostream>
#include <vector>
#include "DrawATree.h"
using namespace std;
struct treeNode {
	int data;
	int height;
	treeNode* left, * right;
};
//中序遍历
void inorderTra(treeNode* curr) {
	if (!curr) return;
	inorderTra(curr->left);
	cout << curr->data << " ";
	inorderTra(curr->right);
}
//获取高度
int getHeight(treeNode* curr) {
	if (!curr) return 0;
	return curr->height;
}
//获取平衡因子
int getFactor(treeNode* curr) {
	if (!curr) return 0;
	return getHeight(curr->left) - getHeight(curr->right);
}
//LL型,右旋单旋
treeNode* LL(treeNode*& curr) {
	treeNode* leftNode = curr->left;
	curr->left = leftNode->right;
	leftNode->right = curr;
	curr = leftNode;
	//这个函数返回后会更改curr->height
	//curr->height = max(getHeight(curr->left), getHeight(curr->right)) + 1;
	curr->right->height = max(getHeight(curr->right->left), getHeight(curr->right->right)) + 1;
	return curr;
}
//RR型,左旋单旋
treeNode* RR(treeNode*& curr) {
	treeNode* rightNode = curr->right;
	curr->right = rightNode->left;
	rightNode->left = curr;
	curr = rightNode;
	//这个函数返回后会更改curr->height
	//curr->height = max(getHeight(curr->left), getHeight(curr->right)) + 1;
	curr->left->height = max(getHeight(curr->left->left), getHeight(curr->left->right)) + 1;
	return curr;
}
//LR型,先对左子树右旋再左旋
treeNode* LR(treeNode*& curr) {
	RR(curr->left);
	return LL(curr);
}
//RL型,先对右子树左旋再右旋
treeNode* RL(treeNode*& curr) {
	LL(curr->right);
	return RR(curr);
}
//重平衡
void rebalance(treeNode*& curr) {
	int factor = getFactor(curr);
	if (factor > 1 && getFactor(curr->left) > 0) LL(curr);
	else if (factor > 1 && getFactor(curr->left) < 0) LR(curr);
	else if (factor < -1 && getFactor(curr->right) > 0) RL(curr);
	else if (factor < -1 && getFactor(curr->right) < 0) RR(curr);
	else return;
}
//插入叶子结点,递归
//注意使用指针的引用
void insert(int num, treeNode*& curr) {
	//如果为空,即找到了叶子结点的位置,分配空间
	if (!curr) {
		curr = new treeNode();
		curr->data = num;
		curr->left = nullptr;
		curr->right = nullptr;
	}
	//如果相同就不需要插入了
	else if (curr->data == num) return;
	//如果数字比当前结点的值小,即进入当前结点的左子树继续判断
	else if (num < curr->data) insert(num, curr->left);
	//如果数字比当前节点的值大,即进入当前节点的右子树继续判断
	else if (num > curr->data) insert(num, curr->right);
	//重平衡
	rebalance(curr);
	//重新更新高度
	curr->height = max(getHeight(curr->left), getHeight(curr->right)) + 1;
}
void dele(int num, treeNode*& curr) {
	if (!curr) return;
	//小于,进入左子树
	if (num < curr->data) dele(num, curr->left);
	//大于,进入右子树
	else if (num > curr->data) dele(num, curr->right);
	//等于,删除
	else if (num == curr->data) {
		//叶子节点,直接删除
		if (!curr->left && !curr->right) {
			delete curr;
			curr = nullptr;
			return;
		}
		//只有右子树,用右子树替代
		else if (!curr->left && curr->right) {
			auto save = curr;
			curr = curr->right;
			delete save;
		}
		//只有左子树,用左子树替代
		else if (curr->left && !curr->right) {
			auto save = curr;
			curr = curr->left;
			delete save;
		}
		//左右子树都有,用前驱结点的值替换,删除前驱结点即可
		else {
			//找到前驱结点
			auto save = curr->left;
			while (save->right) save = save->right;
			//记录前驱结点的值,再往下递归找前驱结点(一定是一个叶子节点)
			//必须这样做,不可以删除直接删除前驱结点,因为回溯时要进行重平衡
			int value = save->data;
			dele(value, curr);
			curr->data = value;
		}
	}
	//重平衡
	rebalance(curr);
	//重新更新高度
	curr->height = max(getHeight(curr->left), getHeight(curr->right)) + 1;
}
void createTree(vector<int> v, treeNode*&pRoot) {
	for (int i = 0; i < static_cast<int>(v.size()); i++) {
		insert(v[i], pRoot);
	}
	//中序遍历,平衡二叉树是特殊的二叉排序树
	//inorderTra(pRoot);
}

int main() {
	vector<int> v = { 49,38,65,97,76,13,27,100 };
	treeNode* pRoot = nullptr;
	createTree(v,pRoot);
	DrawTree::Draw(pRoot);
	int num;
	string action;
	while (true) {
		cout << "删除/添加/退出(d/a/z): ";
		cin >> action;
		if (action == "d") {
			cin >> num;
			dele(num, pRoot);
			DrawTree::Draw(pRoot);
		}
		else if (action == "a") {
			cin >> num;
			insert(num, pRoot);
			DrawTree::Draw(pRoot);
		}
		else if (action == "z") break;
	}
	return 0;
}

结果







posted @ 2020-10-16 01:45  肥斯大只仔  阅读(445)  评论(0编辑  收藏  举报