B树

来源:互联网 发布:黑暗之魂1捏脸数据 编辑:程序博客网 时间:2024/06/06 03:07

树是一种被设计成专门存储在磁盘上的平衡查找树。因为磁盘的操作速度要大大慢于随机存取存储器,所以在分析树的性能时,不仅要看动态集合操作花了多少计算时间,还要看执行了多少次磁盘存储操作。 树与红黑树(下一篇介绍)类似,但在降低磁盘I/O 操作次数方面要更好一些。许多数据库系统就使用 树或 树的变形来存储信息,想象一下一棵每个节点包含 1001 个 key 的高度为 的 树能容纳多少数据啊,而在内存中我们只存储了一个节点,在需要的时候再从磁盘中读取所需的节点。

树红黑树比较:

树的节点有很多子女,从几个到几千,而红黑树只有左右两个;一棵含有 个节点的树与红黑树的高度均为 O(lgn),只不过 树的分支较多,因此高度一般要少于红黑树。

树到底是怎样一棵树呢,下面来看定义:

1),每个节点 有如下域:
        A),keyNum,节点中存储的关键字的个数。
        B),keyNum 个以非降序次序排列的关键字 key[0 ... keyNum - 1]
        C),isLeaf,判断是否是叶子节点还是内节点的标志。

2),每个内节点还包含 keyNum + 1 个指向其子女的指针 child[i] (i >= 0 && i <= keyNum)

3),各个关键字 key[i] 对存储在各子树中的关键字范围加以分隔:即 key[i] 大于等于其左侧子树中的所有关键字,而小于等于其右侧子树中的所有关键字。

4),每个叶节点具有相同的深度,即均为树的高度 h

5)每一个节点能包含的关键字有一个上限和下限。这些界限可以用一个称作 树的最小度数的固定整数 T >= 2 来表示。
        A),每个非根的节点必须至少有 T - 1 个关键字。每个非根的内节点至少有 个子女。如果树是非空的,则根节点至少包含一个关键字。
        B),每个节点可包含至多 2T - 1 个关键字。所以一个内节点至多有 2T 个子女。当一个节点正好有 2T - 1 个关键字时,我们就说这个节点是满的。

等于 2  时的树是最简单的。这时每个内节点有 个或 个或 个子女,这种 树也被称作为 2-3-4 树。当然在实际应用中 的取值比这个大得多。

下面我实现了 默认等于 的 树:

// 定义 B 树的最小度数
// 每个节点中关键字的最大数目 BTree_N = 2 * BTree_T - 1
#define    BTree_T        2
#define BTree_N        (BTree_T * 2 - 1)

struct BTNode 
{
    int    keynum;                        // 结点中关键字的个数,keynum <= BTree_N
    int key[BTree_N];                // 关键字向量为key[0..keynum - 1]
    BTNode* child[BTree_T * 2];        // 孩子指针向量为child[0..keynum]
    bool isLeaf;                    // 是否是叶子节点的标志
}
;

typedef BTNode* BTree;                // B树的类型


void BTree_create(BTree* tree, const int* data, int length);

void BTree_destory(BTree* tree);

void BTree_insert(BTree* tree, int key);

void BTree_remove(BTree* tree, int key);

void BTree_print(BTree tree, int her = 1);

// 在B树 tree 中查找关键字 key,
// 成功时返回找到的节点的地址及 key 在其中的位置 *pos
// 失败时返回 NULL 及查找失败时扫描到的节点位置 *pos
//
BTNode* BTree_search(const BTree tree, int key, int* pos);


下面来看具体的接口实现:

#define max(a, b) (((a) > (b)) ? (a) : (b))

//#define DEBUG_TREE

#ifdef DEBUG_TREE
#define debug_print(fmt,) printf(fmt, ## __VA_ARGS__)
#else
#define debug_print(fmt,)
#endif

// 模拟向磁盘写入节点
void disk_write(BTNode* node)
{
    printf("向磁盘写入节点\n");
}


// 模拟从磁盘读取节点
void disk_read(BTNode** node)
{
    printf("从磁盘读取节点\n");
}


