数据结构之二叉树&完全二叉树&二叉搜索树(BST)&平衡二叉搜索树(AVL)

complete binary tree 完全二叉树

1) 第0层到第h - 1层是满的

2) 最后一层是从左边开始排的。 as far left as possible
![[Pasted image 20220612112845.png]]

优先队列的实现:二叉堆

二叉堆

二叉堆是 1)完全二叉树 2)每一个节点都有一个对应的值(key)3)如果u是一个中间节点,则u的key比所有它的儿子节点的key都小。也就是说, 2)表示树T有n个节点,3)表示u的key是以u为根的子树中的最小的key。

一个序列对应的二叉堆不是唯一的。但这个堆的根是这个堆的最小值。

插入:O(log N)shift up 操作

1) 首先把新加入的节点设为u,放在叶子层的最右边,(建立在完全二叉树的定义之上)如果叶子层满了就放到下一层。

2) 然后去比较u的值和它的父亲fa的值,如果u的值小于fa的值,就交换节点u 和节点fa

3) 重复步骤2),直到不能交换为止,此时的这个序列又对应了一个符合条件的二叉堆。

删除最小值:O(log N)shift down操作

1)输出最小值,把最小值节点和叶子层最右边的节点u交换。

2)比较u的值和u的左右两个孩子中最小值的值,如果孩子中有更小的值就交换。

3)重复步骤2),直到不能交换为止。

注意到这其中都涉及一个问题,如何找到叶子层最右边的节点?

注意到其实这里有一个隐形条件:给定二叉堆的节点总数n。

于是一切变得简单了起来。

1) 把n写成一个01串的二进制形式。

2)从最高位开始(最左边是最高位),跳过第一个1,然后遇到0往左走,遇到1往右走。走完这个01串就走到了叶子层最右边的节点。

整个过程事件复杂度是O(log N)

空间复杂度:O (N)

对于完全二叉树来说,设一个节点为u,则它的左儿子为2u,右儿子为2u+1,这样空间利用是最小的。

Binary Heaps in Dynamic Arrays

insert

image-20220530185546448

delete

image-20220530185626420

Build a Binary Heap in Array

问:给定一个没有顺序的数组,然后排成一个二叉堆的结构所需要的时间复杂度最小是多少?

O(N),如果对每个元素执行add操作,则时间复杂度是O(NlogN)。

更优解:

给定一个乱序的数组,输出一个符合二叉堆结构的数据。(a[i]的左右儿子分别为a[2 * i], a[2 * i + 1])

依次从最后一个非叶子节点到根节点挨个做shift down 操作。相当于每次保证从当前层往下的节点都是满足条件的。这样的话来考虑每一层,倒数第一层(叶子层)有n/2个节点,倒数第k层有n/2^k 个节点,每一层的节点最多被下放到最后一层,也就是k次 (应该是k - 1次,不过不咋影响),因此总共的时间就是

\[n(\frac{k}{2^k} + \frac{k - 1}{2^{k - 1}}+ ... + \frac{1}{2}) \]

计算发现也就是O(N)

image-20220530190115227

考虑另外的问题:

动态前驱查找:dynamic predecessor search,

Successor Query 查询后继

有点相当于lowerbound,找val的前驱(小于或等于它的第一个数)

二叉搜索树 2-ary

注意在非平衡状态下,复杂度要用O(h), 而不是logN

二叉搜索树( binary search tree)

  • 二叉搜索树( binary search tree)是一棵二叉树,可能为空; 一棵非空的二叉搜索树满足以下特征:
  1. 每个元素有一个关键字,并且任意两个元素的关键字都不同;因此,所有的关键字都是唯一的。
  2. 在根节点的左子树中,元素的关键字(如果有的话)都小于根节点的关键字。(降序排列)
  3. 在根节点的右子树中,元素的关键字(如果有的话)都大于根节点的关键字。(升序排列)
  4. 根节点的左、右子树也都是二叉搜索树。
  • 二叉搜索树的所有元素都有一个唯一的关键字,这个要求可以去除,然后再用小于等于代替特征2)中的小于,用大于等于代替特征3)中的大于,这样的二叉树称为有重复值的二叉搜索树( binary search tree with duplicates ),其实也可以在每个节点记录重复值个数。

