数据结构之二叉搜索树

12 篇文章 0 订阅
11 篇文章 0 订阅

         一颗二叉搜索树是以一颗二叉树组织的,这样一颗可以使用一个链表数据结构来表示,其中每个节点都是一个对象,除了key之外,每个节点还包含属性left、right和p,它们分别指向节点的左孩子、右孩子和双亲。如果某个孩子结点和父节点不存在,则相应属性的值为NULL,根节点是树中唯一一个父指针为NULL的节点。

                                      6

                                    /    \

                                  5      7

                                /   \       \

                              2    5      8

        二叉搜索树。对于任何节点x,其左子树中的关键字最大不超过x.key,其有子树中的关键字最小不低于x.key。大部分的搜索树操作的最坏运行时间与树的高度成正比。

      二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:

              设x二叉搜索树中的一个节点。如果y是x左子树中的一个节点,那么y.key <= x.key。如果y是x右子树中的一个节点,那么y.key >= x.key 。

       接下来我们实现二叉搜索树的创建、销毁(递归、非递归)、遍历(先序、中序、后序)、得到最大最小值、插入和删除。

       首先我们来看二叉搜索树的对于接口的声明和节点类型的定义:

        binaryserach_tree.h:

#ifndef _SERACH_TREE_
#define _SERACH_TREE_

#include "tools.h"
#define N (10)
typedef struct Tree_node{
    int                      data;    //数据区域
    struct Tree_node *left_child ;    //左孩子
    struct Tree_node *right_child;    //右孩子
    struct Tree_node           *p;    //双亲
}Tree_node;

typedef Tree_node *Bin_tree;    //二叉搜索树节点指针

//二叉搜索树的接口

//二叉搜索树的创建和销毁
Bin_tree creat_tree(int a[],int n);    //创建二叉搜索树
void destroy_tree(Bin_tree *root);    //二叉搜索树的销毁
void destroy_tree_nr(Bin_tree *root);    //二叉搜索树的非递归销毁

//二叉搜索树的遍历
void pre_order_print(Bin_tree root);    //先序遍历
void mid_order_print(Bin_tree root);    //中序遍历
void last_order_print(Bin_tree root);    //后序遍历
void pre_order_print_nr(Bin_tree root);    //先序遍历(非递归)
void mid_order_print_nr(Bin_tree root);    //中序遍历(非递归)
void last_order_print_nr(Bin_tree root);    //后序遍历(非递归)
void level_order_print(Bin_tree root);    //层序遍历

//二叉搜索树的查找
Tree_node * Tree_serach(Bin_tree root,int k);    //递归
Tree_node * Tree_serach_nr(Bin_tree root,int k);   //非递归

//得到最大关键字元素和得到最小关键字元素
Tree_node *get_tree_minnum(Bin_tree root);
Tree_node *get_tree_maxnum(Bin_tree root);

//返回节点的后继(右孩子)
Tree_node *tree_successor(Bin_tree root);

//插入和删除
void tree_insert(Bin_tree root,Tree_node *value);
Bin_tree tree_delete(Bin_tree root,Tree_node *value);

#endif


本次的节点类型的声明和以往的声明有一点不同,就是在Tree_node的结构体中加入了双亲节点,使得我们的后序操作变得方便。

     (a)二叉搜索树的创建:首先根结点单独创建,对于剩余节点遍历数组剩下的元素,创建节点,寻找节点的双亲,其创建的代码如下:


Bin_tree creat_tree(int a[],int n)    //创建二叉搜索树
{
    Tree_node *root = NULL;
    Tree_node *p_node = NULL;
    Tree_node *c = NULL;
    Tree_node *pa = NULL;
    int i = 0;
    if(a == NULL || n < 0){
        return NULL;
    }
    root = (Tree_node *)Malloc(sizeof(Tree_node));
    root ->data = a[0];
    root->left_child = NULL;
    root ->right_child = NULL;
    root ->p = NULL;
    for(i = 1; i < n; ++i){
        p_node = (Tree_node *)Malloc(sizeof(Tree_node));
        p_node ->data = a[i];
        p_node ->left_child = NULL;
        p_node ->right_child = NULL;
        c = root;
        while(c){
            pa = c;
            if(c ->data > p_node ->data){
                c = c ->left_child;
            }else{
                c = c ->right_child;
            }            
        }
        if(pa ->data > p_node ->data){
            pa ->left_child = p_node;
        }else{
            pa ->right_child = p_node;
        }
        p_node ->p = pa;
    }
    return root;
}

