课程笔记

二叉排序树

一、性质

  1. 左子树 < 根节点
  2. 右子树 > 根节点
  3. 中序遍历的结果是一个有序序列

数据结构,就是定义一种性质并且维护这种性质。

二、插入操作

  1. 插入新的结点,一定会做做为叶子节点

三、删除操作

  1. 删除度为0的结点,直接删除
  2. 删除度为1的结点,把孤儿子树挂到其父节点上面
  3. 删除度为2的结点,可以转化成删除度为1的结点

对于度为2的结点:

  1. 前驱:左子树的最大值
  2. 后继:右子树的最小值

四、随堂练习

  1. 插入顺序会影响最终的树形结构
  2. 不同的树形结构,查找效率不同

平均查找效率:结点查找次数的期望值, 总 次 数 结 点 数 量 \frac{总次数}{结点数量} ,假设每个结点都被等概率查找。

五、扩展内容

  1. 二叉排序树删除代码优化

    1. 删除掉处理度为0的代码逻辑,不影响代码整体功能
  2. 如何解决排名相关排名相关的检索需求

      1. 修改二叉排序树的结构定义,增加 size 字段,记录每棵树的节点数量
      2. k = L S − 1 k = LS - 1 k=LS1,根节点就是排名第 k 位的元素
      3. k ≤ L S k \le LS kLS,排名第 k 位的元素在左子树中
      4. k > L S , s e a r c h k ( r o o t − > r c h i l d , k − L S − 1 ) k \gt LS,search_k(root->rchild, k - LS - 1) k>LSsearchk(root>rchild,kLS1)
    1. 解决 Top-K 问题(找到小于第 k 位的所有元素)
      1. 根节点就是第 k 位元素的话,就把左子树中的值全部输出出来
      2. 第 k 位在左子树中,前 k 位元素全都在左子树中
      3. 第 k 位在右子树中,说明根节点和左子树中的元素,都是前 k 位元素里面的值
    2. 二叉排序树和快速排序的关系
      1. 二叉排序树是快速排序在思维逻辑结构层面用的数据结构
      2. 思考1:快速排序的时间复杂度和二叉排序树建树时间复杂度之间的关系
      3. 思考2:快速选择算法和二叉排序树之间的关系
      4. 程序=算法+数据结构

    所谓算法设计及分析能力:分类讨论及归纳总结的能力

六、代码演示

#include <stdio.h>
#include <stdlib.h>

#define KEY(n) (n ? n->key : -1)
#define SIZE(n) (n ? n->size : 0)
#define L(n) (n ? n->lchild : NULL)

typedef struct Node {
    int key, size;
    struct Node *lchild, *rchild;
} Node;

void update_size(Node *root);

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->size = 1;
    p->lchild = p->rchild = NULL;
    return p;
}

int search(Node *root,int val) {
    if (!root) return 0;
    if (root->key == val) return 1;
    if (root->key > val) return search(root->lchild, val);
    return search(root->rchild, val);
}

Node *insert(Node *root, int key) {
    if (!root) return getNewNode(key);
    if (root->key == key) return root;
    if (key < root->key) root->lchild = insert(root->lchild, key);
    else root->rchild = insert(root->rchild, key);
    update_size(root);
    return root;
}

Node *predecessor(Node *root) {
    Node *temp = root->lchild;
    while (temp->rchild) temp = root->rchild;
    return temp;
}

int search_k(Node *root, int k) {
    if (!root) return -1;
    if (SIZE(L(root)) == k - 1) return root->key;
    if (k <= SIZE(L(root))) {
        return search_k(root->lchild, k);
    }
    return search_k(root->rchild, k - SIZE(L(root->lchild)) - 1);
}

void update_size(Node *root) {
    root->size = SIZE(root->lchild) + SIZE(root->rchild) + 1;
    return ;
}

