基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

一、什么是二叉查找树

  二叉查找树(Binary Search Tree)是一种特殊的二叉树,对于一个二叉查找树,树中的每个结点X,它的左子树中所有关键字的值都小于X的关键字值;而它的右子树中所有关键字的值大于X的关键字值。这意味着,该树的所有元素可以使用一种统一的方式进行排序,因此,二叉查找树又称为二叉排序树。下图即为一个二叉查找树:

基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

二、如何在 BST 中查找一个结点

  二叉查找树很适合进行查找操作。以上图中的二叉查找树为例:如果需要在该 BST 中查找值 10,先从根结点开始进行比较,10小于13,根据 BST 的性质,可以知道如果该 BST 中有结点值为10的话,那么该结点必然位于根结点的左子树中;再从结点 5 开始,可知 10 在结点 5 的右子树;再从结点 8 开始,10 在结点 8 的右子树,最终找到元素 10。

  用算法来描述,在一个 二叉查找树 T(T 为二叉树的根结点)中查找元素 n:

1)若 T 为空,则 n 不在 BST 中;

2)判断 n 与根结点值(T->val)的关系;

3)如果 n 等于 T->val,则找到了元素 n;

4)如果 n 大于 T->val,则去根结点的右子树 (T->right) 继续查找。将 T->right 作为 T,回到第 1 步;

5)如果 n 小于 T->val,则去根结点的左子树 (T->left ) 继续查找。将 T->left 作为 T,回到第1步。

  算法实现如下:

typedef int ElementType;

typedef struct BinaryTreeNode_ {
    ElementType val;
    struct BinaryTreeNode_ *left;
    struct BinaryTreeNode_ *right;
}BinaryTreeNode;

typedef BinaryTreeNode *BiTreeNode;

// 查找一个结点
BiTreeNode FindNode(BiTreeNode BiTree,ElementType n)
{
    if (BiTree == NULL)        // 如果头结点为空,表明没有找到元素 n
        return NULL;
    if (BiTree->val < n)    // 结点值小于查找元素值,说明元素在结点的右边
        return FindNode(BiTree->right, n);    // 递归,去右子树继续查找
    else if (BiTree->val > n)
        return FindNode(BiTree->left, n);    // 递归,去左子树继续查找
    else
        return BiTree;
} 

   该查找算法的时间复杂度为 O(log2N)。

 

三、向 BST 中插入一个结点

  向 BST 中插入一个结点 n 时,若 BST 中已经存在与该结点值相等的结点,则判断结点已存在,不做任何操作;如果 BST 中不存在该结点,则将该结点将作为一个新的叶子结点插入到 BST 中去(新结点总是 BST 的叶子结点),且需要保证插入后的树是一个二叉查找树。

  可以以类似查找算法的方式来遍历二叉树,从而找到插入的位置:

  用算法来描述,向一个二叉查找树 T(T 为二叉树的根结点)中插入一个结点,节点元素为 n:

1)若 T 为空,则将新结点插入到 T 所在的位置;

2)若 T 不为空,且 T->val 小于 n,则表明应该把 n 插入到 T 的右子树。将 T->right 作为 T,回到第 1 步;

3)若 T 不为空,且 T->val 大于 n,则表明应该把 n 插入到 T 的左子树。将 T->left 作为 T,回到第 1 步。

4)若 T->val 等于 n,则直接返回 T。

// 插入一个结点
BiTreeNode InsertNode(BiTreeNode BiTree, ElementType n)
{
    if (BiTree == NULL) {    // 树为空,或已到达叶子结点
        // 为新结点分配内存
        BiTreeNode newNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode));
        if (newNode == NULL) {
            perror("malloc failed!\n");
            exit(EXIT_FAILURE);
        }
        newNode->val = n;
        newNode->left = NULL;
        newNode->right = NULL;
        BiTree = newNode;
    }
    else if (BiTree->val < n) {    // 插入值大于当前结点的值,则将元素插入到结点的右子树
        BiTree->right = InsertNode(BiTree->right, n);
    }
    else if (BiTree->val > n) {    // 插入值小于当前结点的值,则将元素插入到结点的左子树
        BiTree->left = InsertNode(BiTree->left, n);
    }
    
    return BiTree;
    
}

 

