数据结构学习之二叉树(实践篇)

注:本文的主要目的是为了记录自己的学习过程,也方便与大家做交流。转载请注明来自:

http://blog.csdn.net/ab198604

        上一篇博文主要对树、二叉树的概念及其一些基本特性进行简要的描述,偏向于理论知识。而本文的主要内容是针对二叉树这种数据结构的实现细节进行设计与分析,理论与实践相结合可以加深对系统知识的掌握。二叉树这种数据结构,应用非常广泛,在linux内核中随处可见,因此,如果能够熟练的掌握这项技能,将有助于理解其它系统。

        一、“初识”二叉树

        在代码的实现中,二叉树究竟是什么?请看下面代码:

/* 
 * filename: bitree.h
 * author: zhm
 * date: 2012-01-08
 */

#ifndef BITREE_H
#define BITREE_H

#include <stdlib.h>

/* define a binary tree node */
typedef struct BiTreeNode_
{
    void *data;
    struct BiTreeNode_ *left;    //point to left node.
    struct BiTreeNode_ *right;   //point to right node.
}BiTreeNode;

        这是一段关于二叉树结点的数据结构,一共有3个域,数据域和左右指针域,数据域包含了二叉树每个结点的关键信息,左右指针域分别指向它的左右孩子结点。

/* define a binary tree */
typedef struct BiTree_
{
    int size;           //number of the elements in the tree.
    BiTreeNode *root;   //root node.
    int (*compare)(const void *key1, const void *key2);
    void (*destroy)(void *data);
}BiTree;

        这里定义了一个结构体,这个结构体就是一棵二叉树了。因为它维护了一棵二叉树的重要信息,如,二叉树中结点总数size,根结点的位置root,结点数据信息的比较操作,销毁二叉树的destroy函数等。可以通过这个结构体的root域就可以方便的按深度及广度遍历整个二叉树,寻找到任何一个结点了。

        二、“深入”二叉树

        二叉树究竟是如何建立的?凡事产生均有一个过程,二叉树的建立也有一个过程。它是由不同的结点组成,按照实际情况逐一将这些结点插入从而形成二叉树,当然,也面临着结点的删除操作等,总而言之,它有以下基本操作(接口):

/* public interface */
void bitree_init( BiTree *tree, void (*destroy)(void *data) );
void bitree_destroy(BiTree *tree);
int bitree_ins_left(BiTree *tree, BiTreeNode *node, const void *data);
int bitree_ins_right(BiTree *tree, BiTreeNode *node, const void *data);
void bitree_rem_left(BiTree *tree, BiTreeNode *node);
void bitree_rem_right(BiTree *tree, BiTreeNode *node);
int bitree_merge(BiTree *merge, BiTree *left, BiTree *right, const void *data);

#define bitree_size(tree) ((tree)->size) //获取大小
#define bitree_root(tree) ((tree)->root) //获取根结点
#define bitree_is_eob(node) ((node) == NULL) //判断分支是否结束
#define bitree_is_leaf(node) ((node)->left == NULL && (node)->right == NULL)  //判断是否是叶子结点
#define bitree_data(node) ((node)->data) //获取数据域
#define bitree_left(node) ((node)->left) //获取左结点(左孩子)
#define bitree_right(node) ((node)->right)//获取右结点(右孩子)

#endif

        1 二叉树的初始化(bitree_init):此操作完成后,一棵空的二叉树就建立了,此时它没有任何结点,这是二叉树进行后续操作的前提。

        2 二叉树的销毁(bitree_destroy):此操作用于销毁一棵二叉树

        3 二叉树插入操作(bitree_ins_left):将data中的信息插入到当前node结点的左指针域,成为当前node结点的左孩子。当nodeNULL时,从根结点位置插入。

        4二叉树插入操作(bitree_ins_right):同3,不同的是其插入的是右指针域。

        5 二叉树删除操作(bitree_rem_left):删除以node结点为根的子树的左子树。当node = NULL时,则为删除整棵二叉树

        6二叉树删除操作(bitree_rem_right):同5,不同的是其删除的是右子树。

        7 二叉树的合并(bitree_merge):将两棵二叉树,分别合并成以data域为根的新二叉树,原来这两棵二叉树分别成为新二叉树的左右子树。

        8其它宏定义:代码中已经说明清楚,这里不再累述。

        9二叉树的三种遍历操作:先序遍历、中序遍历和后序遍历。(放在后面说明)

 

        三、实现二叉树
        1、二叉树初始化的实现(bitree_init)
/*
 * filename: bitree.c
 * author: zhm
 * date: 2012-01-08
 */

#include <string.h>
#include <stdlib.h>

#include "bitree.h"

