重写数据结构--二叉搜索树

性质:

根节点左边的节点关键字都比根节点的关键字小;根节点右边的节点关键字都比根节点的关键字大;

结构体:

typedef struct BinarySearchTreeNode {
    int value;
    BinarySearchTreeNode* parent;
    BinarySearchTreeNode* left;
    BinarySearchTreeNode* right;
}BinarySearchTreeNode;

按序输出:

中序遍历(复杂度为 O(n) ):

void InOrder(BinarySearchTreeNode* root) {
    if (root != NULL) {
        InOrder(root->left);
        cout << root->value << " ";
        InOrder(root->right);
    }
}

查找元素:

(递归实现)

BinarySearchTreeNode* Search(BinarySearchTreeNode* root, int x) {
    if (root == NULL || x == root->value) { //感觉这句好机智
        return root;
    } else {
        if (x > root->value) {
            return Search(root->right, x);
        } else {
            return Search(root->left, x);
        }
    }
}

(非递归实现)

BinarySearchTreeNode* Search_(BinarySearchTreeNode* root, int x) {
    while( root!= NULL && x != root->value) {
        if (x > root->value) {
            root = root->right;
        } else {
            root = root->left;
        }
    }
    return root;
}

最小元素和最大元素:

//最小
BinarySearchTreeNode* Min(BinarySearchTreeNode* root) {
    BinarySearchTreeNode* ans = NULL;
    while (root != NULL ) {
        ans  = root;
        root = root->left;
    }
    return ans;
}

//最大
BinarySearchTreeNode* Max(BinarySearchTreeNode* root) {
    BinarySearchTreeNode* ans = NULL;
    while (root != NULL ) {
        ans  = root;
        root = root->right;
    }
    return ans;
}

寻找前驱和后继:

《算法导论》给出了一个找后继的例子:
如果该节点有右子树,直接返回右子树的最小值;
否则沿着该节点向上找双亲,找到一个节点,这个节点是它的双亲的左子树,就返回它的双亲;
但是我觉得更直观的理解是不断向上找双亲,然后直接比较关键字,遇到第一个关键字比寻找的元素大,就终止返回;
他那样写一定有他的理由,因为是直观的去按照中序遍历的思路去寻找,肯定是很完备的;只是目前我找不出我的思路的错误

//算法导论上的实现
BinarySearchTreeNode* FindSuccessor(BinarySearchTreeNode* root) {
    if (root->right != NULL) {
        return Min(root->right);
    } else {
        BinarySearchTreeNode* ans = root.parent;
        while(ans != NULL && root == ans->right) {
            root = ans;
            ans  = ans->parent;
        }
        return ans;
    }
}

寻找前驱应该是对称的;

插入和删除:

插入:
首先传入的树根一定是引用,其次,先建立一个临时节点便于之后插入,向下索引时(插入的位置一定是NULL),所以要同时记录插入位置的双亲节点,然后再根据大小关系判断插入。

void Insert(BinarySearchTreeNode* &root, int z) {

    BinarySearchTreeNode* temp = new BinarySearchTreeNode;
    temp->value = z;
    temp->parent = temp->left = temp->right = NULL;

    BinarySearchTreeNode* index = root;
    BinarySearchTreeNode* index_parent = NULL; 
    while(index != NULL) {
        index_parent = index;
        if (temp->value > index->value) {
            index = index->right;
        } else {
            index = index->left;
        }
    }
    temp->parent = index_parent;
    if (index_parent == NULL) {
        root = temp;
    } else if (temp->value > index_parent->value) {
        index_parent->right = temp;
    } else {
        index_parent->left = temp;
    }

}

删除:
被删除节点为z
分三种情况:

  • z没有孩子,直接删除,并修改双亲结点指向z的指针为NULL
  • z只有一个孩子,将孩子提到z的位置,并修改双亲结点指向z的指针为z的孩子
  • z有两个孩子,先找z的后继,如果后继不是z的右子树的根节点,先把后继换到右子树的根节点;接着,把后继提上来,再改变后继的左子树为z的左子树

替换的子过程:

//以v为根的子树替换一棵以u为根的子树,此时u作为一个单独的节点保留自己的信息,但是独立出这棵树了
void TransPlant(BinarySearchTreeNode* & root, BinarySearchTreeNode* u, BinarySearchTreeNode* & v) {
    if(u->parent == NULL) {
        root = v;
    } else {
        BinarySearchTreeNode* & Parent = u->parent;
        if (u == Parent->left) {
            Parent->left = v;
        } else {
            Parent->right = v;
        }
    }
    if (v != NULL) {
        v->parent = u->parent;
    }
}

删除节点:

void Delete(BinarySearchTreeNode* &root, BinarySearchTreeNode* z) {     //如果传入的参数是int,那就调用一次Search
    if (root == NULL) {
        cout << "Already empty!" << endl;
        return;
    }
    if (z == NULL) {
        cout << "Not exist!" << endl;
        return;
    }
    if (z->left == NULL) { 
        TransPlant(root, z, z->right);
    } else if (z->right == NULL) {
        TransPlant(root, z, z->left);
    } else { //上两种情况是只有一个子树,或者没有子树,下面的情况是有两个子树
        BinarySearchTreeNode* y = Min(z->right);
        if (y->parent != z) {   //如果y不是z的右子树, 把y换到z的右子树
            TransPlant(root, y, y->right);
            y->right = z->right;
            y->right->parent = y;
        }

        TransPlant(root, z, y);
        y->left = z->left;
        y->left->parent = y;
    }
}

再次考虑到,什么时候该用引用呢?当需要在函数中改变参数的时候,传参就加上引用;但是遇到很多遍历的情况,会经常不知所措,所以目前采取的策略是,在确保不会错的情况下,只要函数里对这个变量有赋值的操作,我都会加上引用;

构建随机二叉搜索树:

将n个不同的关键字按照随机次序单纯使用插入操作建树;
一个有n个不同关键字的随机构建二叉搜索树的期望高度为 log(n)

双亲的意义:

用来寻找前驱和后继;

和堆&优先队列的区别

堆是左右子树的根节点都会比双亲节点大,并且优先队列插入和删除的操作是不断交换的过程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值