linux内核二叉查找树的应用

参考资料:

1. **百科
2. man-page地址 
http://man7.org/linux/man-pages/man3/tsearch.3.html 3. 下面这个连接也是介绍二叉树搜索的,有空可以参考一下。
http://www.gnu.org/software/libc/manual/html_node/Tree-Search-Function.html

什么叫二叉查找树
二叉查找树(Binary Search Tree),(又:
二叉搜索树
,二叉排序树)它或者是一棵空树,或者是具有下列性质的
二叉树
: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为
二叉排序树

《linux内核二叉查找树的应用》

man-page 部分翻译

SYNOPSIS

#include <search.h>
void *tsearch(const void *key, void **rootp,int (*compar)(const void *, const void *));
void *tfind(const void *key, void *const *rootp,int (*compar)(const void *, const void *));
void *tdelete(const void *key, void **rootp,int (*compar)(const void *, const void *));
void twalk(const void *root, void (*action)(const void *nodep,const VISIT which,const int depth));
#define _GNU_SOURCE         /* See feature_test_macros(7) */
       #include <search.h>
void tdestroy(void *root, void (*free_node)(void *nodep));

DESCRIPTION

        tsearch(), tfind(), twalk(), and tdelete() manage a binary tree.
       They are generalized from Knuth (6.2.2) Algorithm T.  The first field
       in each node of the tree is a pointer to the corresponding data item.
树的每个节点的第一个域都是指针,该指针指向相对应的数据项。
       (The calling program must store the actual data.)  compar points to a
(这些函数的调用者一定要存储数据项的实际内存)
       comparison routine, which takes pointers to two items.  It should
compar参数指向一个函数指针,该函数指针有两个参数,分别指向两个数据项。

return an integer which is negative, zero, or positive, depending on

该函数指针应当返回一个整型数如负数、0、正数,而这具体是多少则完全取决于它的第一个数据项是小于、等于、大于第二个数据项。
       whether the first item is less than, equal to, or greater than the
       second.

       tsearch() searches the tree for an item.  key points to the item to
tsearch()用于在一颗指定的树中搜索一个数据项,而参数key指针就指向这个被搜索的数据项,
       be searched for.  rootp points to a variable which points to the root
rootp指针指向这棵树的根部。
       of the tree.  If the tree is empty, then the variable that rootp
如果这棵树是空的,那么rootp指针所指变量应当被设置为NULL。
       points to should be set to NULL.  If the item is found in the tree,
如果这个待搜索的数据项在树中被找到了,那么tsearch()将会返回一个指向该元素的指针。
       then tsearch() returns a pointer to it.  If it is not found, then
如果没有找到,tsearch()将会把它添加到树中去,然后返回一个指向这个新添加的数据项的指针。
       tsearch() adds it, and returns a pointer to the newly added item.

       tfind() is like tsearch(), except that if the item is not found, then
       tfind() returns NULL.

       tdelete() deletes an item from the tree.  Its arguments are the same
       as for tsearch().

       twalk() performs depth-first, left-to-right traversal of a binary
twalk()按照深度优先、从左到右的顺序编译一颗二叉树(其实就是中序遍历二叉树).
       tree.  root points to the starting node for the traversal.  If that
       node is not the root, then only part of the tree will be visited.
       twalk() calls the user function action each time a node is visited
       (that is, three times for an internal node, and once for a leaf).
       action, in turn, takes three arguments.  The first argument is a
       pointer to the node being visited.  The structure of the node is
       unspecified, but it is possible to cast the pointer to a pointer-to-
       pointer-to-element in order to access the element stored within the
       node.  The application must not modify the structure pointed to by
       this argument.  The second argument is an integer which takes one of
       the values preorder, postorder, or endorder depending on whether this
       is the first, second, or third visit to the internal node, or the
       value leaf if this is the single visit to a leaf node.  (These
       symbols are defined in <search.h>.)  The third argument is the depth
       of the node; the root node has depth zero.

       (More commonly, preorder, postorder, and endorder are known as
       preorder, inorder, and postorder: before visiting the children, after
       the first and before the second, and after visiting the children.
       Thus, the choice of name postorder is rather confusing.)

       tdestroy() removes the whole tree pointed to by root, freeing all
       resources allocated by the tsearch() function.  For the data in each
       tree node the function free_node is called.  The pointer to the data
       is passed as the argument to the function.  If no such work is
       necessary, free_node must point to a function doing nothing.