Node *erase(Node *root, int key) {
    if (!root) return NULL;
    if (key < root->key) {
        root->lchild = erase(root->lchild, key);
    } else if (key > root->key) {
        root->rchild = erase(root->rchild, key);
    } else {
        /*if (root->lchild == NULL && root->rchild == NULL) {
        free(root);
        return NULL;
    } else */
        if (root->lchild == NULL || root->rchild == NULL) {
            Node *temp = root->lchild ? root->rchild : root->rchild;
            free(root);
            return temp;
        } else {
            Node *temp = predecessor(root);
            root->key = temp->key;
            root->lchild = erase(root->lchild, temp->key);
        }
    }
    update_size(root);
    return root;
}

void clear(Node *root) {
    if (!root) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

void print(Node *root) {
    printf("%d[%d], %d, %d\n", KEY(root), SIZE(root),KEY(root->lchild), KEY(root->rchild));
    return ;
}

void output(Node *root) {
    if (!root) return ;
    output(root->lchild);
    print(root);
    output(root->rchild);
    return ;
}

void output_k(Node *root, int k) {
    if (k == 0 || !root) return ;
    if (k <= SIZE(L(root))) {
        output_k(root->lchild, k);
    } else {
        output(root->lchild);
        print(root);
        output_k(root->rchild, k - SIZE(L(root->lchild)) - 1);
    }
    return ;
}

int main() {
    int op, val;
    Node *root = NULL;
    while (~scanf("%d%d", &op, &val)) {
        switch (op) {
            case 0: printf("search %d, result : %d\n", val, search(root, val)); break;
            case 1: root = insert(root, val); break;
            case 2: root = erase(root, val); break;
            case 3: {
                printf("search k = %d, result : %d\n", val, search_k(root, val));
                output_k(root, val);
                printf("----------\n");
            } break;
            case 4: {
                printf("output top-%d elements\n", val);
                output_k(root, val);
                printf("----------\n");
            } break;
        }
        if (op == 1 || op == 2) {
            output(root);
            printf("------------\n");
        }
    }
    return 0;
}

平衡二叉树排序树 之 AVL 树

一、学习重点

  1. 平衡二叉树排序树,本质上也是二叉排序树,所以拥有二叉排序树的所有性质
  2. 平衡二叉树排序树的学习重点,在于平衡条件以及平衡调整的相关学习

二、性质

  1. 平衡条件:左右子树高度差不超过 1

三、课中思考

  1. AVL 树改进的是节点数量的下限
  2. 树高 = 生命长度,节点数量 = 生命财富,不同的算法,达到的结果是不同的
  3. 教育提升的是底限,而不是上限,上限取决于能力和运气

思考:

  1. 高度为 H 的BS树,所包含的结点数量在上面范围之内?
  2. 高度为H的AVL树,所包含的结点数量在上面范围之内?

BS树: H ≤ H\le HSIZE ≤ 2 H − 1 \le2^H-1 2H1

AVL树:设low(H)为高度为H的树最少包含结点,则 l o w ( H − 2 ) + l o w ( H − 1 ) + 1 ≤ S I Z E ≤ 2 H − 1 low(H - 2) + low(H - 1) + 1 \le SIZE \le 2^H - 1 low(H2)+low(H1)+1SIZE2H1, 设结点数为n,树高为h,则有 h = log ⁡ a ( n ) h=\log_a(n) h=loga(n) 1.5 ≤ a ≤ 2 1.5\le a \le 2 1.5a2

四、平衡调整策略

基本操作

  1. 左旋:要旋转的结点K1的右节点K3成为新的根节点,原K3的左子树称为K1的右子树

  2. 右旋:要旋转的结点K1的左节点K2成为新的根节点,原K2的右子树称为K1的左子树

  1. 发生在回溯阶段的,第一个失衡节点处

  2. 理解平衡调整策略的关键在于:分析清楚四种情况下,ABCD 四棵子树树高的关系

  3. LL,大右旋

    设A、B、C、D的树高分别为 h 1 h_1 h1 h 2 h_2 h2 h 3 h_3 h3 h 4 h_4 h4,则存在以下关系:

    h 1 + 1 = m a x ( h 3 , h 4 ) + 3 → h 1 = m a x ( h 3 , h 4 ) + 2 = h 2 + 1 h_1 + 1 = max(h_3, h_4) + 3\rightarrow h_1 = max(h_3, h_4) + 2 = h_2 + 1 h1+1=max(h3,h4)+3h1=max(h3,h4)+2=h2+1

    则调整后:

    A和K1, B和K3高度相等

  4. LR,先小左旋,再大右旋

    image-20201220220158253

    m a x ( h 2 , h 3 ) = h 4 = h 1 max(h_2, h_3) = h_4 = h_1 max(h2,h3)=h4=h1

  5. RL,先小右旋,再大左旋

  6. RR,大左旋

五、代码演示

  1. 插入和删除以后,注意重新计算树高字段
  2. 引入了 NIL 阶段,代替 NULL,NULL 不可访问,NIL 是一个是实际节点,可访问
#include <iostream>
#include <cstdio>
#include <stdlib.h>

#define L(n) (n->lchild)
#define R(n) (n->rchild)
#define H(n) (n->h)

typedef struct Node {
    int key, h;
    struct Node *lchild, *rchild;
} Node;

Node __NIL;
#define NIL (&__NIL)
__attribute__((constructor))
void init_NIL() {
    NIL->key = 0, NIL->h = 0;
    NIL->lchild = NIL->rchild = NIL;
    return ;
}

void update_high(Node *root);

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->h = 1;
    p->lchild = p->rchild = NIL;
    return p;
}