// 按层次打印 B 树
void BTree_print(BTree tree, int her)
{
    int i;
    BTNode* node = tree;

    if (node) 
{
        printf("第 %d 层, %d node : ", her, node->keynum);

        for (i = 0; i < node->keynum; ++i) 
{
            printf("%c ", node->key[i]);
        }


        printf("\n");

        ++her;
        for (i = 0 ; i <= node->keynum; i++) 
{
            if (node->child[i]) 
{
                BTree_print(node->child[i], her);
            }

        }

    }

    else {
        printf("树为空。\n");
    }

}


// 将非满的节点与其第 index 个满孩子节点合并
// parent 是一个非满的父节点
// node 是 tree 孩子表中下标为 index 的孩子节点,且是满的
void BTree_split_child(BTNode* parent, int index, BTNode* node)
{
    assert(parent && node);

    int i;

    // 创建新节点,存储 node 中后半部分的数据
    BTNode* newNode = (BTNode*)calloc(sizeof(BTNode), 1);
    if (!newNode) 
{
        printf("Error! out of memory!\n");
        return;
    }


    newNode->isLeaf = node->isLeaf;
    newNode->keynum = BTree_T - 1;

    // 拷贝 node 后半部分关键字
    for (i = 0; i < newNode->keynum; ++i)
{
        newNode->key[i] = node->key[BTree_T + i];
        node->key[BTree_T + i] = 0;
    }


    // 如果 node 不是叶子节点,拷贝 node 后半部分的孩子节点
    if (!node->isLeaf) 
{
        for (i = 0; i < BTree_T; i++) 
{
            newNode->child[i] = node->child[BTree_T + i];
            node->child[BTree_T + i] = NULL;
        }

    }


    // 将 node 分裂出 newNode 之后,里面的数据减半
    node->keynum = BTree_T - 1;

    // 调整父节点
    for (i = parent->keynum; i > index; --i) 
{
        parent->child[i + 1] = parent->child[i];
    }


    parent->child[index + 1] = newNode;

    for (i = parent->keynum - 1; i >= index; --i) 
{
        parent->key[i + 1] = parent->key[i];
    }


    parent->key[index] = node->key[BTree_T - 1];
    ++parent->keynum;

    // 清除 node 中的中后部数据
    // 可以不处理,因为是通过 keynum 控制访问的
//     for (i = BTree_T - 1; i < BTree_N; ++i) {
//         node->key[i] = 0;
//         node->child[i + 1] = NULL;
//     }

    // 写入磁盘
     disk_write(parent);
     disk_write(newNode);
     disk_write(node);
}


void BTree_insert_nonfull(BTNode* node, int key)
{
    assert(node);

    int i;

    // 节点是叶子节点,直接插入
    if (node->isLeaf) 
{
        i = node->keynum - 1;
        while (i >= 0 && key < node->key[i]) 
{
            node->key[i + 1] = node->key[i];
            --i;
        }


        node->key[i + 1] = key;
        ++node->keynum;

        // 写入磁盘
        disk_write(node);
    }


    // 节点是内部节点
    else 
{
        // 查找插入的位置
        i = node->keynum - 1;
        while (i >= 0 && key < node->key[i]) 
{
            --i;
        }


        ++i;

        // 从磁盘读取孩子节点
        disk_read(&node->child[i]);

        // 如果该孩子节点已满,分裂调整值
        if (node->child[i]->keynum == BTree_N) 
{
            BTree_split_child(node, i, node->child[i]);

            if (key > node->key[i]) 
{
                ++i;
            }

        }


        BTree_insert_nonfull(node->child[i], key);
    }

}


void BTree_insert(BTree* tree, int key)
{
    BTNode* node;
    BTNode* root = *tree;

    // 树为空
    if (NULL == root) 
{
        root = (BTNode*)calloc(sizeof(BTNode), 1);
        if (!root) 
{
            printf("Error! out of memory!\n");
            return;
        }

        root->isLeaf = true;
        root->keynum = 1;
        root->key[0] = key;

        *tree = root;

        // 写入磁盘
        disk_write(root);

        return;
    }


    // 节点已满,需要进行分裂调整
    if (root->keynum == BTree_N) 
{
        // 产生新节点当作根
        node = (BTNode*)calloc(sizeof(BTNode), 1);
        if (!node) 
{
            printf("Error! out of memory!\n");
            return;
        }


        *tree = node;
        node->isLeaf = false;
        node->keynum = 0;
        node->child[0] = root;

        BTree_split_child(node, 0, root);

        BTree_insert_nonfull(node, key);
    }


    // 节点未满,在当前节点中插入 key
    else 
{
        BTree_insert_nonfull(root, key);
    }

}



