簡體   English   中英

如何實現檢查 function 以檢查 b 樹屬性的有效性?

[英]How can I implement a check function to check the validity of the properties of a b-tree?

我最近在 C 中實現了一個普通的 B 樹(沒有任何變體),但我想檢查我的實現是否有效,即它是否不違反以下屬性:

  1. 每個節點最多有 m 個子節點。
  2. 每個非葉節點(根除外)至少有 ⌈m/2⌉ 子節點。
  3. 如果根不是葉節點,則根至少有兩個子節點。
  4. 具有 k 個子節點的非葉節點包含 k-1 個鍵。
  5. 所有葉子都出現在同一級別並且不攜帶任何信息。

如果有人可以幫助我執行此過程,請給我一個示例,其中包含 C 中的一些代碼或一些建議。 提前謝謝了。

#include <stdio.h>
#include <stdlib.h>

#define TRUE 1
#define FALSE 0
#define EMPTY 0

#define NODE_ORDER      3 /*The degree of the tree.*/
#define NODE_POINTERS   (NODE_ORDER*2)
#define NODE_KEYS       NODE_POINTERS-1

typedef unsigned char bool;

typedef struct tree_node {
    int key_array[NODE_KEYS];
    struct tree_node *child_array[NODE_POINTERS];
    unsigned int key_index;
    bool leaf;
} node_t;

typedef struct {
    node_t *node_pointer;
    int key;
    bool found;
    unsigned int depth;
} result_t;

typedef struct {
    node_t *root;
    unsigned short order;
    bool lock;
} btree_t;

static int BTreeGetLeftMax(node_t *T);
static int BTreeGetRightMin(node_t *T);
/* The AllocateNode operation allocate a b-tree node.And then set the node's
** properties to the defualt value :
**   BTreeNode => K[i] = 0
**   BTreeNode => child_array[i] = NULL
**   BTreeNode => key_index = 0
**   BTreeNode => isLeaf = 1;
*/
static node_t *create_node()
{
    int i;
    node_t *new_node = (node_t *)malloc(sizeof(node_t));

    if(!new_node){
        printf("Out of memory");
        exit(0);
    }

    // Set Keys
    for(i = 0;i < NODE_KEYS; i++){
        new_node->key_array[i] = 0;
    }

    // Set ptr
    for(i = 0;i < NODE_POINTERS; i++){
        new_node->child_array[i] = NULL;
    }

    new_node->key_index = EMPTY;
    new_node->leaf = TRUE;

    return new_node;
}
/* The CreatBTree operation creates an empty b-tree by allocating a new root
** that has no keys and is a leaf node.Only the root node is permitted to
** have this properties.
*/
btree_t *create_btree()
{
    btree_t *new_root = (btree_t *)malloc(sizeof(btree_t));

    if(!new_root){
        return NULL;
    }

    node_t *head = create_node();

    if(!head){
        return NULL;
    }

    new_root->order = NODE_ORDER;
    new_root->root = head;
    new_root->lock = FALSE;

    return new_root;
}

static result_t *get_resultset()
{
    result_t *ret = (result_t *)malloc(sizeof(result_t));

    if(!ret){
        printf("ERROR! Out of memory.");
        exit(0);
    }

    ret->node_pointer = NULL;
    ret->key = 0;
    ret->found = FALSE;
    ret->depth = 0;

    return ret;
}