/* bitree_init */
void bitree_init( BiTree *tree, void (*destroy)(void *data) )
{
    /* Initialize the binary tree */
    tree->size = 0;
    tree->root = NULL;
    tree->destroy = destroy;
    return;
}

        完成对维护二叉树结构体的各域值的初始化。

        2、二叉树的销毁操作(bitree_destroy)
/* bitree_destroy */
void bitree_destroy(BiTree *tree)
{
    /* Remove all the nodes from the tree */
    bitree_rem_left(tree, NULL);

    memset(tree, 0, sizeof(BiTree) );
    return;
}

        先删除二叉树的所有结点,然后清空二叉树结构体。

        3、二叉树插入操作(bitree_ins_left及bitree_ins_right)

        先是插入左子树操作:

/* bitree_ins_left */
int bitree_ins_left(BiTree *tree, BiTreeNode *node, const void *data)
{
    BiTreeNode *new_node, **position;
    
    if( node == NULL )
    {
        if( bitree_size(tree) > 0 )
            return -1;

        position = &tree->root;
    }
    else
    {
        if( bitree_left(node) != NULL )
            return -1;
        
        position = &node->left;
    }

    /* Allocate storage for the node */
    new_node = (BiTreeNode *)malloc(sizeof(BiTreeNode));
    if( new_node == NULL )
        return -1;

    /* insert the node into the tree */
    new_node->data = (void *)data;
    new_node->left = NULL;
    new_node->right = NULL;
    
    *position = new_node;

    tree->size++;
    
    return 0;
}

        接着是插入右子树操作:

/* bitree_ins_right */
int bitree_ins_right(BiTree *tree, BiTreeNode *node, const void *data)
{
    BiTreeNode *new_node, **position;

    if( node == NULL )
    {
        if( bitree_size(tree) > 0 )
            return -1;
        
        position = &tree->root;
    }
    else
    {
        if( bitree_right(node) != NULL )
            return -1;

        position = &node->right;
    }

    /* allocate the storage for the node. */
    new_node = (BiTreeNode *)malloc(sizeof(BiTreeNode));
    if( new_node == NULL )
        return -1;

    new_node->data = (void *)data;
    new_node->left = NULL;
    new_node->right = NULL;

    *position = new_node;
    
    tree->size++;
    return 0;
}

        通过代码可以看出,这两个函数的实现几乎一样,我们这里只需要学会其内在思想:

        (1) 找准需要插入的位置:是在根结点位置,当前结点的左指针还是右指针位置。

        (2) 分配新结点,在适当的地方插入结点: *position = new_node完成了这个插入操作

        (3) 更新二叉树size域。

        4、二叉树删除操作(bitree_rem_left及bitre_rem_right)

        先是删除左子树操作:

/* bitree_rem_left */
void bitree_rem_left(BiTree *tree, BiTreeNode *node)
{
    BiTreeNode **position;

    /* Do not allow removal from an empty tree. */
    if( bitree_size(tree) == 0 )
        return;

    if( node == NULL )
    {
        position = &tree->root;
    }
    else
    {
        position = &node->left;
    }

    /* Remove the nodes. */
    if( *position != NULL )
    {
        bitree_rem_left(tree, *position);
        bitree_rem_right(tree, *position);

        if( tree->destroy != NULL )
        {
            tree->destroy((*position)->data);
        }
        free(*position);
        *position = NULL;

        /* adjust the size */
        tree->size--;
    }
    return;
}

        接着是删除右子树操作:

/* bitree_rem_right */
void bitree_rem_right(BiTree *tree, BiTreeNode *node)
{
    BiTreeNode **position;

    if( bitree_size(tree) == 0 )
        return;

    if( node == NULL )
    {
        position = &tree->root;
    }
    else
    {
        position = &node->right;
    }

    /* Remove the nodes */
    if( *position != NULL )
    {
        bitree_rem_left(tree, *position);
        bitree_rem_right(tree, *position);

        if( tree->destroy != NULL )
        {
            tree->destroy((*position)->data);
        }

        free(*position);
        *position = NULL;
        tree->size--;
    }

    return;
}

        同样的,我们需要掌握其实现的思想:

        通过采用递归的思想,后序遍历逐层深入到最底层(深度优先搜索),从下至上逐一删除各个结点,释放被删除结点的数据域空间,更新二叉树size值大小。注意递归退出的条件:

        (1) 树为空时退出

        (2) *Position为空时退出

        可以思考:为何删除操作不能采用前序或中序遍历?

        5、二叉树的合并(bitree_merge)