// 对 tree 中的节点 node 进行合并孩子节点处理
// 注意:孩子节点的 keynum 必须均已达到下限,即均等于 BTree_T - 1
// 将 node 中索引为 index + 1 的孩子节点合并到索引为 index 的孩子节点中,
// 并将 tree 中索引为 index  的 key 下降到该节点中,调整相关的 key 和指针。
//
void BTree_merge_child(BTree* tree, BTNode* node, int index)
{
    assert(tree && node && index >= 0 && index < node->keynum);

    int i;

    int key = node->key[index];
    BTNode* prevChild = node->child[index];
    BTNode* nextChild = node->child[index + 1];

    assert(prevChild && prevChild->keynum == BTree_T - 1
        && nextChild && nextChild->keynum == BTree_T - 1);

    prevChild->key[prevChild->keynum] = key;
    prevChild->child[prevChild->keynum + 1] = nextChild->child[0];
    ++prevChild->keynum;

    // 合并
    for (i = 0; i < nextChild->keynum; ++i) 
{
        prevChild->key[prevChild->keynum] = nextChild->key[i];
        prevChild->child[prevChild->keynum + 1] = nextChild->child[i + 1];
        ++prevChild->keynum;
    }


    // 在 node 中移除 key 以及指向后继孩子节点的指针
    for (i = index; i < node->keynum - 1; ++i) 
{
        node->key[i] = node->key[i + 1];
        node->child[i + 1] = node->child[i + 2];
    }


    node->key[node->keynum - 1] = 0;
    node->child[node->keynum] = NULL;
    --node->keynum;

    // 如果根节点没有 key 了,删之,并将根节点调整为前继孩子节点。
    if (node->keynum == 0) 
{
        if (*tree == node) 
{
            *tree = prevChild;
        }


        free(node);
        node = NULL;
    }


    free(nextChild);
}