它的中序遍历是有序的。

插入

非常类似于二分,二叉搜索树在插入的值是单调的情况下会退化成一条链。

删除

  • 假设要删除的节点是p,我们需要考虑三种情况: 1. p是树叶; 2. p只有一棵非空子树; 3. p有两棵非空子树。
  1. 情况1. 要删除的节点是叶节点。处理的方法是释放该叶节点空间,若是根节点,则令根为NULL。
  2. 情况2. 要删除的节点p只有一棵子树。如果p没有父节点(即p是根节点),则p的唯一子树的根节点成为新的搜索树的根节点。如果p有父节点pp, 则修改pp的指针域,使得它指向p的唯一孩子,然后释放节点p。
  3. 情况3. 要删除的节点p具有两棵非空子树。我们先将该节点的元素替换为它的左子树的最大元素或右子树的最小元素,然后把替换元素的节点删除。
  4. 注意,右子树的最小关键字节点(左子树的最大关键字节点)要么没有子树,要么只有一棵子树。要在一个节点的左子树中查找关键字最大的元素,先移动到左子树的根,然后沿着右孩子指针移动,直到右孩子指针为NULL的节点为止。类似地,要在一个节点的右子树中查找关键字最小的元素,先移动到右子树的根,然后沿着左孩子指针移动,直到左孩子指针为NULL的节点为止。注意,要删除一个左右子树都不为空的元素节点,我们的算法是: 先替换,然后删除一个叶子或个仅有单子树的节点。然后视情况把那个单子树接上。

在实现的时候常见的写法是分为叶子/有左子树/有右子树三种情况。

为了解决退化的情况,引入平衡二叉树:

Balanced Binary Tree

Difinition . balanced.

A binary tree T is balanced if the following holds on every internal node u of T: The height of the left subtree of u differs from that of the right subtree of u by at most 1.

Theorem: a balanced binary tree with n nodes has height O(log⁡n).

我还不信我搞不懂AVL树!!

1、左-左型:做右旋。

2、右-右型:做左旋转。

3、左-右型:先做左旋,后做右旋。

4、右-左型:先做右旋,再做左旋。

定义:平衡因子(balanced factor), 左子树深度同右子树深度的差。对于一个平衡二叉树而言,其节点的平衡因子的取值只可能是0、1、-1。

通过下面这张图,我们可以清晰的发现平衡因子完全揭示了旋转的模式。从直观上来讲,树的左边重了就往右边旋转,右边重了就往左边旋转。

于是好像理论部分就这样结束了。

下面是小一的板子部分嘿嘿。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

struct AVLNode{
    int val;
    AVLNode* l;
    AVLNode* r;
    int height;
    int num;
    int size;
    AVLNode():val(0), height(1), num(1),size(1),l(nullptr),r(nullptr){};
    AVLNode(int value):val(value),l(nullptr),r(nullptr), height(1), num(1), size(1){}
};

class AVL{
public:
    typedef AVLNode Node;
    
    int Getsize(Node* cur){
        if(cur) return cur->size;
        return 0;
    }

    int GetHeight(Node* cur){
        if(cur) return cur->height;
        return 0;
    }
    
    void update(Node*& cur){
        cur->size = Getsize(cur->l) + Getsize(cur->r) + cur->num;
        cur->height = max(GetHeight(cur->l), GetHeight(cur->r)) + 1;
    }

    void RotateRight(Node*& cur){
        Node* lson = cur->l;
        cur->l = lson->r;
        lson->r = cur;
        update(cur);
        update(lson);
        cur = lson;
    }

    void RotateLeft(Node*& cur){
        Node* rson = cur->r;
        cur->r = rson->l;
        rson->l = cur;
        update(cur);
        update(rson);
        cur = rson;
    }

    inline void RotateRL(Node*& cur){
        RotateRight(cur->r);
        RotateLeft(cur);
    }

    inline void RotateLR(Node*& cur){
        RotateLeft(cur->l);
        RotateRight(cur);
    }