其中p_node用于创建新的节点,pa 和 c联动进行寻找p_node插入的位置,最终pa为p_node的双亲,然后比较pa数据域的值和p_node数据域的值,并根据大小将p_node插入到pa的左子女或者右子女。

     看完创建过程,接下来我们来看二叉搜索树的销毁。

      (b)二叉搜索树的销毁:本文章给大家介绍两种方式:递归销毁和非递归销毁。递归销毁的方式比较简单,非递归的销毁方式需要借助于队列,队列的实现在之前的二叉树已经详细的说明,今天则不重点说明,下面我们具体来看实现:

static void destroy(Bin_tree root)
{
    if(root){
        destroy(root ->left_child);
        destroy(root ->right_child);
        free(root);
    }
}
void destroy_tree(Bin_tree *root)    //二叉搜索树的销毁
{
    if(root == NULL || *root == NULL){
        return;
    }
    destroy(*root);
    *root = NULL;
}
void destroy_tree_nr(Bin_tree *root)    //二叉搜索树的非递归销毁
{
    Queue *queue = NULL;
    Tree_node *node = NULL;
    if(root == NULL || *root == NULL){
        return ;
    }
    queue = init_queue();
    in(queue,*root);
    *root = NULL;
    while(!is_queue_empty(queue)){
        get_queue_front(queue,(void **)&node);
        out(queue);
        
        if(node ->left_child){
            in(queue,node ->left_child);
        }
        if(node ->right_child){
            in(queue,node->right_child);
        }
        free(node);
    }
    destory_queue(&queue);
}
     创建和销毁都和大家介绍完了,我们通过查看内存是否泄露来判断程序的对错:

 对于非递归销毁的检测:


对于非递归的检测:


(c)二叉搜索树的遍历:对于遍历比较特殊的是,对于二叉搜索树的中序遍历即可得到升序序列,二叉搜索树的遍历和二叉树遍历方式一样,分为:先序、中序、后序、层序。下面我们来看具体的实现(包括递归、非递归):

//二叉搜索树的遍历
void pre_order_print(Bin_tree root)    //先序遍历
{
    if(root){
        printf("%5d",root ->data);
        pre_order_print(root ->left_child);
        pre_order_print(root ->right_child);
    }
}
void mid_order_print(Bin_tree root)    //中序遍历
{
    if(root){
        mid_order_print(root ->left_child);
        printf("%5d",root ->data);
        mid_order_print(root ->right_child);
    }

}
void last_order_print(Bin_tree root)    //后序遍历
{
    if(root){
        last_order_print(root ->left_child);
        last_order_print(root ->right_child);
        printf("%5d",root ->data);
    }

}
void level_order_print(Bin_tree root)    //层序遍历
{
    Queue *queue = NULL;
    Tree_node *node = NULL;
    if(root == NULL){
        return ;
    }
    queue = init_queue();
    in(queue,root);
    while(!is_queue_empty(queue)){
        get_queue_front(queue,(void **)&node);
        out(queue);
        printf("%5d",node ->data);
        if(node ->left_child){
            in(queue,node ->left_child);
        }
        if(node ->right_child){
            in(queue,node ->right_child);
        }
    }
    destory_queue(&queue);
}
void pre_order_print_nr(Bin_tree root)    //先序遍历非递归
{
    Stack *stack = NULL;
    Tree_node *node = NULL;
    if(root == NULL){
        return;
    }
    stack = init_stack();
    push(stack,root);
    while(!is_stack_empty(stack)){
        get_top(stack,(void**)&node);
        pop(stack);
        printf("%5d",node ->data);
        if(node ->right_child){
            push(stack,node ->right_child);
        }
        if(node ->left_child){
            push(stack,node ->left_child);
        }
    }
    destroy_stack(&stack);
}
void mid_order_print_nr(Bin_tree root)    //中序遍历非递归
{
    Stack * stack = NULL;
    Tree_node *node = NULL;
    if(root == NULL){
       return;
    }
    stack = init_stack();
    node = root;
    while(!is_stack_empty(stack) || node != NULL){
        while(node != NULL){
            push(stack,node);
            node  = node ->left_child;
        }
        get_top(stack,(void **)&node);
        printf("%5d",node ->data);
        pop(stack);
        node = node ->right_child;
    }
    destroy_stack(&stack);
}
void last_order_print_nr(Bin_tree root)    //后序遍历非递归
{
    Stack *stack = NULL;
    Tree_node *node = NULL;
    Tree_node *prev = NULL;
    if(root == NULL){
        return ;
    }
    stack = init_stack();
    node = root;
    push(stack,node);
    while(!is_stack_empty(stack)){
        get_top(stack,(void **)&node);
        if((node ->left_child == NULL && node ->right_child == NULL) 
                     || (prev != NULL && (node ->left_child == prev
                                    ||node ->right_child == prev))){
            printf("%5d",node ->data);
            pop(stack);
            prev = node;
        }else{
            if(node ->right_child){
                push(stack,node ->right_child);
            }
            if(node ->left_child){
                push(stack,node ->left_child);
            }
        }
    }
    destroy_stack(&stack);
}