代码示例

// 这里的代码是以man-page给的示例代码为参考的。
#define _GNU_SOURCE     /* Expose declaration of tdestroy() */ 
#include <search.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

static void *root = NULL;

static void * xmalloc(unsigned n)
{
        void *p;
        p = malloc(n);
        if (p)
                return p;
        fprintf(stderr, "insufficient memory\n");
        exit(EXIT_FAILURE);
}

static int compare(const void *pa, const void *pb)
{
        printf("compare, *(int *)pa = %d, *(int *)pb = %d\n", *(int *)pa, *(int *)pb);
        if (*(int *) pa < *(int *) pb)
                return -1;
        if (*(int *) pa > *(int *) pb)
                return 1;
        return 0;
}

static void action(const void *nodep, const VISIT which, const int depth)
{
        int *datap;
        //printf("which = %d, preorder = %d, postorder = %d, endorder = %d, leaf = %d\n", which, preorder, postorder, endorder, leaf);
        switch (which)
        {
                case preorder:
                        //datap = *(int **) nodep;
                        //printf("%6d\n", *datap);
                        break;
                case postorder:
                        datap = *(int **) nodep;
                        printf("%6d\n", *datap);
                        break;
                case endorder:
                        //datap = *(int **) nodep;
                        //printf("%6d\n", *datap);
                        break;
                case leaf:
                        datap = *(int **) nodep;
                        printf("%6d\n", *datap);
                        break;
        }
}

// 本来函数fun()中的所有内容是直接放在main函数中的,而这里将其单独拿出来就是为了说明tsearch的第一个参数的内存问题。
void fun(void)
{
        int i, *ptr;
        void *val;
        //srand(time(NULL)); // 官方文档上的例子是采用随机数,但是由于每次执行都会产生不同的随机数,这样不便于观察,所以这里采用下面固定的数组。
        int array[12] = {2, 2, 1, 12, 9, 4, 8, 10, 3, 7, 5, 11};
        for (i = 0; i < 12; i++)
        {
                printf("~~~~~~~~~~~~ i = %d ~~~~~~~~~~~~~~\n", i);
                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                ptr = xmalloc(sizeof(int));
                //*ptr = rand() & 0xff; // 取随机数的低8位。
                *ptr = array[i];
                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                // ptr 所指内存最好是实际存在的,而不是临时的(如栈空间),
                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                // ptr = array + i;
                // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// root 初始状态应被设置成 NULL,它将始终指向一颗二叉树的根部。
// tsearch() 会把ptr所指内存的值与root树中各个节点(由于root树是一颗二叉排序树,所以实际操作并不是每个节点都需要比较的)的值按照compare()方法作比较,如果compare()返回0,则将ptr丢弃(即不将其存入root树);如果compare()返回1,则将其存入root树的右子树;如果compare()返回-1,则将其存入root树的左子树。
// compare()方法通常是按照strcmp(char *s1, char *s2)的思想来操作,即s1小于s2时,返回-1;s1等于s2时,返回0;s1大于s2时,返回1。
// 但是第一次调用tsearch() 时,由于root=NULL,tsearch() 会直接将ptr所指内存地址添加到root树中去,而不会去调用compare()函数了。
// tsearch() 是将ptr所指内存地址赋给root树子节点,而不是将ptr所指内存中的值拷贝到root树的子节点。单独写一个fun()函数就是为了验证这一点。                
                val = tsearch((void *) ptr, &root, compare);

                int *pint = *(int **)val;
                printf("ptr = %p, *ptr = %d, val = %p, pint = %p, *pint = %d, root = %p\n", ptr, *ptr, val, pint, *pint, root);
                if (val == NULL) // (me) tsearch() 中申请内存失败
                {
                        printf("tsearch return NULL, will exit\n");
                        exit(EXIT_FAILURE);
                }
                else if (pint != ptr) // ptr所指内存的值在root树中已经存在了,即pint所内存。
                {
                        printf("pint != ptr, will free(ptr)\n");
                        free(ptr);
                }
        }
}

