二叉排序树(BST)

121 阅读3分钟

二叉搜索树是为了更高效的查找、插入和删除,并不是为了对关键字进行排序。他的定义如下:

  1. 空树也是二叉排序树
  2. 若右子树不为空,则右子树的所有节点值都大于根节点
  3. 若左子树不为空,则左子树的所有节点值都大于根节点
  4. 二叉排序树的左右子树都是二叉排序树

二叉排序树(二叉搜索树)的节点定义如下:

struct TreeNode { 
    int key; 
    TreeNode* left; 
    TreeNode* right; // 维护其他信息,如高度,节点数量等 
    int size; // 当前节点为根的子树大小 
    int count; // 当前节点的重复数量 
    TreeNode(int value) : key(value), size(1), count(1), left(nullptr), right(nullptr) {} 
};

搜索树的遍历

 void inorderTraversal(TreeNode* root) { 
    if (root == nullptr) { return; } 
    inorderTraversal(root->left); 
    std::cout << root->key << " "; 
    inorderTraversal(root->right); 
 }

由中序遍历出的二叉搜索树的序列都是递增序列,该中序遍历的事件复杂度为O(n)

搜索树的查找

代码与遍历方式基本相同,若当前所处结点的值大于目标数则到左子树查找,若小于目标树则到右子树查找,直到找到目标树。如果未找到直接返回NULL即可,我们使用非递归的方式再实现一遍上述代码逻辑。

bool search(TreeNode* root, int target) { 
    TreeNode* current = root;
    if (current->key == target) { return true; }
    while(current != NULL && current->key != target) {
        if(target < current->key) current = current->left;
        else if(target > current->key) current = current->right;
        else return true;
    }
    return false;
}

时间复杂度为O(h)

搜索树的插入

在二叉搜索树root中插入一个value时,可能出现以下几种情况

  1. root为null,直接插入
  2. value小于root权值,在root的左子树插入value结点
  3. value大于root权值,在root的右子树插入value结点
  4. value等于root权值,让root的count++
TreeNode* insert(TreeNode* root, int value) {
  if (root == nullptr) {
    return new TreeNode(value);
  }
  if (value < root->key) {
    root->left = insert(root->left, value);
  } else if (value > root->key) {
    root->right = insert(root->right, value);
  } else {
    root->count++;  // 节点值相等,增加重复数量
  }
  root->size = root->count + (root->left ? root->left->size : 0) +
               (root->right ? root->right->size : 0);  // 更新节点的子树大小
  return root;
}

时间复杂度为O(h)

搜索树的删除

在删除搜索中的value时,需要先在书中find到该结点,删除时会出现以下情况:

  1. 该点的count>1,直接让count--就可以
  2. 结点为叶子结点,直接删除即可。
  3. 结点只有一个儿子,直接让他的儿子代替他即可
  4. 结点有两个儿子,让左子树的最大值或者时右子树的最小值代替这个结点
TreeNode* findMin(TreeNode* curr) {
  while(curr->left != nullptr) {
    curr = curr->left;
  }
  return curr;
}

TreeNode* remove(TreeNode* root, int value) {
  if(root == nullptr) {
    return root;
  }

  if(value < root->key) {
    root->left = remove(root->left, value);
  } else if(value > root->key) {
    root->right = remove(root->right, value);
  } else {
    // value == root->key
    if(root->count > 1) {
      root->count --;
    } else {
      // 删除该节点
      if(root->left == nullptr) {
        TreeNode* temp = root->right;
        delete root;
        return temp;
      } else if(root->right == nullptr) {
        TreeNode* temp = root->left;
        delete root;
        return temp;
      } else {
        // 查找右子树的最小值
        TreeNode* success = findMin(root->right);
        root->count = success->count;
        root->key = success->key;
        // 赋值完后进行删除
        root->right = remove(root->right, success->key);
      }
    }
  }
  return root;
}

效率分析

通过分析上面的操作可以看出二叉排序树与二分查找有点像,但是二分查找的判定树是唯一的,二叉排序树不唯一 二叉排序树的平均查找长度ASL=i=1nhinuminums ASL= \frac{\sum_{i=1}^nh_i·num_i}{nums}。 其中hih_i为第i层的高度、numinum_i是第i层的数量、nums是总的节点数量。
插入、删除时间复杂度O(logn)O(log{n})