对于遍历的检测:



(d)二叉搜索树的查找:对于二叉搜索树的查找可以使用两种方式:递归和非递归,对于二叉搜索树的查找的时间复杂度为o(h),h为树的高度,下面是查找的代码:

//二叉搜索树的查找
Tree_node *Tree_serach(Bin_tree root,int k)    //递归
{
    if(root == NULL || root ->data == k){
        return root;
    }
    if(k < root ->data){
        return Tree_serach(root ->left_child,k);
    }else{
        return Tree_serach(root ->right_child,k);
    }
}
Tree_node * Tree_serach_nr(Bin_tree root,int k)  //非递归
{
    Tree_node *node = NULL;
    node = root;
    while(node != NULL && k!= node ->data){
        if(k < root ->data){
            node = node ->left_child;
        }else{
            node = node ->right_child;
        }
    }
    return node;
}
(d)得到最大和最小的值:由于二叉搜索树的性质,我们可以得出,二叉搜索树的最大值位于右子树的最右边的最后一个叶子,最小值位于左子树的最左边的最后一个叶子,其代码如下:

//得到最大关键字元素和得到最小关键字元素
Tree_node *get_tree_minnum(Bin_tree root)
{
    Tree_node *node = NULL;
    node = root;
    while(node && node ->left_child){
        node = node ->left_child;
    }
    return node;
}
Tree_node *get_tree_maxnum(Bin_tree root)
{
    Tree_node *node = NULL;
    node = root;
    while(node && node ->right_child){
        node = node ->right_child;
    }
    return node;
}
(e)得到前驱、后继节点:给定一颗二叉搜索树中的一个节点,有时候需要按中序遍历的次序查找它的后继,如果所有的关键字互不相同,则一个节点x的后继是大于x.key的最小关键字的节点,其代码的实现如下:
//返回节点的后继、前驱
Tree_node *tree_successor(Bin_tree r)
{
    //               15
    //             /   \
    //            6     18
    //           / \    / \
    //          3   7  17  20
    //         / \   \
    //        2   4   13
    //                /
    //                9
    //   给定一颗二叉树中的节点,有时则需要按中序遍历的次序查找它的后继
    //   如果所有的关键字都不相同,则一个节点x的后继是大于x.key的最小关键字
    //   的节点
    //   因此可分为两种情况:
    //   第一种:节点x 的右子树非空,那么x的后继恰是x右子树的最左节点
    //   可以通过get_tree_minnum(x.right)的调用找到 例如 关键字为15的节点
    //   的后继是关键字为17的节点  
    //   第二种:如果节点x的右子树为空并有一个后继y,那么y是x的最底层祖先
    //   例如:关键字为13的节点的后继是关键字为15的节点
    Tree_node *parent = NULL;
    Tree_node *node = NULL;
    node = r;
    if(r == NULL){
        return NULL;
    }
    if(r ->right_child){
        return get_tree_minnum(r ->right_child);
    }
    parent = node ->p;
    while(parent && node == parent ->right_child){
        node = parent;
        parent = parent ->p;
    }
    return parent;
}

(f)二叉搜索树的插入:二叉树的插入操作的代码如下:node和parent用于确定新节点插入的位置,parent为新节点的双亲节点,最后再将新节点挂在parent的左子女上或右子女上。