void clear(Node *root) {
    if (root == NIL) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
    return ;
}

Node *left_rotate(Node *root) {
    Node *temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    update_high(root);
    update_high(temp);
    return temp;
}

Node *right_rotate(Node *root) {
    Node *temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    update_high(root);
    update_high(temp);
    return temp;
}

Node *maintain(Node *root) {
    if (abs(H(L(root)) - H(R(root))) <= 1) return root;
    if (root->lchild->h > root->rchild->h) {
        if (root->lchild->lchild->h < root->lchild->rchild->h) {
            root->lchild = left_rotate(root->lchild);
        }
        root = right_rotate(root);
    } else {
        if (root->rchild->rchild->h < root->rchild->lchild->h) {
            root->rchild = right_rotate(root->rchild);
        }
        root = left_rotate(root);
    }
    return root;
}

void update_high(Node *root) {
    root->h = (H(L(root)) > H(R(root)) ? H(L(root)) : H(R(root))) + 1;
}

void print(Node *root) {
    printf("%d[%d], %d, %d\n", root->key, root->h, root->lchild->key, root->rchild->key);
    return ;
}

void output(Node *root) {
    if (root == NIL) return ;
    print(root);
    output(root->lchild);
    output(root->rchild);
    return ;
}

Node *insert(Node *root, int key) {
    if (root == NIL) return getNewNode(key);
    if (root->key == key) return root;
    if (root->key > key) {
        root->lchild = insert(root->lchild, key);
    } else {
        root->rchild = insert(root->rchild, key);
    }
    update_high(root);
    return maintain(root);
}

Node *predecessor(Node *root) {
    Node *temp = root->lchild;
    while (temp->rchild) temp = temp->rchild;
    return temp;
}

Node *erase(Node *root, int key) {
    if (root == NIL) return NIL;
    if (key < root->key) {
        root->lchild = erase(root->lchild, key);
    } else if (root->key < key) {
        root->rchild = erase(root->rchild, key);
    } else {
        if (root->lchild == NIL || root->rchild == NIL) {
            Node *temp = root->lchild ? root->lchild : root->rchild;
            free(root);
            return temp;
        } else {
            Node *temp = predecessor(root);
            root->key = temp->key;
            root->lchild = erase(root->lchild, temp->key);
        }
    }
    update_high(root);
    return maintain(root);
}

int main() {
    int op, val;
    Node *root = NIL;
    while (~scanf("%d%d", &op, &val)) {
        switch (op) {
            case 0: root = erase(root, val);
            case 1: root = insert(root, val);
        }
        output(root);
        printf("--------\n");
    }
    return 0;
}

扩展小tips

vim按列添加和删除:

  1. 按列添加,首先按ctrl + V,接下来用箭头键选中列,再按shift + I,输出要添加的内容,再按ESC键。

    image-20201220222447050
    image-20201220222533087

  2. 按列删除,首先按ctrl + V,接下来用箭头键选中列,再按x即可删除选择列的内容。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值