/* The BTreeSearch operation is to search X in T.Recursively traverse the tree
** from top to bottom.At each level, BTreeSearch choose the maximum key whose
** value is greater than or equal to the desired value X.If equal to the
** desired ,found.Otherwise continue to traverse.
*/
result_t *search(int key, node_t *node)
{
    print_node(node);
    int i = 0;

    while((i < node->key_index) && (key > node->key_array[i])){
        //printf("it %d is <= %d and key %d > than %d\n", i, node->key_index, key, node->key_array[i]);
        i++;
    }
//printf("end iterator: %d\n", i);

//printf("better: \n");
/*
    int c = 0;
    while((c < node->key_index) && (key > node->key_array[c])){
        printf("it %d is <= %d and key %d > than %d\n", c, node->key_index, key, node->key_array[c]);
        c++;
    }
*/
    // HACK /// may not be working 
    if(i == 6){
        i--;
    }

    // Check if we found it
    if((i <= node->key_index) && (key == node->key_array[i])){
        result_t *result = get_resultset();
        result->node_pointer = node;
        result->key = i;
        result->found = TRUE;
        return result;
    }

    // Not found check leaf or child
    if(node->leaf){
        result_t *result = get_resultset();
        result->node_pointer = node;
        result->found = FALSE;
        return result;
    }else{
        result_t *result = get_resultset();
        return search(key, node->child_array[i]);
    }
}
/* The split_child operation moves the median key of node child_array into
** its parent ptrParent where child_array is the ith child of ptrParent.
*/
static void split_child(node_t *parent_node, int i, node_t *child_array)
{
    int j;

    //Allocate a new node to store child_array's node.
    node_t *new_node = create_node();
    new_node->leaf = child_array->leaf;
    new_node->key_index = NODE_ORDER-1;

    //Move child_array's right half nodes to the new node.
    for(j = 0;j < NODE_ORDER-1;j++){
        new_node->key_array[j] = child_array->key_array[NODE_ORDER+j];
    }

    //If child_array is not leaf node,then move child_array's [child_array]s to the new
    //node's [child_array]s.
    if(child_array->leaf == 0){
        for(j = 0;j < NODE_ORDER;j++){
            new_node->child_array[j] = child_array->child_array[NODE_ORDER+j];
        }
    }
    child_array->key_index = NODE_ORDER-1;

    //Right shift ptrParent's [child_array] from index i
    for(j = parent_node->key_index;j>=i;j--){
        parent_node->child_array[j+1] = parent_node->child_array[j];
    }

    //Set ptrParent's ith child_array to the newNode.
    parent_node->child_array[i] = new_node;

    //Right shift ptrParent's Keys from index i-1
    for(j = parent_node->key_index;j>=i;j--){
        parent_node->key_array[j] = parent_node->key_array[j-1];
    }

    //Set ptrParent's [i-1]th Key to child_array's median [child_array]
    parent_node->key_array[i-1] = child_array->key_array[NODE_ORDER-1];

    //Increase ptrParent's Key number.
    parent_node->key_index++;
}
/* The BTreeInsertNonFull operation insert X into a non-full node T.before
** execute this operation,guarantee T is not a full node.
*/
static void insert_nonfull(node_t *n, int key){
    int i = n->key_index;
    
    if(n->leaf){
        // Shift until we fit
        while(i>=1 && key<n->key_array[i-1]){
            n->key_array[i] = n->key_array[i-1];
            i--;
        }
        n->key_array[i] = key;
        n->key_index++;
    }else{
        // Find the position i to insert.
        while(i>=1 && key<n->key_array[i-1]){
            i--;
        }
        //If T's ith child_array is full,split first.
        if(n->child_array[i]->key_index == NODE_KEYS){
            split_child(n, i+1, n->child_array[i]);
            if(key > n->key_array[i]){
                i++;
            }
        }
        //Recursive insert.
        insert_nonfull(n->child_array[i], key);
    }
}
/* The BTreeInsert operation insert key into T.Before insert ,this operation
** check whether T's root node is full(root->key_index == 2*d -1) or not.If full,
** execute split_child to guarantee the parent never become full.And then
** execute BTreeInsertNonFull to insert X into a non-full node.
*/
node_t *insert(int key, btree_t *b)
{
    if(!b->lock){
        node_t *root = b->root;
        if(root->key_index == NODE_KEYS){ //If node root is full,split it.
            node_t *newNode = create_node();
            b->root = newNode; //Set the new node to T's Root.
            newNode->leaf = 0;
            newNode->key_index = 0;
            newNode->child_array[0] = root;
            split_child(newNode, 1, root);//Root is 1th child of newNode.
            insert_nonfull(newNode, key); //Insert X into non-full node.
        }else{ //If not full,just insert X in T.
            insert_nonfull(b->root, key);
        }
    }else{
        printf("Tree is locked\n");
    }

    return b->root;
}
/* The merge_children operation merge the root->K[index] and its two child
** and then set chlid1 to the new root.
*/
static void merge_children(node_t *root, int index, node_t *child1, node_t *child2){
    child1->key_index = NODE_KEYS;
    int i;
    //Move child2's key to child1's right half.
    for(i=NODE_ORDER;i<NODE_KEYS;i++)
        child1->key_array[i] = child2->key_array[i-NODE_ORDER];
    child1->key_array[NODE_ORDER-1] = root->key_array[index]; //Shift root->K[index] down.
    //If child2 is not a leaf node,must copy child2's [ptrchlid] to the new
    //root(child1)'s [child_array].
    if(0 == child2->leaf){
        for(i=NODE_ORDER;i<NODE_POINTERS;i++)
            child1->child_array[i] = child2->child_array[i-NODE_ORDER];
    }
    //Now update the root.
    for(i=index+1;i<root->key_index;i++){
        root->key_array[i-1] = root->key_array[i];
        root->child_array[i] = root->child_array[i+1];
    }
    root->key_index--;
    free(child2);
}
/* The BTreeBorrowFromLeft operation borrows a key from leftPtr.curPtr borrow
** a node from leftPtr.root->K[index] shift down to curPtr,shift leftPtr's
** right-max key up to root->K[index].
*/
static void BTreeBorrowFromLeft(node_t *root, int index, node_t *leftPtr, node_t *curPtr){
    curPtr->key_index++;
    int i;
    for(i=curPtr->key_index-1;i>0;i--)
        curPtr->key_array[i] = curPtr->key_array[i-1];
    curPtr->key_array[0] = root->key_array[index];
    root->key_array[index] = leftPtr->key_array[leftPtr->key_index-1];
    if(0 == leftPtr->leaf)
        for(i=curPtr->key_index;i>0;i--)
            curPtr->child_array[i] = curPtr->child_array[i-1];
    curPtr->child_array[0] = leftPtr->child_array[leftPtr->key_index];
    leftPtr->key_index--;
}
/* The BTreeBorrowFromLeft operation borrows a key from rightPtr.curPtr borrow
** a node from rightPtr.root->K[index] shift down to curPtr,shift RightPtr's
** left-min key up to root->K[index].
*/
static void BTreeBorrowFromRight(node_t *root, int index, node_t *rightPtr, node_t *curPtr){
    curPtr->key_index++;
    curPtr->key_array[curPtr->key_index-1] = root->key_array[index];
    root->key_array[index] = rightPtr->key_array[0];
    int i;
    for(i=0;i<rightPtr->key_index-1;i++)
        rightPtr->key_array[i] = rightPtr->key_array[i+1];
    if(0 == rightPtr->leaf){
        curPtr->child_array[curPtr->key_index] = rightPtr->child_array[0];
        for(i=0;i<rightPtr->key_index;i++)
            rightPtr->child_array[i] = rightPtr->child_array[i+1];
    }
    rightPtr->key_index--;
}
/* The BTreeDeleteNoNone operation recursively delete X in root,handle both leaf
** and internal node:
**   1. If X in a leaf node,just delete it.
**   2. If X in a internal node P:
**      a): If P's left neighbor -> prePtr has at least d keys,replace X with
**          prePtr's right-max key and then recursively delete it.
**      b): If P's right neighbor -> nexPtr has at least d keys,replace X with
**          nexPtr's left-min key and then recursively delete it.
**      c): If both of prePtr and nexPtr have d-1 keys,merge X and nexPtr into
**          prePtr.Now prePtr have 2*d-1 keys,and then recursively delete X in
**          prePtr.
**   3. If X not in a internal node P,X must in P->child_array[i] zone.If child_array[i]
**      only has d-1 keys:
**      a): If child_array[i]'s neighbor have at least d keys,borrow a key from
**          child_array[i]'s neighbor.
**      b): If both of child_array[i]'s left and right neighbor have d-1 keys,merge
**          child_array[i] with one of its neighbor.
**      finally,recursively delete X.
*/
static void BTreeDeleteNoNone(int X, node_t *root){
    int i;
    //Is root is a leaf node ,just delete it.
    if(1 == root->leaf){
        i=0;
        while( (i<root->key_index) && (X>root->key_array[i])) //Find the index of X.
            i++;
        //If exists or not.
        if(X == root->key_array[i]){
            for(;i<root->key_index-1;i++)
                root->key_array[i] = root->key_array[i+1];
            root->key_index--;
        }
        else{
            printf("Node not found.\n");
            return ;
        }
    }
    else{  //X is in a internal node.
        i = 0;
        node_t *prePtr = NULL, *nexPtr = NULL;
        //Find the index;
        while( (i<root->key_index) && (X>root->key_array[i]) )
            i++;
        if( (i<root->key_index) && (X == root->key_array[i]) ){ //Find it in this level.
            prePtr = root->child_array[i];
            nexPtr = root->child_array[i+1];
            /*If prePtr at least have d keys,replace X by X's precursor in
             *prePtr*/
            if(prePtr->key_index > NODE_ORDER-1){
                int aPrecursor = BTreeGetLeftMax(prePtr);
                root->key_array[i] = aPrecursor;
                //Recursively delete aPrecursor in prePtr.
                BTreeDeleteNoNone(aPrecursor,prePtr);
            }
            else
            if(nexPtr->key_index > NODE_ORDER-1){
            /*If nexPtr at least have d keys,replace X by X's successor in
             * nexPtr*/
                int aSuccessor = BTreeGetRightMin(nexPtr);
                root->key_array[i] = aSuccessor;
                BTreeDeleteNoNone(aSuccessor,nexPtr);
            }
            else{
            /*If both of root's two child have d-1 keys,then merge root->K[i]
             * and prePtr nexPtr. Recursively delete X in the prePtr.*/
                merge_children(root,i,prePtr,nexPtr);
                BTreeDeleteNoNone(X,prePtr);
            }
        }
        else{ //Not find in this level,delete it in the next level.
            prePtr = root->child_array[i];
            node_t *leftBro = NULL;
            if(i<root->key_index)
                nexPtr = root->child_array[i+1];
            if(i>0)
                leftBro = root->child_array[i-1];
            /*root->child_array[i] need to borrow from or merge with his neighbor
             * and then recursively delete. */
            if(NODE_ORDER-1 == prePtr->key_index){
            //If left-neighbor have at least d-1 keys,borrow.
                if( (leftBro != NULL) && (leftBro->key_index > NODE_ORDER-1))
                    BTreeBorrowFromLeft(root,i-1,leftBro,prePtr);
                else //Borrow from right-neighbor
                if( (nexPtr != NULL) && (nexPtr->key_index > NODE_ORDER-1))
                    BTreeBorrowFromRight(root,i,nexPtr,prePtr);
                //OR,merge with its neighbor.
                else if(leftBro != NULL){
                    //Merge with left-neighbor
                    merge_children(root,i-1,leftBro,prePtr);
                    prePtr = leftBro;
                }
                else //Merge with right-neighbor
                    merge_children(root,i,prePtr,nexPtr);
            }
            /*Now prePtr at least have d keys,just recursively delete X in
             * prePtr*/
            BTreeDeleteNoNone(X,prePtr);
        }
    }
}
/*Get T's left-max key*/
static int BTreeGetLeftMax(node_t *T){
    if(0 == T->leaf){
        return BTreeGetLeftMax(T->child_array[T->key_index]);
    }else{
        return T->key_array[T->key_index-1];
    }
}
/*Get T's right-min key*/
static int BTreeGetRightMin(node_t *T){
    if(0 == T->leaf){
        return BTreeGetRightMin(T->child_array[0]);
    }else{
        return T->key_array[0];
    }
}
/* The BTreeDelete operation delete X from T up-to-down and no-backtrack.
** Before delete,check if it's necessary to merge the root and its children
** to reduce the tree's height.Execute BTreeDeleteNoNone to recursively delete
*/
node_t *delete(int key, btree_t *b)
{
    if(!b->lock){
        //if the root of T only have 1 key and both of T's two child have d-1
        //key,then merge the children and the root. Guarantee not need to backtrack.
        if(b->root->key_index == 1){
            node_t *child1 = b->root->child_array[0];
            node_t *child2 = b->root->child_array[1];
            if((!child1) && (!child2)){
                if((NODE_ORDER-1 == child1->key_index) && (NODE_ORDER-1 == child2->key_index)){
                    //Merge the children and set child1 to the new root.
                    merge_children(b->root, 0, child1, child2);
                    free(b->root);
                    BTreeDeleteNoNone(key, child1);
                    return child1;
                }
            }
        }
        BTreeDeleteNoNone(key, b->root);
    }else{
        printf("Tree is locked\n");
    }
    return b->root;
}

