IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 四、高级数据结构和算法:二叉搜索树(BST树)、平衡二叉树(AVL树) -> 正文阅读

[数据结构与算法]四、高级数据结构和算法:二叉搜索树(BST树)、平衡二叉树(AVL树)

4 二叉查找树(BST树)、平衡二叉树(AVL树)

4.1 二叉查找树(BST树)

二叉查找树(Binary Search Tree, BST),又被称为二叉搜索树
在这里插入图片描述

4.1.1 特点

(1)若节点的左子树不空, 则左子树上所有节点的key均小于它的根节点的key
(2)若节点的右子树不空, 则右子树上所有节点的key均大于它的根节点的key
(3)任意节点的左,右子树也分别为二叉查找树
(4)没有key相等的节点

二叉查找树进行中序遍历,可以得到一个递增的有序序列
在这里插入图片描述

  • 实例
#include <iostream>
using namespace std;
class Node{
public:
    char val;
    Node* left;
    Node* right;
    Node(char val):val(val),left(nullptr),right(nullptr){}
    Node(char val,Node* left,Node* right):val(val),left(left),right(right){}
};
class BSTtree{
private:
    Node* m_root = nullptr;
    void inorder(Node* root){
    	if(nullptr == root) return;
		inorder(root->left);
		cout << root->val << " ";
		inorder(root->right);
    }
    Node* Insert(Node* root,char val){
    	if(nullptr ==  root) return new Node(val);
		if(root->val > val){
	    	root->left = Insert(root->left,val);
		}else{
	   		root->right = Insert(root->right,val);
		}
		return root;
    }
public:
    void Insert(char val){
		m_root = Insert(m_root,val);
    }
    void Print(){
    	inorder(m_root);//二叉查找树进行中序遍历,可以得到一个递增的有序序列
		cout << endl;
    }
};
int main(){
    BSTtree root;
    root.Insert('T');
    root.Insert('E');
    root.Insert('B');
    root.Insert('F');
    root.Insert('Y');
    root.Insert('X');
    root.Insert('Z');
    
    root.Print();
}

树的结构:
在这里插入图片描述

结果:B E F T X Y Z
分析:递增序列

4.1.2 结构

二叉搜索树通常使用链式存储孩子表示法。

class Node{
public:
    char val;
    Node* left;
    Node* right;
    Node(char val):val(val),left(nullptr),right(nullptr){}
    Node(char val,Node* left,Node* right):val(val),left(left),right(right){}
};

4.1.3 操作(插入、查找和删除)

4.1.3.1 插入

  • 步骤
    (1)如果二叉查找树为空,则直接插入。
    (2)如果插入节点key小于根结点key,则插入到左子树中;大于根结点key,则插入到右子树中。
    (3)依次类推,直到找到插入位置。
    在这里插入图片描述
  • 实例
#include <iostream>
using namespace std;
template <typename T> 
class _Node{
public:
    T val;
    _Node* left;
    _Node* right;
    _Node(T val):val(val),left(nullptr),right(nullptr){}
    _Node(T val,_Node* left,_Node* right):val(val),left(left),right(right){}
};
template <typename T>
class BSTtree{
private:
    typedef _Node<T> Node;
    Node* m_root = nullptr;
    void inorder(Node* root){
    	if(nullptr == root) return;
		inorder(root->left);
		cout << root->val << " ";
		inorder(root->right);
    }
    void preorder(const Node* root){
    	if(nullptr == root) return;
    	if(root->left) cout << root->val << "--" << root->left->val << endl;
    	if(root->right) cout << root->val << "--" << root->right->val << endl;
    	preorder(root->left);
    	preorder(root->right);
    }
    Node* Insert(Node* root,T val){
    	if(nullptr ==  root) return new Node(val);
		if(root->val > val){
	    	root->left = Insert(root->left,val);
		}else{
	    	root->right = Insert(root->right,val);
		}
		return root;
    }
public:
    void Insert(T val){
   		if(Search(val)) return;
		m_root = Insert(m_root,val);
    }
    void Print(){
    	inorder(m_root);//二叉查找树进行中序遍历,可以得到一个递增的有序序列
		cout << endl;
    	preorder(m_root);
    }
};
int main(){
    BSTtree<int> root;
    root.Insert(15);
    root.Insert(4);
    root.Insert(20);
    root.Insert(17);
    root.Insert(19);
    
    root.Print();
}

树的结构:
在这里插入图片描述

结果:

4 15 17 19 20 
15--4
15--20
20--17
17--19

4.1.3.2 查找

  • 查找指定key的节点
    (1)从根结点开始,若二叉树非空,将给定值与根结点的关键字比较,若相等,则查找成功;
    (2)若不等,则当给定值小于根结点关键字时,在根结点的左子树中查找,否则在根结点的右子树中查找。

  • 实例

#include <iostream>
using namespace std;
template <typename T> 
class _Node{
public:
    T val;
    _Node* left;
    _Node* right;
    _Node(T val):val(val),left(nullptr),right(nullptr){}
    _Node(T val,_Node* left,_Node* right):val(val),left(left),right(right){}
};
template <typename T>
class BSTtree{
private:
    typedef _Node<T> Node;
    Node* m_root = nullptr;
    bool Search(Node* root,T val){
    	if(nullptr == root) return false;
		if(root->val == val) return true;
		if(root->val < val){
	    	Search(root->right,val);
		}else{
	    	Search(root->left,val);
		}
    }
    void inorder(Node* root){
    	if(nullptr == root) return;
		inorder(root->left);
		cout << root->val << " ";
		inorder(root->right);
    }
    void preorder(const Node* root){
    	if(nullptr == root) return;
    	if(root->left) cout << root->val << "--" << root->left->val << endl;
    	if(root->right) cout << root->val << "--" << root->right->val << endl;
    	preorder(root->left);
    	preorder(root->right);
    }
    Node* Insert(Node* root,T val){
    	if(nullptr ==  root) return new Node(val);
		if(root->val > val){
	    	root->left = Insert(root->left,val);
		}else{
		    root->right = Insert(root->right,val);
		}
		return root;
    }
public:
    bool Search(T val){
    	return Search(m_root,val);
    }
    void Insert(T val){
   		if(Search(val)) return;
		m_root = Insert(m_root,val);
    }
    void Print(){
    	inorder(m_root);//二叉查找树进行中序遍历,可以得到一个递增的有序序列
		cout << endl;
    	preorder(m_root);
    }
};
int main(){
    BSTtree<int> root;
    root.Insert(15);
    root.Insert(4);
    root.Insert(20);
    root.Insert(17);
    root.Insert(19);
    
    root.Print();

    cout << root.Search(1) << " " << root.Search(19) << endl;
}

4.1.3.3 删除

二叉查找树的删除操作是相对复杂一点,它要按 3 种情况来处理:

(1)被删除结点是叶子结点,直接删除。
在这里插入图片描述

(2)结点只有左子树或只有右子树,则让子树替代;
在这里插入图片描述
(3)结点既有左子树,又有右子树,有两种处理方式
a)合并删除,右子树合并到左子树的最大值的右子树上;或者左子树合并到右子树最小值的左子树上。
b)替代删除,后继代替删除节点,然后删除后继;或者前驱代替删除节点,然后删除前驱。(常用
在这里插入图片描述

  • 实例
#include <iostream>
using namespace std;
template <typename T>
class _Node {
public:
    T val;
    _Node* left;
    _Node* right;
    _Node(T val):val(val),left(nullptr),right(nullptr) {}
    _Node(T val,_Node* left,_Node* right):val(val),left(left),right(right) {}
};
template <typename T>
class BSTtree {
private:
    typedef _Node<T> Node;
    Node* m_root = nullptr;
    bool Search(Node* root,T val) {
        if(nullptr == root) return false;
        if(root->val == val) return true;
        if(root->val < val) {
            return Search(root->right,val);
        } else {
            return Search(root->left,val);
        }
    }
    void inorder(Node* root) {
        if(nullptr == root) return;
        inorder(root->left);
        cout << root->val << " ";
        inorder(root->right);
    }
    void preorder(const Node* root) {
        if(nullptr == root) return;
        if(root->left) cout << root->val << "--" << root->left->val << endl;
        if(root->right) cout << root->val << "--" << root->right->val << endl;
        preorder(root->left);
        preorder(root->right);
    }
    Node* Insert(Node* root,T val) {
        if(nullptr ==  root) return new Node(val);
        if(root->val > val) {
            root->left = Insert(root->left,val);
        } else {
            root->right = Insert(root->right,val);
        }
        return root;
    }
    Node* Remove(Node* root,T val) {
        if(nullptr == root) return nullptr;
        if(root->val < val) {
            root->right = Remove(root->right,val);
        } else if(root->val > val) {
            root->left = Remove(root->left,val);
        } else {
            //删除结点为叶子节点,直接删除
            //左右子树都为空
            if(nullptr == root->right && nullptr == root->left) {
                delete root;
                return nullptr;
            }
            //结点只有左子树或只有右子树,则让子树替代
            //右子树为空,左子树作为根结点
            if(nullptr == root->right) {
                Node* left = root->left;
                delete root;
                return left;
            }
            //左子树为空,右子树作为根结点
            if(nullptr == root->left) {
                Node* right = root->right;
                delete root;
                return right;
            }
            //左右子树都不为空,右子树中找到最小值,替换掉根结点
            T minval = Minimum(root->right);
            root->val = minval;
            //root->right = Remove(root->right,minval);
            root->right = DeleteMin(root->right);
        }
        return root;
    }
    int Minimum(Node* root) {
        if(nullptr == root) throw runtime_error("root is null");
        while(nullptr != root->left) {
            root = root->left;
        }
        return root->val;
    }
    Node* DeleteMin(Node* root) {
        if(nullptr == root) throw runtime_error("root is null");
        Node* prev = nullptr;
        Node* cur = root;
        while(nullptr != cur->left) {
            prev = cur;
            cur = cur->left;
        }
		prev->left = nullptr;
        delete cur;
		return root;
    }
public:
    bool Search(T val) {
        return Search(m_root,val);
    }
    void Insert(T val) {
        if(Search(val)) return;
        m_root = Insert(m_root,val);
    }
    void Print() {
        inorder(m_root);//二叉查找树进行中序遍历,可以得到一个递增的有序序列
        cout << endl;
        preorder(m_root);
    }
    void Remove(T val) {
        m_root = Remove(m_root,val);
    }
};
int main() {
    BSTtree<int> root;
    root.Insert(15);
    root.Insert(10);
    root.Insert(30);
    root.Insert(5);
    root.Insert(11);
    root.Insert(12);
    root.Insert(20);
    root.Insert(40);

    root.Remove(15);
    root.Print();

}

树的可视化工具:GraphvizOnline

删除前
在这里插入图片描述
删除结点15后

在这里插入图片描述
删除结点5后
在这里插入图片描述
删除结点11后
在这里插入图片描述

4.1.4 优缺点

  • 优点
操作时间复杂度
插入O(log n)
查找O(log n)
插入O(log n)
  • 缺点
    二叉搜索树构造顺序影响操作的时间复杂度。

4.2 平衡二叉树(AVL树)

解决BST树的缺点:不平衡

如果数据连续,则BST树退化成链表的形式,导致查找效率低。
此时,采用AVL树可解决BST树的缺点。
在这里插入图片描述
平衡二叉树/自平衡二叉查找树(Balanced Binary Tree): 也称为AVL树。
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  • 特点
    (1)左右子树深度之差的绝对值不超过1(任意节点的两子树的高度最大差别为1).
    (2)左右子树仍然为平衡二叉树.

4.2.1 判断平衡

平衡因子BF(Balance Factor):左树深度减去右树深度的值。平衡因子BF = 左子树深度-右子树深度。
平衡二叉树每个结点的平衡因子只能是1,0,-1。若其绝对值超过1,则该二叉排序树就是不平衡的。当BF为-1、0、1时,二叉树平衡;反之,不平衡。
在这里插入图片描述
(1)(3)不是
(2)(4)是

4.2.2 操作

AVL树的操作基本和二叉查找树一样,这里我们关注的是两个变化很大的操作:插入和删除!

4.2.3 旋转

旋转条件:当最小不平衡子树根节点BF>1,右旋;BF<-1,左旋。
在这里插入图片描述
当P的A、B节点插入新结点,导致Q点不平衡,左重右轻,右旋。
当Q的B、C节点插入新结点,导致P点不平衡,右重左轻,左旋。
旋转点上升,不平衡点向轻的一侧旋转。

  • 旋转步骤:
    (1)获取旋转节点
    (2)旋转节点的子节点替换父节点的旋转节点
    (3)旋转节点父节点做旋转节点子节点
    (4)返回旋转节点

例:

(1)依次插入1,2,3在这里插入图片描述
(2)依次插入3,2,1
在这里插入图片描述
(3)依次插入2,1,3或2,3,1
在这里插入图片描述

(4)依次插入1,3,2
在这里插入图片描述
(5)依次插入3,1,2
在这里插入图片描述

(6)依次插入4,5在这里插入图片描述
(7)依次插入6,7在这里插入图片描述

4.2.4 插入

插入时的不平衡的四种情况:
LL:插入一个新节点到不平衡节点的左子树(Left)的左子树(Left),导致不平衡节点的平衡因子由1变为2

在这里插入图片描述
RR:插入一个新节点到不平衡节点的右子树(Right)的右子树(Right),导致不平衡节点的平衡因子由-1变为-2

在这里插入图片描述

LR:插入一个新节点到不平衡节点的左子树(Left)的右子树(Right),导致不平衡节点的平衡因子由1变为2
在这里插入图片描述

RL:插入一个新节点到不平衡节点的右子树(Right)的左子树(Left),导致不平衡节点的平衡因子由-1变为-2

在这里插入图片描述

  • 不平衡处理方法
    (1)单向右旋平衡处理LL
    (2)单向左旋平衡处理RR
    (3)双向旋转(先左后右)平衡处理LR
    (4)双向旋转(先右后左)平衡处理RL

在这里插入图片描述

  • 实例
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
template <typename T>
class _Node{
public:
    T val;
    _Node* left;
    _Node* right;
    _Node(const T& val):val(val),left(nullptr),right(nullptr){}
    _Node(const T& val,_Node* left,_Node* right):val(val),left(left),right(right){}
};
//    P         Q
//   / \       / \
//  Q   C     A   P
// / \           / \
//A   B         B   C
typedef _Node<int> Node;
Node* RotateRight(Node* root){
    Node* p = root;
    Node* q = root->left;
    Node* b = q->right;
    q->right = p;
    p->left = b;
    return q;
}
//    P         Q
//   / \       / \
//  A   Q     P   C
//     / \   / \       
//    B   C A   B
Node* RotateLeft(Node* root){
    Node* p = root;
    Node* q = root->right;
    Node* b = q->left;
    q->left = p;
    p->right = b;
    return q;
}
void Preorder(Node* root){
     if(nullptr == root) return;
     if(root->left) cout << root->val << "--" << root->left->val << endl;
     if(root->right) cout << root->val << "--" << root->right->val << endl;
     Preorder(root->left);
     Preorder(root->right);
} 
int main(){
    cout << "左旋:" << endl;
{
    Node a(1);
    Node b(2);
    Node c(3);
	
    a.right = &b;
    b.right = &c;

    Preorder(&a);
    Node* root = RotateLeft(&a); //左旋
    Preorder(root);
}
cout << "右旋:" << endl;
{
    Node a(1);
    Node b(2);
    Node c(3);
	
    c.left = &b;
    b.left = &a;

    Preorder(&c);
    Node* root = RotateRight(&c); //左旋
    Preorder(root);

}
cout << "右旋左旋:" << endl;
{
    Node a(1);
    Node b(2);
    Node c(3);
	
    a.right = &c;
    c.left = &b;
    Node* root = &a;

    Preorder(root);
    root->right = RotateRight(root->right);
    Preorder(root);
    root = RotateLeft(root); //左旋
    Preorder(root);

}
cout << "左旋右旋:" << endl;
{
    Node a(1);
    Node b(2);
    Node c(3);
	
    c.left = &a;
    a.right = &b;

    Node* root = &c;
    Preorder(root);
    root->left = RotateLeft(root->left);
    Preorder(root);
    root = RotateRight(root); //左旋
    Preorder(root);
}
}

结果:

左旋:
1--2
2--3
2--1
2--3
右旋:
3--2
2--1
2--1
2--3
右旋左旋:
1--3`在这里插入代码片`
3--2
1--2
2--3
2--1
2--3
左旋右旋:
3--1
1--2
3--2
2--1
2--1
2--3

4.2.5 删除

  • 删除节点有三种情况
    (1)删除叶子节点。
    (2)删除只有一棵子树的节点。
    (3)删除有两棵子树的节点。

AVL删除与BST删除方式一致,在BST删除结点的基础上增加平衡操作即可。

  • 实例
#include <iostream>
#include <stack>
#include <vector>
#include <queue>
using namespace std;

template <typename T>
class _Node {
public:
    T val;
    _Node* left;
    _Node* right;
    _Node(const T& val):val(val),left(nullptr),right(nullptr) {}
    _Node(const T& val,_Node* left,_Node* right):val(val),left(left),right(right) {}
};

template <typename T>
class AVLTree {
    typedef _Node<T> Node;
    Node* m_root = nullptr;
public:
    bool Search(const T& val) {
        return Search(m_root,val);
    }
    void Insert(const T& val) {
        if(Search(val)) return;
        m_root = Insert(m_root,val);
    }
    void Remove(const T& val) {
        m_root = Remove(m_root,val);
    }
    void Print() {
        preorder(m_root);
    }
private:
    bool Search(Node* root,const T& val) {
        if(nullptr == root) return false;
        if(root->val == val) return true;
        if(root->val < val) {
            return Search(root->right,val);
        } else {
            return Search(root->left,val);
        }
        // return Search(root->val<val?root->right:root->left,val);
    }
    Node* Remove(Node* root,const T& val) {
        if(nullptr == root) return nullptr;
        if(root->val < val) root->right = Remove(root->right,val);
        else if(root->val > val) root->left = Remove(root->left,val);
        else {
            if(nullptr == root->right && nullptr == root->left) {
                delete root;
                return nullptr;
            }
            if(nullptr == root->right) {
                Node* left = root->left;
                delete root;
                return left;
            }
            if(nullptr == root->left) {
                Node* right = root->right;
                delete root;
                return right;
            }
            T minval = Minimun(root->right);
            root->val = minval;
            root->right = Remove(root->right,minval);
            // root->right = DeleteMin(root->right);
        }
        return Balance(root);
    }
    Node* DeleteMin(Node* root) {
        if(nullptr == root) throw runtime_error("root is null");
        Node* prev = nullptr;
        Node* cur = root;
        while(nullptr != cur->left) {
            prev = cur;
            cur = cur->left;
        }
        delete cur;
        return root;
    }
    T Minimun(Node* root) {
        if(nullptr == root) throw runtime_error("root is null");
        while(nullptr != root->left) root = root->left;
        return root->val;
    }
    T Maximun(Node* root) {
        if(nullptr == root) throw runtime_error("root is null");
        while(nullptr != root->right) root = root->right;
        return root->val;
    }
    Node* Insert(Node* root,const T& val) {
        if(nullptr == root) return new Node(val);
        if(root->val>val) {
            root->left = Insert(root->left,val);
        } else {
            root->right = Insert(root->right,val);
        }
        return Balance(root);
    }
    void preorder(const Node* root) {
        if(nullptr == root) return;
        if(root->left) cout << root->val << "--" << root->left->val << endl;
        if(root->right) cout << root->val << "--" << root->right->val<< endl;
        preorder(root->left);
        preorder(root->right);
    }
//     P          Q
//    / \        / \
//   Q   C      A   P
//  / \            / \
// A   B          B   C
    Node* RotateRight(Node* root) {
        Node* p = root;
        Node* q = p->left;
        Node* b = q->right;
        q->right = p;
        p->left = b;
        return q;
    }
//   P            Q
//  / \          / \
// A   Q        P   C
//    / \      / \
//   B   C    A   B
    Node* RotateLeft(Node* root) {
        Node* p = root;
        Node* q = p->right;
        Node* b = q->left;
        q->left = p;
        p->right = b;
        return q;
    }
    int Balancefactory(Node* root) {
        return Depth(root->left)-Depth(root->right);
    }

    Node* Balance(Node* root) {
        int bf = Balancefactory(root);
        if(bf == -2) {
            if(Balancefactory(root->right)>0) {
                root->right = RotateRight(root->right);
            }
            root = RotateLeft(root);
        } else if(bf == 2) {
            if(Balancefactory(root->left)<0) {
                root->left = RotateLeft(root->left);
            }
            root = RotateRight(root);
        }
        return root;
    }

    int Depth(Node* root){
        if(nullptr == root) return 0;
		return max(Depth(root->left),Depth(root->right))+1;
    }
};
int main() {
    AVLTree<int> intbst;
    int n;
    while(cin >> n) {
        intbst.Insert(n);
    }
    intbst.Print();
}

输入1 2 3 4 5,结果为:

[root@localhost avl]# ./a.out 
1
2
3
4
5
2--1
2--4
4--3
4--5

画出树的结构,可以看出,插入数据后,树会自动保持平衡
在这里插入图片描述
移除结点1,仍然保持平衡
在这里插入图片描述

4.3 BST vs. AVL

在这里插入图片描述
区别:AVL的插入和删除操作在BST的基础上加入平衡操作。

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-06-26 17:03:41  更:2022-06-26 17:03:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:37:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码