int main(void)
{
        fun(); // fun()函数的内容本来是被直接写在main函数里的,而这里将其写到fun()函数里,是为了验证tsearch()的第一个参数最好是指向一个实际存在的内存。
        twalk(root, action);
        int key = 10;
        void *p = tfind((void *)&key, &root, compare);
        if(p)
        {
                int *pint = *(int **)p;
                printf("tfind key >>> p = %p, pint = %p, *pint = %d\n\n", p, pint, *pint);
        }

        p = tdelete((void *)&key, &root, compare);
        if(p)
        {
                int *pint = *(int **)p;
                printf("tdelete key, key's father is >>> p = %p, pint = %p, *pint = %d\n\n", p, pint, *pint);
        }
        twalk(root, action);

        tdestroy(root, free);
        exit(EXIT_SUCCESS);
}

编译/执行 $ gcc tree.c 

$ ./a.out 

~~~~~~~~~~~~ i = 0 ~~~~~~~~~~~~~~ ptr = 0x90e010, *ptr = 2, val = 0x90e030, pint = 0x90e010, *pint = 2, root = 0x90e030 ~~~~~~~~~~~~ i = 1 ~~~~~~~~~~~~~~ compare, *(int *)pa = 2, *(int *)pb = 2 ptr = 0x90e060, *ptr = 2, val = 0x90e030, pint = 0x90e010, *pint = 2, root = 0x90e030 pint != ptr, will free(ptr) ~~~~~~~~~~~~ i = 2 ~~~~~~~~~~~~~~ compare, *(int *)pa = 1, *(int *)pb = 2 ptr = 0x90e060, *ptr = 1, val = 0x90e080, pint = 0x90e060, *pint = 1, root = 0x90e030 ~~~~~~~~~~~~ i = 3 ~~~~~~~~~~~~~~ compare, *(int *)pa = 12, *(int *)pb = 2 ptr = 0x90e0b0, *ptr = 12, val = 0x90e0d0, pint = 0x90e0b0, *pint = 12, root = 0x90e030 ~~~~~~~~~~~~ i = 4 ~~~~~~~~~~~~~~ compare, *(int *)pa = 9, *(int *)pb = 2 compare, *(int *)pa = 9, *(int *)pb = 12 ptr = 0x90e100, *ptr = 9, val = 0x90e120, pint = 0x90e100, *pint = 9, root = 0x90e030 ~~~~~~~~~~~~ i = 5 ~~~~~~~~~~~~~~ compare, *(int *)pa = 4, *(int *)pb = 2 compare, *(int *)pa = 4, *(int *)pb = 12 compare, *(int *)pa = 4, *(int *)pb = 9 ptr = 0x90e150, *ptr = 4, val = 0x90e170, pint = 0x90e150, *pint = 4, root = 0x90e030 ~~~~~~~~~~~~ i = 6 ~~~~~~~~~~~~~~ compare, *(int *)pa = 8, *(int *)pb = 2 compare, *(int *)pa = 8, *(int *)pb = 9 compare, *(int *)pa = 8, *(int *)pb = 4 ptr = 0x90e1a0, *ptr = 8, val = 0x90e1c0, pint = 0x90e1a0, *pint = 8, root = 0x90e030 ~~~~~~~~~~~~ i = 7 ~~~~~~~~~~~~~~ compare, *(int *)pa = 10, *(int *)pb = 2 compare, *(int *)pa = 10, *(int *)pb = 9 compare, *(int *)pa = 10, *(int *)pb = 12 ptr = 0x90e1f0, *ptr = 10, val = 0x90e210, pint = 0x90e1f0, *pint = 10, root = 0x90e030 ~~~~~~~~~~~~ i = 8 ~~~~~~~~~~~~~~ compare, *(int *)pa = 3, *(int *)pb = 2 compare, *(int *)pa = 3, *(int *)pb = 9 compare, *(int *)pa = 3, *(int *)pb = 4 ptr = 0x90e240, *ptr = 3, val = 0x90e260, pint = 0x90e240, *pint = 3, root = 0x90e030 ~~~~~~~~~~~~ i = 9 ~~~~~~~~~~~~~~ compare, *(int *)pa = 7, *(int *)pb = 2 compare, *(int *)pa = 7, *(int *)pb = 9 compare, *(int *)pa = 7, *(int *)pb = 4 compare, *(int *)pa = 7, *(int *)pb = 9 compare, *(int *)pa = 7, *(int *)pb = 8 ptr = 0x90e290, *ptr = 7, val = 0x90e2b0, pint = 0x90e290, *pint = 7, root = 0x90e170 ~~~~~~~~~~~~ i = 10 ~~~~~~~~~~~~~~ compare, *(int *)pa = 5, *(int *)pb = 4 compare, *(int *)pa = 5, *(int *)pb = 9 compare, *(int *)pa = 5, *(int *)pb = 8 compare, *(int *)pa = 5, *(int *)pb = 7 ptr = 0x90e2e0, *ptr = 5, val = 0x90e300, pint = 0x90e2e0, *pint = 5, root = 0x90e170 ~~~~~~~~~~~~ i = 11 ~~~~~~~~~~~~~~ compare, *(int *)pa = 11, *(int *)pb = 4 compare, *(int *)pa = 11, *(int *)pb = 9 compare, *(int *)pa = 11, *(int *)pb = 12 compare, *(int *)pa = 11, *(int *)pb = 10 ptr = 0x90e330, *ptr = 11, val = 0x90e350, pint = 0x90e330, *pint = 11, root = 0x90e170      1      2      3      4      5      7      8      9     10     11     12 compare, *(int *)pa = 10, *(int *)pb = 4 compare, *(int *)pa = 10, *(int *)pb = 9 compare, *(int *)pa = 10, *(int *)pb = 11 compare, *(int *)pa = 10, *(int *)pb = 10 tfind key >>> p = 0x1036230, pint = 0x1036210, *pint = 10