void BTree_remove(BTree* tree, int key)
{
    // B-数的保持条件之一:
    // 非根节点的内部节点的关键字数目不能少于 BTree_T - 1

    int i, j, index;
    BTNode *root = *tree;
    BTNode *node = root;
    BTNode *prevChild, *nextChild, *child;
    int prevKey, nextKey;

    if (!root) 
{
        printf("Failed to remove %c, it is not in the tree!\n", key);
        return;
    }


    index = 0;
    while (index < node->keynum && key > node->key[index]) 
{
        ++index;
    }


    //
    //  index of key:    i-1  i  i+1
    //                +---+---+---+---+---+
    //               +   + A +   +  
    //                +---+---+---+---+---+
    //                 /    |    \
    //  index of C: i - 1   i    i + 1
    //               /      |      \
    //        +---+---+     +---+      +---+---+
    //       +   +     +   +   +   +  
    //        +---+---+     +---+      +---+---+
    //      prevChild     child   nextChild

    // Find the key.
    if (index < node->keynum && node->key[index] == key) 
{
        // 1,所在节点是叶子节点,直接删除
        if (node->isLeaf) 
{
            for (i = index; i < node->keynum; ++i) 
{
                node->key[i] = node->key[i + 1];
                node->child[i + 1] = node->child[i + 2];
            }


            --node->keynum;

            if (node->keynum == 0) 
{
                assert(node == *tree);
                free(node);
                *tree = NULL;
            }


            return;
        }


        // 2a,如果位于 key 前的子节点的 key 数目 >= BTree_T,
        // 在其中找 key 的前驱,用前驱的 key 值赋予 key,
        // 然后在前驱所在孩子节点中递归删除前驱。
        else if (node->child[index]->keynum >= BTree_T) 
{
            prevChild = node->child[index];
            prevKey = prevChild->key[prevChild->keynum - 1];
            node->key[index] = prevKey;

            BTree_remove(&prevChild, prevKey);
        }


        // 2b,如果位于 key 后的子节点的 key 数目 >= BTree_T,
        // 在其中找 key 的后继,用后继的 key 值赋予 key,
        // 然后在后继所在孩子节点中递归删除后继。
        else if (node->child[index + 1]->keynum >= BTree_T) 
{
            nextChild = node->child[index + 1];
            nextKey = nextChild->key[0];
            node->key[index] = nextKey;

            BTree_remove(&nextChild, nextKey);
        }


        // 2c,前驱和后继都只包含 BTree_T - 1 个节点,
        // 将 key 下降前驱孩子节点,并将后继孩子节点合并到前驱孩子节点,
        // 删除后继孩子节点,在 node 中移除 key 和指向后继孩子节点的指针,
        // 然后在前驱所在孩子节点中递归删除 key。
        else if (node->child[index]->keynum == BTree_T - 1
            && node->child[index + 1]->keynum == BTree_T - 1)
{
            prevChild = node->child[index];

            BTree_merge_child(tree, node, index);

            // 在前驱孩子节点中递归删除 key
            BTree_remove(&prevChild, key);
        }

    }


    // 3,key 不在内节点 node 中,则应当在某个包含 key 的子节点中。
    //  key < node->key[index], 所以 key 应当在孩子节点 node->child[index] 中
    else 
{
        child = node->child[index];
        if (!child) 
{
            printf("Failed to remove %c, it is not in the tree!\n", key);
            return;
        }


        if (child->keynum == BTree_T - 1) 
{
            prevChild = NULL;
            nextChild = NULL;

            if (index - 1 >= 0) 
{
                prevChild = node->child[index - 1];
            }


            if (index + 1 <= node->keynum) 
{
                nextChild = node->child[index + 1];
            }


            // 3a,如果所在孩子节点相邻的兄弟节点中有节点至少包含 BTree_t 个关键字
            // 将 node 的一个关键字下降到 child 中,将相邻兄弟节点中一个节点上升到
            // node 中,然后在 child 孩子节点中递归删除 key。
            if ((prevChild && prevChild->keynum >= BTree_T)
                || (nextChild && nextChild->keynum >= BTree_T)) 
{

                if (nextChild && nextChild->keynum >= BTree_T) 
{
                    child->key[child->keynum] = node->key[index];
                    child->child[child->keynum + 1] = nextChild->child[0];
                    ++child->keynum;

                    node->key[index] = nextChild->key[0];

                    for (j = 0; j < nextChild->keynum - 1; ++j) 
{
                        nextChild->key[j] = nextChild->key[j + 1];
                        nextChild->child[j] = nextChild->child[j + 1];
                    }

                    --nextChild->keynum;
                }

                else {
                    for (j = child->keynum; j > 0; --j) 
{
                        child->key[j] = child->key[j - 1];
                        child->child[j + 1] = child->child[j];
                    }

                    child->child[1] = child->child[0];
                    child->child[0] = prevChild->child[prevChild->keynum];
                    child->key[0] = node->key[index - 1];
                    ++child->keynum;

                    node->key[index - 1] = prevChild->key[prevChild->keynum - 1];

                    --prevChild->keynum;
                }

            }


            // 3b, 如果所在孩子节点相邻的兄弟节点都只包含 BTree_t - 1 个关键字,
            // 将 child 与其一相邻节点合并,并将 node 中的一个关键字下降到合并节点中,
            // 再在 node 中删除那个关键字和相关指针,若 node 的 key 为空,删之,并调整根。
            // 最后,在相关孩子节点中递归删除 key。
            else if ((!prevChild || (prevChild && prevChild->keynum == BTree_T - 1))
                && ((!nextChild || nextChild && nextChild->keynum == BTree_T - 1))) 
{
                if (prevChild && prevChild->keynum == BTree_T - 1) 
{

                    BTree_merge_child(tree, node, index - 1);

                    child = prevChild;
                }


                else if (nextChild && nextChild->keynum == BTree_T - 1) 
{

                    BTree_merge_child(tree, node, index);
                }

            }

        }


        BTree_remove(&child, key);
    }

}