void tree_insert(Bin_tree root,Tree_node *value)
{
    Tree_node *node = NULL;
    Tree_node *parent = NULL;
    if(value == NULL){
        return;
    }
    node = root;
    while(node){
        parent = node;
        if(node ->data < value ->data){
            node = node ->right_child;
        }else {
            node = node ->left_child;
        }
    }
    value ->p = parent;
    if(parent == NULL){
       root = value; 
    }else{  
        if(value ->data > parent ->data){
            parent ->right_child = value;
        }else{
            parent->left_child = value;
        }
    }
}
(g) 二叉搜索树删除:
       从一颗二叉搜索树T中删除一个节点z的整个策略可以分为三种基本情况(如下所述),但是只有一种情况比较棘手。

       1>如果z没有孩子节点,那么只是简单的删除,并修改它的父节点,用NULL作为孩子来替换z

       2>如果在z只有一个孩子,那么将这个孩子提升到树中的z的位置,并修改z的父节点,用z的孩子来替换z;

       3>如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来的右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。这种情况稍显麻烦,因为还与y是否为z的右子树相关。

      从一颗二叉搜索树T中删除一个节点z,这个过程取指向T和z的指针作为输入参数,考虑如图所示的4种情况,它与前面概括出的三种情况有所不同。

        a>

                                      Z

                                   /      \                                           R

                             NULL    R             ------->              /    \

                                         /    \                                                                                                                                                          

                                             (a)

              如图(a)所示 ,z没有左孩子,那么用其右孩子来替换z,这个右孩子可以是NULL,也可以不是。当右孩子为NULL时,此时这种情况归为z没有孩子节点的情形,当右孩子非NULL时,此时这种情况就是z仅有一个孩子节点的情况,该孩子是右孩子;

        b>

                                  Z

                                /    \                  ------>              L        

                              L     NULL                               /   \

                            /   \

                                           (b)

               如图(b)所示,如果z仅有一个孩子为左孩子,那么用其左孩子来替换z;否则z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树并且没有左孩子,则需要将y移出原来的位置进行拼接,并替换树中的z。

        c>

                                  Z

                                /   \

                              L     Y                      ------->                            Y

                                    /   \                                                          /    \

                              NULL  X                                                    L      X

                                          /  \                                                 /   \     /   \

               如图(c)所示,用y替换z,并仅留下y的右孩子。

        d>

                                   Z

                                 /   \                              Z                  Y                                           Y

                               L    R       ------->        /                    /   \        ------>                      /    \

                             /  \    /  \                       L              NULL  R                                   L     R

                                    Y                         /  \                         /  \                                /  \     /  \

                                  /   \                                                    X                                          X

                            NULL X                                                /  \                                         /  \

                                      /  \

              其代码实现如下:

static Bin_tree tree_transplant(Bin_tree root,Bin_tree u,Bin_tree v)
{
    if(u ->p == NULL){
        root = v;
    }else if(u == u ->p ->left_child){
        u ->p ->left_child = v;
    }else{
        u ->p ->right_child = v;
    }
    if(v != NULL){
        v ->p = u ->p;
    }
    return root;
}
Bin_tree tree_delete(Bin_tree root,Tree_node *value)
{
    Tree_node *p_node = NULL;
    if(root == NULL || value == NULL){
        return;
    }    
    if(value ->left_child == NULL){
        root = tree_transplant(root,value,value ->right_child);
    }else if(value ->right_child == NULL){
        root = tree_transplant(root,value,value ->left_child);
    }else {
        p_node = get_tree_minnum(value ->right_child);
        if(p_node ->p != value){
            root = tree_transplant(root,p_node,p_node ->right_child);
            p_node ->right_child = value ->right_child;
            p_node ->right_child ->p = p_node; 
        }
        root = tree_transplant(root,value,p_node);
        p_node ->left_child = value ->left_child;
        p_node ->left_child ->p = p_node;
    }
    return root;
}
接下来我们看测试程序的书写:
#include <stdio.h>
#include <stdlib.h>
#include "binaryserach_tree.h"