compare, *(int *)pa = 10, *(int *)pb = 4 compare, *(int *)pa = 10, *(int *)pb = 9 compare, *(int *)pa = 10, *(int *)pb = 11 compare, *(int *)pa = 10, *(int *)pb = 10 tdelete key, key’s father is >>> p = 0x2311350, pint = 0x2311330, *pint = 11

     1      2      3      4      5      7      8      9     11     12

思考1: 其他函数不变,只将compare()函数作如下修改(红色字体为修改部分),执行结果:twalk()遍历的结果是从大到小输出的。 static int compare(const void *pa, const void *pb) {         if (*(int *) pa < *(int *) pb)
                return 
1;         if (*(int *) pa > *(int *) pb)
                return 
-1;         return 0; }

思考2: 其他函数不变,只将compare()函数作如下修改(红色字体为修改部分),执行结果:twalk()遍历的结果是和root的第一个元素相同的元素。

static int compare(const void *pa, const void *pb) {         if (*(int *) pa < *(int *) pb)
                return 0;         if (*(int *) pa > *(int *) pb)
                return 0;
        return 
1
// 此时返回1或者-1都是一样的。 } 分析:第一次调用 tsearch() 时,tsearch() 会将array的第一个元素2添加到root树,此时不会调用compare()函数;以后再执行 tsearch() 时,就会调用compare()函数了,由于此时只有当待搜索元素和root树中的元素相等时compare()才会返回非0值,所以只有和root树中相等的值才会被添加到root树中。

思考3: 如果fun()函数中为ptr指向的不是malloc出来的内存,而是局部变量array所指内存,那么twalk()的输出内容就会出错,而tdestroy(root, free)更是会导致进程崩溃。但是如果将fun()函数的内容抽出来直接放到main函数中,的twalk()便可正确输出了,但此时tdestroy(root, free)还是会崩溃,因为ptr所指内存是位于栈空间的局部变量,而不是malloc所申请的。

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/yahstudio/article/details/49902475
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