BTNode* BTree_search(const BTree tree, int key, int* pos)
{
    if (!tree) 
{
        return NULL;
    }


    int i = 0;

    while (i < tree->keynum && key > tree->key[i]) 
{
        ++i;
    }


    // Find the key.
    if (i < tree->keynum && tree->key[i] == key) 
{
        if (pos) 
{
            *pos = i;
        }


        return tree;
    }


    // tree 为叶子节点,找不到 key,查找失败返回
    if (tree->isLeaf) 
{
        return NULL;
    }


    // 节点内查找失败,但 tree->key[i - 1]< key < tree->key[i],
    // 下一个查找的结点应为 child[i]

    // 从磁盘读取第 i 个孩子的数据
    disk_read(&tree->child[i]);

    // 递归地继续查找于树 tree->child[i]
    return BTree_search(tree->child[i], key, pos);
}


void BTree_create(BTree* tree, const int* data, int length)
{
    assert(tree);

    int i;

#ifdef DEBUG_TREE
    debug_print("\n 开始创建 B- 树,关键字为:\n");
    for (i = 0; i < length; i++) 
{
        printf(" %c ", data[i]);
    }

    debug_print("\n");
#endif


    for (i = 0; i < length; i++) 
{
#ifdef DEBUG_TREE
        debug_print("\n插入关键字 %c:\n", data[i]);
#endif
        BTree_insert(tree, data[i]);

#ifdef DEBUG_TREE
        BTree_print(*tree);
#endif
    }


    debug_print("\n");
}


void BTree_destory(BTree* tree)
{
    int i;
    BTNode* node = *tree;

    if (node) 
{
        for (i = 0; i <= node->keynum; i++) 
{
            BTree_destory(&node->child[i]);
        }


        free(node);
    }


    *tree = NULL;
}


测试代码:

//==================================================================
//                    测试 B 树
//==================================================================
void test_BTree_search(BTree tree, int key)
{
    int pos = -1;
    BTNode*    node = BTree_search(tree, key, &pos);
    if (node) {
        printf("在%s节点(包含 %d 个关键字)中找到关键字 %c,其索引为 %d\n",
            node->isLeaf ? "叶子" : "非叶子",
            node->keynum, key, pos);
    }
    else {
        printf("在树中找不到关键字 %c\n", key);
    }
}

void test_BTree_remove(BTree* tree, int key)
{
    printf("\n移除关键字 %c \n", key);
    BTree_remove(tree, key);
    BTree_print(*tree);
    printf("\n");
}

void test_btree()
{
    const int length = 10;
    int array[length] = {
        'G', 'M', 'P', 'X', 'A', 'C', 'D', 'E', 'J', 'K',
        //'N', 'O', 'R', 'S', 'T', 'U', 'V', 'Y', 'Z', 'F'
    };

    BTree tree = NULL;
    BTNode* node = NULL;
    int pos = -1;
    int key1 = 'R';        // in the tree.
    int key2 = 'B';        // not in the tree.

    // 创建
    BTree_create(&tree, array, length);

    printf("\n=== 创建 B- 树 ===\n");
    BTree_print(tree);
    printf("\n");

    // 查找
    test_BTree_search(tree, key1);
    printf("\n");
    test_BTree_search(tree, key2);

    // 插入关键字
    printf("\n插入关键字 %c \n", key2);
    BTree_insert(&tree, key2);
    BTree_print(tree);
    printf("\n");

    test_BTree_search(tree, key2);

    // 移除关键字
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'M';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'E';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'G';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'A';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'D';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'K';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'P';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'J';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'C';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    key2 = 'X';
    test_BTree_remove(&tree, key2);
    test_BTree_search(tree, key2);

    // 销毁
    BTree_destory(&tree);
}

测试结果:

=== 创建 B  树 ===

第 层, 1 node : E

第 层, 1 node : C

第 层, 1 node : A

第 层, 1 node : D

第 层, 1 node : M

第 层, 3 node : G J K

第 层, 2 node : P X

从磁盘读取节点

从磁盘读取节点

在树中找不到关键字 R

从磁盘读取节点

从磁盘读取节点

在树中找不到关键字 B

插入关键字 B

从磁盘读取节点

从磁盘读取节点

向磁盘写入节点

第 层, 1 node : E

第 层, 1 node : C

第 层, 2 node : A B

第 层, 1 node : D

第 层, 1 node : M

第 层, 3 node : G J K

第 层, 2 node : P X

从磁盘读取节点

从磁盘读取节点

在叶子节点(包含 个关键字)中找到关键字 B,其索引为 1

原创粉丝点击