二叉查找树

来源:互联网 发布:中国旅游服务贸易数据 编辑:程序博客网 时间:2024/06/02 03:01

不知道为什么,之前写的二叉查找树被CSDN删掉了,往上搜了一下好像好挺多这种情况,不管了,就当是为了学习吧,再写一遍好了。

二叉查找树

什么是二叉查找树

二叉查找树是一种特殊的二叉树,之所以特殊是因为它的查找效率很高,而查找效率高的原因是对节点的组织方式遵从下面的两条规则:
1. 所有的父节点的左子树中的元素的key小于父节点的key
2. 所有的父节点的右子树中的结点的key大于父节点的key

查找

既然称之为查找树,先来说说它是如何查找到一个key所在的节点的。
简单的说就是从树根开始,如果要查找key大于根的key值,则在它的又子树中找,否则就在左子树中找,这样下来,数有多高,查找的最坏情况就是多少,所以查找的时间复杂度为:O(h).

Node *tree_search(Node* tree, int key){    Node *x = tree;    while(x && x->key != key)    {        if(key > x->key)            x = x->right;        else            x = x->left;    }    return x;}

二叉树的最大元素和最小元素

最大元素和最小元素分别是树的最右侧元素和最左侧元素:

最小元素:

Node* tree_minimum(Node* node){    while(node->left)        node = node->left;    return node;}

最大元素:

Node* tree_maximum(Node* node){    while(node->right)        node=node->right;   return node;}

树的遍历

树的遍历有三种方式:

  1. 先输出左儿子,再输出父节点,最后输出右儿子,叫中序遍历。
  2. 先输出父节点,再输出左儿子,最后输出右儿子,叫前序遍历
  3. 先输出左儿子,再输出右儿子,最后输出父节点,叫后续遍历

分别为:

中序遍历:

void tree_inorder_walk(Node * tree){    if(!tree)        return;    tree_inorder_walk(tree->left);    PRINT tree    tree_inorder_walk(tree->right);}

前序遍历:

void tree_inorder_walk(Node * tree){    if(!tree)        return;    PRINT tree    tree_inorder_walk(tree->left);    tree_inorder_walk(tree->right);}

后序遍历:

void tree_inorder_walk(Node * tree){    if(!tree)        return;    tree_inorder_walk(tree->left);    tree_inorder_walk(tree->right);    PRINT tree}

前驱和后继

前驱和后继的概念非常简单,在进行中序遍历时,一个节点的紧邻前一个节点就是前驱节点,后一个节点就是后继节点。中序遍历总是先输出小数再输出大数,所以是以从小到大的顺序输出的。也就是说一个节点的前驱就是比它小的第一个数,后驱就是比它大的第一个数

在一个二叉树中,对于一个节点来说,谁在它后面输出谁就是后继。
1. 如果该节点有右子树,右子树种最小者(使用tree_minimum操作)是后继。
2. 如果没有右子树, 下一个要输出就是它的祖先节点中的一个,就是向上找,直到找到一个向右拐的叉时停下来,此时第一个向右拐的节点就是它的后继。对于一个树的右子数的最右边节点,一直向上找都不会找到一个向右拐的结点,所以就不存在后继。其实这个节点是最后一个输出的,没有人是它的后继。同理,最小的那个节点同样没有前驱。

对于一个节点,谁在它前面输出,谁就是它的前驱,所以第一个输出的节点没有前驱。
1. 如果节点有左子树,则左子树中最大的那个就是这个节点的前驱。
2. 如果没有左子树,下一个要输出的就是它的祖先节点中的一个,向上找,找到一个向左拐的结点,就是它的前驱。

算法代码如下:

找到一个节点的前驱:

Node *tree_precessor(Node *node){    if(node->left)        return tree_maximum(node->left);    Node *p = node->p;    while(p && p->left == node)    {        node = p;        p = node->p;    }    return p;}

找到一个节点的后继:

Node *tree_successor(Node *node){    if(node->right)        return tree_minimum(node);    Node *p = node->p;    while(p && p->right == node)    {        node = p;        p = node->p;    }    return p;}

树的插入

要把一个节点插入到树中,先要找到它所在的位置,这个位置满足儿二叉查找树的性质。
1. 找到它的父节点位置
2. 根据key与父节点key的关系,选择插入

代码如下:

Node *tree_insert(Node* tree, Node *node){    Node *x = node;    Node *p = NULL;    while(x)    {        p = x;        if(node->key > p->key)            x = p->right;        else            x = p->left;    }    node->right = node->left = NULL;    node->p = p;    if(!p)    {        return node;    }    else    {        if(p->key > node->key)            p->left = node;        else            p->right = node;    }    return tree;}

删除

节点的删除稍微要分情况一下:
1. 当要删除的结点没有子树时,直接给它父节点的响应指向赋值NULL即可。
2. 当要删除的结点只有一个子树时, 把这个子树链接到父节点上即可。
3. 当要删除的结点同时有左右子树时,把它的后继节点替换到要删除节点,然后删除它的后继节点。

所以应该按照下面的思路执行删除:
1. 找到要删除的节点:要删除节点 或 它的后继节点。
2. 删除该节点。
3. 如果删除的结点不是原来要删除节点,则把删除的结点的key替换到要删除节点那里。

代码如下:

Node* tree_delete(Node* tree, Node* node){    Node *d = NULL;    if(node->left == NULL || node->right == NULL)        d = node;    else        d = tree_successor(node);    Node *n == NULL;    if(d->left == NULL)        n = d->right;    else        n = d->left;    if( n != NULL)        n->p = d->p;    if(d->p == NULL)        return n;    else if(d->p->left == d)        d->p->left = n;    else        d->p->right = n;    if(d != node)        node->key = d->key;    return tree;}

到这里二叉查找树的基本操作就介绍完了。

二叉查找树的缺点

二叉查找树在查找效率上比较高,但是如果插入的元素按递增或递减的顺序插入,那么整棵树就只有一条腿,插入多少就有多高,这样下来查找的效率还不如链表来的快,也就是说这棵树没有自平衡的能力,下一节的 红黑树 很好的解决了这个问题。

最后,麻烦csdn别再删除我写的博客了,至少删的话给个原因再删啊,弄的莫名其妙的。

0 0
原创粉丝点击