int main(int argc,char **argv)
{
    int a[N] = {15,6,18,3,7,17,20,2,4,13};
    Bin_tree root = NULL;
    Tree_node *p_node = NULL;
    Tree_node *node = NULL;
    Tree_node *node1 = NULL;
    root = creat_tree(a,N);
    #if 0 
    printf("level_order:\n");
    level_order_print(root); 
    printf("\n");

    printf("pre_order:\n");
    pre_order_print(root);
    printf("\n");

    printf("mid_order:\n");
    mid_order_print(root);
    printf("\n");

    printf("last_order:\n");
    last_order_print(root);
    printf("\n");

    printf("pre_order_nr:\n");
    pre_order_print_nr(root);
    printf("\n");

    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");

    printf("last_order_nr:\n");
    last_order_print_nr(root);
    printf("\n");
    #endif
    #if 1
    p_node = Tree_serach(root,6);
    if(p_node){
        printf("%d is found !\n",p_node ->data);
    }else{
        printf(" is not found !\n");
    }

    p_node = Tree_serach(root,18);
    if(p_node){
        printf("%d is found !\n",p_node ->data);
    }else{
        printf(" is not found !\n");
    }

    p_node = Tree_serach_nr(root,9);
    if(p_node){
        printf("%d is found !\n",p_node ->data);
    }else{
        printf(" is not found !\n");
    }
    #endif
    #if 0
    p_node = get_tree_minnum(root);
    if(p_node){
        printf("minnum :%d\n",p_node ->data);
    }

    p_node = get_tree_maxnum(root);
    if(p_node){
        printf("maxnum :%d\n",p_node ->data);
    }

    p_node = tree_successor(root);
    if(p_node){
        printf("root:%d\n",p_node ->data);

    }else{
        printf("root:not found!\n");
    }

    p_node = tree_successor(root ->right_child ->right_child);
    if(p_node){
        printf("root ->right_child ->right_child:%d\n",p_node ->data);
    }else{
        printf("root ->right_child ->right_child:not found!\n");
    }
    
    p_node = tree_successor(root ->left_child ->left_child);
    if(p_node){
        printf("root ->left_child ->left_child:%d\n",p_node ->data);
    }else{
        printf("root ->left_child->left_child:not found !\n");
    }

    p_node = tree_successor(root ->right_child ->left_child);
    if(p_node){
        printf("root ->right_child ->left_child:%d\n",p_node ->data);
    }else{
        printf("root ->right_child->left_child:not found !\n");
    }

    p_node = tree_successor(root ->left_child ->left_child ->right_child);
    if(p_node){
        printf("root ->left_child ->left_child->right_child:%d\n",p_node ->data);
    }else{
        printf("root ->left_child ->left_child->right_child:not found !\n");
    }

    //二叉搜索树的插入:
    node = (Tree_node *)Malloc(sizeof(Tree_node));
    node ->data = 10;
    node ->left_child = NULL;
    node ->right_child = NULL;
    node ->p = NULL;
    tree_insert(root,node);

    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");

    node1 = (Tree_node *)Malloc(sizeof(Tree_node));
    node1 ->data = 19;
    node1 ->left_child = NULL;
    node1 ->right_child = NULL;
    node1 ->p = NULL;
    tree_insert(root,node1);

    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");


    p_node = root ->right_child;
    root = tree_delete(root,p_node);
    printf("delete root ->right_child:%d\n",p_node ->data);
    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");

    root = tree_delete(root,node1);
    printf("delete node1:%d\n",node1 ->data);
    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");

    root = tree_delete(root,node);
    printf("delete node:%d\n",node->data);
    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");

    p_node = root;
    root = tree_delete(root,p_node);
    printf("delete root:%d\n",p_node->data);
    printf("mid_order_nr:\n");
    mid_order_print_nr(root);
    printf("\n");
    #endif
    //destroy_tree(&root);
    destroy_tree_nr(&root);
    return 0;
}
我们先来看其测试结果:


为了方便大家理解程序,我跟大家大致画出最初创建出的二叉树:
                                                                       15

                                                                   /         \

                                                               6              18

                                                           /      \          /       \

                                                         3       7       17      20

                                                       /    \        \

                                                     2     4       13

把节点10和19插入到二叉搜索树中后:

                                                                      15

                                                                   /         \

                                                               6              18

                                                           /      \          /       \

                                                         3         7       17      20

                                                       /    \           \        \

                                                     2     4          13    19

                                                                         /

                                                                       10


至此二叉搜索树的实现已完成,此后会和大家一起讨论红黑树、B树、AVL树等等,敬请期待!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值