四、遍历 BST

  在上一篇博客中已经介绍了二叉树的遍历方法,主要有三种:先序遍历、后序遍历和中序遍历。为了更好的体现二叉查找树的特性,我们使用中序遍历来遍历它:

// 中序遍历 BST
void TraverseTree(BiTreeNode BiTree)
{
    if (BiTree != NULL) {
        TraverseTree(BiTree->left);
        cout << BiTree->val << endl;
        TraverseTree(BiTree->right);
    }

}

 

五、删除一个结点

  删除操作比较复杂,分为三种情况:

1)被删除结点为叶子结点:

  可以直接删除该结点,如图:

基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

2)被删除结点有一个左孩子或一个右孩子:

  将孩子结点设为该结点的父结点的孩子后,即可删除该结点:

基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

  如图,结点 2 是结点 5 的左孩子,他有一个右孩子 4。删除结点 2 后,其右孩子 4 替代原来的结点 2 成为结点 5 的左孩子。

基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

  如图,结点 16 是结点 18 的左孩子,他有一个左孩子 15。删除结点 16 后,其左孩子 15 替代原来的结点 16 成为结点 18 的左孩子。

3)被删除结点有两个孩子结点:

  这种情况比较复杂,一般的删除策略是:用被删除结点的右子树中的最小结点替代被删除结点,并递归地删除这个最小数据结点:

基本数据结构 -- 二叉查找树的插入、删除、查找和遍历

 

// 删除一个结点
BiTreeNode DeleteNode(BiTreeNode BiTree, ElementType n)
{
    BiTreeNode tmpNode = (BiTreeNode)malloc(sizeof(BinaryTreeNode));
    if (tmpNode == NULL) {
        perror("malloc failed!\n");
        exit(EXIT_FAILURE);
    }

    // 先找到要删除的结点在二叉树中的位置
    if (BiTree == NULL) {    // 没有找到元素
        perror("can`t find element\n");
        return NULL;
    }
        
    if (BiTree->val < n) {        // 元素值大于结点元素值,则去结点右子树寻找
        BiTree->right = DeleteNode(BiTree->right, n);
        return BiTree;
    }
    else if (BiTree->val > n) {    // 元素值小于结点元素值,则去结点左子树寻找
        BiTree->left = DeleteNode(BiTree->left, n);
        return BiTree;
    }
    // 找到结点
    else {
        // 若该结点有两个孩子结点
        if (BiTree->left && BiTree->right) {
            tmpNode = FindMin(BiTree->right);    // 寻找右子树最小结点
            tmpNode->right = DeleteMin(BiTree->right);    // 删除右子树的最小结点
            tmpNode->left = BiTree->left;        // 左子树保持不变
            return tmpNode;
        }
        else {    // 只有1个或没有孩子结点
            tmpNode = BiTree;
            if (BiTree->right == NULL) {    // 没有右子树,则直接返回左子树
                BiTree = BiTree->left;
                return BiTree;
            }
            if (BiTree->left == NULL) {        // 没有左子树,则直接返回右子树
                BiTree = BiTree->right;
                return BiTree;
            }
        }
    }
}

// 查找最左叶子结点(最小的结点)
BiTreeNode FindMin(BiTreeNode BiTree)
{
    if (BiTree == NULL)
        return NULL;
    if (BiTree->left == NULL)    // 结点没有左子树,即为最左叶子结点
        return BiTree;
    else        // 有左子树,则继续在左子树中查找
        return FindMin(BiTree->left);
}

// 删除最小结点
BiTreeNode DeleteMin(BiTreeNode BiTree)
{
    if (BiTree->left == NULL)
        return BiTree->right;
    else {
        BiTree->left = DeleteMin(BiTree->left);
        return BiTree;
    }

}

 

参考资料:

《数据结构与算法分析 C 语言描述》

 

上一篇:


下一篇:实验四 二叉树