/* bitree_merge */
int bitree_merge(BiTree *merge, BiTree *left, BiTree *right, const void *data)
{
    /* Initialize the merged tree. */
    bitree_init(merge, left->destroy);

    /* Insert the data for the root node of the merged tree */
    if( bitree_ins_left(merge, NULL, data) != 0 )
    {
        bitree_destroy(merge);
        return -1;
    }

    /* Merge the two binary trees into a single binary tree */
    bitree_root(merge)->left = bitree_root(left);
    bitree_root(merge)->right = bitree_root(right);
    
    /* Adjust the size of the new tree */
    merge->size = merge->size + bitree_size(left) + bitree_size(right);

    /* Do not let the original trees access the merged nodes. */
    left->root = NULL;
    left->size = 0;
    right->root = NULL;
    right->size = 0;

    return 0;
}

        二叉树的合并操作非常简单,有以下几个步骤:

        (1) 初始化新二叉树,并插入data域成为新二叉树的根结点

        (2) 新二叉树的左指针指向左子树的根结点

        (3) 新二叉树的右指针指向右子树的根结点

        (4) 新二叉树结点个数 =左、右子树结点之和+1

        (5) 对原左、右子树结构体相关域的清空操作。

 

        四、遍历二叉树

        遍历二叉树是指按照一定的规律,对二叉树的每个结点访问且仅访问一次的处理过程。这里的“访问”是泛指对结点数据的某种处理操作,如可以是printf打印显示,可以是链表的插入操作,也可以是某些数学运算,等等。

        遍历的目的:通过一次遍历后,可以使树中结点的非线性结构按访问的先后顺序转变为某种线性序列。如:可以按照访问的先后顺序将数据域逐一填入某一数组中,当然,也可以插入某个链表中,然后通过链表的方式对数据域进行访问。

        遍历的次序: DLR先序遍历、LDR中序遍历、LRD后序遍历

        可以看出,先、中、后序的遍历主要根据根结点的访问次序命名,而L左结点总是先于R右结点访问。无论是哪种遍历方式,其核心的思想是总是递归,以中序遍历二叉树为例,说明其算法思想:

        若二叉树非空,则:

        1)中序遍历左子树

        2)访问根结点

        3)中序遍历右子树

 

        (1)先序遍历二叉树
/* preorder */
int preorder(const BiTreeNode *node, List *list)
{
    if( !bitree_is_eob(node) )
    {
    
        if( list_ins_next(list, list_tail(list), bitree_data(node) ) != 0 )
        {
            return -1;
        }
        
        if( !bitree_is_eob( bitree_left(node) ) )
        {
            if( preorder(bitree_left(node), list) != 0 )
                return -1;
        }
        
        if( !bitree_is_eob( bitree_right(node) ) )
        {
            if( preorder(bitree_right(node), list) != 0 )
                return -1;
        }
    }
    return 0;
}
        (2)中序遍历二叉树
/* inorder */
int inorder(const BiTreeNode *node, List *list)
{
    if( !bitree_is_eob(node) )
    {
        if( !bitree_is_eob(bitree_left(node)) )
        {
            if( inorder( bitree_left(node), list ) != 0 )
                return -1;
        }
        
        if( list_ins_next(list, list_tail(list), bitree_data(node)) != 0 )
        {
            return -1;
        }
        
        if( !bitree_is_eob(bitree_right(node)) )
        {
            if( inorder( bitree_right(node), list ) != 0 )
                return -1;
        }
    }
    return 0;
}
        (3)后序遍历二叉树
/* postorder */
int postorder(const BiTreeNode *node, List *list)
{
    if( !bitree_is_eob(node) )
    {
        if( !bitree_is_eob(bitree_left(node)) )
        {
            if( postorder(bitree_left(node), list) != 0 )
                return -1;
        }
        
        if( !bitree_is_eob(bitree_right(node)) )
        {
            if( postorder(bitree_right(node), list) != 0 )
                return -1;
        }
        
        if( list_ins_next(list, list_tail(list), bitree_data(node)) != 0 )
            return -1;
    }

    return 0;
}

          在本例的三种遍历代码中,“访问”的方式是将结点的数据域插入至某一链表结构中。

        五、二叉树简单应用

        对上述所有二叉树的代码如果未进行使用或验证,无疑是一些基本符号,没有任何的意义,所以本节主要对前面二叉树的各个接口进行简单的应用测试,在进入实际的应用之前,有几个函数需要先实现,如下:

/* destroy */
void destroy(void *data)
{
    free(data);
    return;
}

        这是创建销毁函数的代码,在bitree_init()初始化二叉树时需要传递它的函数入口地址。接着是创建二叉树的函数:

/* create_tree */
int create_tree(BiTree *tree, BiTreeNode *node)
{
    
    int ret;
    int *int_ptr = NULL;
    char ch;
    
    scanf("%c", &ch);

    if( ch == '#' )
    {
        return 0;
    }
    int_ptr = (int *)malloc(sizeof(int));
    if( int_ptr == NULL )
        return -1;

    *int_ptr = ch-48;

    if( node == NULL )
    {
        bitree_init(tree, destroy);
        ret = bitree_ins_left(tree, NULL, (void *)int_ptr);
        if( ret != 0 )
        {
            free(int_ptr);
            return -1;
        }
        printf("root is %d\n", *(int *)bitree_data(tree->root));
        create_tree(tree, tree->root);
    }
    else
    {
        //insert the data into left tree 
        ret = bitree_ins_left(tree, node, (void *)int_ptr);
        if( ret != 0 )
        {
            free(int_ptr);
            return -1;
        }
        printf("node: %d  's left node is :%d\n", *(int *)bitree_data(node), *(int *)bitree_data(node->left));
        ret = create_tree(tree, node->left);

        scanf("%c", &ch);

        if( ch == '#')
            return 0;

        int_ptr = (int *)malloc(sizeof(int));
        if( int_ptr == NULL )
            return -1;

        *int_ptr = ch-48;
        // insert the data into right tree.
        ret = bitree_ins_right(tree, node, (void *)int_ptr);
        if( ret != 0 )
        {
            free(int_ptr);
            return -1;
        }
        printf("node: %d  's right node is :%d\n", *(int *)bitree_data(node), *(int *)bitree_data(node->right));
        ret = create_tree(tree, node->right);
    }

    return 0;
}

        它的实现逻辑比较简单,在于采用递归的思想来创建一棵二叉树,并且其递归退出的条件是输入“#”符号,注意:本代码的实现只能插入简单的数字(范围0-9),这对于简单测试来说已经足够了。

        下面是具体的关于二叉树各接口代码的简单测试应用,如下:

  

/* main */
int main(int argc, char **argv)
{
    int ret;
    int *int_ptr;
    BiTree tree1, tree2, tree_merge;
    List list;
    ListElmt *member;
    BiTreeNode *nd;

    /* tree1 as follows :
                1
              /   \
             2     5
            / \   / \
           3  4  6   7
    */
    create_tree(&tree1, NULL); //input "123#4#56#7#"
    printf("\nstep1:tree1 build success\n");

    /* tree2 as follows:      
                0
              /   \
             8     9
            / \   / 
           6   7 3
     */
    int_ptr = NULL;
    create_tree(&tree2, NULL); //input "086#7#93###"
    printf("step2:tree2 build success\n");

    int_ptr = (int *)malloc(sizeof(int));
    if( int_ptr == NULL )
        return -1;
    *int_ptr = 11;
 
    /* after merged as follow( by tree1 and tree2 ) :
                                11
                          /             \
                         1               0
                     /       \         /    \
                    2         5       8      9
                 /    \    /    \   /   \   /  \
                3      4  6      9 6     7 3    NULL
    */
    ret = bitree_merge(&tree_merge, &tree1, &tree2, int_ptr);
    printf("step3: after merged: there are %d number nodes in the tree_merge.\n", bitree_size(&tree_merge));
    

    /* after remove the right tree:
                   11 
                 /   \
                1     NULL
              /   \
             2     5
            / \   / \
           3  4  6   7
     */                   
    printf("\nstep4: remove the right tree in tree_merge.\n");
    bitree_rem_right(&tree_merge,  bitree_root(&tree_merge) );
    printf("after remove the right tree, there are %d number nodes in the tree_merge.\n", bitree_size(&tree_merge));
    
    printf("\nstep5: preorder traverse the tree and insert the nodes into the list\n");
    list_init(&list, destroy);
    ret = preorder( bitree_root(&tree_merge), &list );

    printf("according to the sequence of the preorder traversing the tree:\n");
    for(member = list_head(&list); member != NULL; member = list_next(member) )
    {
       printf("%d ", *(int *)list_data(member)); 
    }
    printf("\n");


    printf("\nsetp6: inorder traverse the tree and insert the nodes into the list\n");
    list_init(&list, destroy);

    ret = inorder( bitree_root(&tree_merge), &list );

    printf("according to the sequence of the inorder traversing the tree:\n");
    for(member = list_head(&list); member != NULL; member = list_next(member) )
    {
       printf("%d ", *(int *)list_data(member)); 
    }
    printf("\n");

    printf("\nsetp7: postorder traverse the tree and insert the nodes into the list\n");
    list_init(&list, destroy);
    
    ret = postorder( bitree_root(&tree_merge), &list );
    printf("according to the sequence of the postorder traversing the tree:\n");
    for(member = list_head(&list); member != NULL; member = list_next(member) )
    {
       printf("%d ", *(int *)list_data(member)); 
    }
    printf("\n");

    printf("\nstep8: delete all the nodes in the tree.\n");
    bitree_rem_left( &tree_merge, NULL );
    printf("there are %d number nodes.\n", bitree_size(&tree_merge) );
    
    bitree_destroy(&tree_merge);

    return 0;
}

        具体的含义不再说明,注释以及printf已经非常详细。代码的编译过程如下:

        程序执行过程如下:

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值