    void Balance(Node*& cur){
        int curfac = GetHeight(cur->l)- GetHeight(cur->r);
        if(curfac == 2){
            if(GetHeight(cur->l->l) - GetHeight(cur->l->r) >= 1)  RotateRight(cur);
            else if(GetHeight(cur->l->l) - GetHeight(cur->l->r) <= -1) RotateLR(cur);
        }
        if(curfac == -2){
            if(GetHeight(cur->r->r) - GetHeight(cur->r->l) >= 1)  RotateLeft(cur);
            else if(GetHeight(cur->r->r) -  GetHeight(cur->r->l) <= -1) RotateRL(cur);
        }
    }

    void insert(Node*& cur, int value){
        if(cur == nullptr){ 
            cur = new Node(value);
            return; 
        }
		if(cur->val == value) {cur->num += 1; update(cur); return;}
        insert(cur->val < value ? cur->r : cur->l, value);
        update(cur); Balance(cur);
    }

    void erase(Node*& cur, int value){
        if(cur == nullptr) return;
        if(cur->val != value){
            erase(cur->val < value ? cur->r: cur->l, value);
            update(cur); Balance(cur);
            return;
        }
        if(cur->num > 1) { cur->num -= 1; update(cur); return;}
        if(cur->l && cur->r){
            Node* q = cur->r;
            while(q->l) q = q->l;
            cur->num = q->num;
            cur->val = q->val;
            q->num = 1;
            erase(cur->r, q->val);
            update(cur); Balance(cur);
        }
        else{
            if(cur->l) cur = cur->l, update(cur);
            else if(cur->r) cur = cur->r, update(cur);
            else cur = nullptr;
        }
    }

    int ranking(Node* cur, int value){
        if(cur->val == value) return Getsize(cur->l) + 1;
        if(cur->val < value)  return ranking(cur->r, value) + Getsize(cur->l) + cur->num;
        return ranking(cur->l, value);
    }

    int k_th(Node* cur, int k){
        if(Getsize(cur->l) >= k)   return k_th(cur->l, k);
        if(Getsize(cur->l) + cur->num >= k) return cur->val;
        return k_th(cur->r, k - Getsize(cur->l) - cur->num);
    }
    int GetPre(Node* cur, int value){
        int ans = -1e8;
        Node* q = cur;
        while(q){
            if(q->val == value){
                if(q->l) {
                    q = q->l;
                    while(q->r) q = q->r;
                    ans = q->val;
                }
                break;
            }
            if(q->val < value && q->val > ans)   ans = q->val;
            q = q->val < value ? q->r: q->l;
        }
        return ans;
    }  

    int GetNext(Node* cur, int value){
        int ans = 1e8;
        Node* q = cur;
        while(q){
            if(q->val == value){
                if(q->r){
                    q = q->r;
                    while(q->l) q = q->l;
                    ans = q->val;
                }
                break;
            }
            if(q->val > value && q->val < ans) ans = q->val;
            q = q->val < value ? q->r : q->l;
        }
        return ans;
    }

    void inorder(Node* cur) {
		if (cur) {
			inorder(cur->l);
            cout << cur->val << ' ';
			inorder(cur->r);
		}
	}
};


int Q,op,x;

int main() {
    AVL avl;
    scanf("%d", &Q);
    AVLNode* root = nullptr;
    while(Q--){
        scanf("%d%d", &op, &x);
        if(op == 1) avl.insert(root, x);
        else if(op == 2) avl.erase(root, x);
        else if(op == 3) printf("%d\n", avl.ranking(root, x));
		else if(op == 4) printf("%d\n", avl.k_th(root, x));
		else if(op == 5) printf("%d\n", avl.GetPre(root,x));
		else printf("%d\n", avl.GetNext(root, x));
    }        

    
	system("pause");
	return 0;
}

参考:

http://data.biancheng.net/view/59.html 这篇的图真的画的很好,把旋转讲得很清楚

posted @ 2022-06-12 11:36  爱吃番茄的玛丽亚  阅读(179)  评论(0)    收藏  举报