void tree_unlock(btree_t *r)
{
    r->lock = FALSE;
}

bool tree_lock(btree_t *r)
{
    if(r->lock){
        return FALSE;
    }   
    r->lock = TRUE;

    return TRUE;
}

您沒有展示任何代碼,因此很難提出適合您的實現的代碼示例。 但是,原則上您可以采取以下方法:

  • 為您的代碼編寫單元測試。 使用 B 樹意味着從小樹開始(即使是空樹),並在測試中使用檢查來驗證屬性。 然后,您將添加越來越多的測試,特別是在“棘手”場景中也檢查錯誤。 有很多關於單元測試的一般信息可用,您應該能夠根據您的特定問題對其進行調整。
  • 在代碼中添加斷言(閱讀 C 中的assert宏)。 您提到的許多屬性都可以在代碼中的適當位置直接檢查。

當然,你還可以做更多的事情,比如讓一些同事審查代碼,或者使用一些形式驗證工具,但上述兩種方法是很好的起點。

更新(添加代碼后):

關於如何進行單元測試的更多提示。 原則上,您應該在所謂的測試框架的幫助下編寫測試,這是一個幫助庫,可以使編寫測試更容易。 然而,為了解釋這個概念,我只使用普通的 C 甚至偽代碼。

此外,您還可以將一些聲明和/或定義放入 header 文件中,例如“btree.h”。 但是,為了舉例,我將在下面的代碼示例中只使用#include "btree.c"

  • 創建一個文件“btree-test.c”(名稱是一個提案,你可以隨意命名)。
  • 第一個測試看起來有點像:
#include "btree.c"
#include <assert.h>

void test_create_empty_btree() {
    btree_t *actual_btree = create_btree();
    // now, check that the created btree has all desired properties
    // for example:
    assert(actual_btree != NULL);
    assert(actual_btree->order == NODE_ORDER);
    assert(actual_btree->lock == FALSE);
    assert(actual_btree->root->key_index == EMPTY);
    assert(actual_btree->root->leaf == TRUE);
    printf("PASSED: test_create_empty_btree");
}

上面的代碼只是一個例子,我什至沒有嘗試編譯它。 另請注意,測試還不是很干凈:會有 memory 泄漏,因為在測試結束時沒有正確刪除 btree,這將是更好的做法。 但是,它應該讓您了解如何開始編寫單元測試。

然后第二個測試可以再次創建一個 btree,但另外插入一些數據。 然后,在您的測試中,您將檢查 btree 是否具有預期的形式。 等等,添加越來越多的測試。 每個測試用例有一個 function 是一種很好的做法......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM