二叉查找树

#include <stdio.h>

//二叉查找树
//结合了链表插入的灵活性和有序数组查找的高效性,适用于经常插入\查找
//的场合使用.
//其中二叉查找树的特性为每个节点最多有两个子节点,其中左子节点小于当
//前节点,右子节点大于当前节点
//             6
//           /   \
//          3     8
//         / \   / \
//        2   4     11
typedef struct _NODE
{
    int             key;
    int             val;
    struct _NODE   *left; 
    struct _NODE   *right; 
    int             n;
} NODE;

NODE *root = NULL;

//计算当前节点为根的子树节点总数(包含当前节点)
int size(NODE *pNode)
{
    if ( pNode == NULL )
    {
        return 0; 
    }

    return pNode->n;
}

NODE* newNode(int key, int val)
{
    NODE *pNew = (NODE *)malloc(sizeof(NODE));   
    if ( pNew == NULL )
    {
        return NULL; 
    }

    memset(pNew, 0, sizeof(NODE));
    pNew->key = key;
    pNew->val = val;
    pNew->n = 1;

    return pNew;
}

//查找
//根据二叉树的特性
//1 如果当前查找键小于当前节点,则当前查找节点切为左子节点,
//  继续去左子节点查找
//2 如果当前查找键大于当前节点,则当前查找节点切为右子节点,
//  继续去右子节点查找
//3 查找成功,返回键值
//4 对应的子节点已经为空,则没有任何匹配项,当前返回-1来表示
int _get(NODE *pNode, int key)
{
    if ( pNode == NULL )
    {
        return -1; 
    }

    if ( key < pNode->key )
    {
        return _get(pNode->left, key); 
    }
    else if ( key > pNode->key )
    {
        return _get(pNode->right, key); 
    }

    return pNode->val;
}

int get(int key)
{
    return _get(root, key);
}

//插入
//1 如果当前关键字小于当前节点,则继续向左子节点进行插入处理
//2 如果当前关键字大于当前节点,则继续向右子节点进行插入处理
//3 如果当前关键字等于当前节点,则表明当前已经存在,则进行键值替换
//4 如果最后移到了树的最左端或最右端,已经没有节点,则在此位置建立
//  新的节点项
//5 在递归插入过程中,有可能新的KEY没有已存在项,所以新的KEY会
//  使用newNode分配一个新的节点对象进行插入,此时该路径上的所有
//  关联父节点的N子节点个数都要进行更新,这里采用了递归回退的方
//  式进行实现,当插入完成后,递归调用一步步退回每个节点的父节点
//  调用位置,则这里进行该路径上所有关联父节点的更新.
NODE* _put(NODE* pNode, int key, int val)
{
    if ( pNode == NULL )
    {
        return newNode(key, val);
    }

    if ( key < pNode->key )
    {
        pNode->left = _put(pNode->left, key, val); 
    }
    else if ( key > pNode->key )
    {
        pNode->right = _put(pNode->right, key, val); 
    }
    else
    {
        pNode->val = val; 
    }

    pNode->n = size(pNode->left) + size(pNode->right) + 1;

    return pNode;
}

void put(int key, int val)
{
    root = _put(root, key, val);
}

//获取当前树的最小节点,根据二叉树的特性,则一直遍历
//左子树,直到该节点没有左子节点,则该节点就是当前树
//的最小节点
NODE* _min(NODE* pNode)
{
    if ( pNode->left == NULL )
    {
        return pNode; 
    }

    return _min(pNode->left);
}

int min()
{
    if ( root == NULL )
    {
        return -1; 
    }

    return _min(root)->key;
}

//获取最小的第index节点
//这里借用了节点中的N成员变量进行实现,因为N成员变量表示
//以当前节点为根的子树成员个数,其中也间接隐含了一种排名
//的指示.
//注意这里index索引值范围是从0开始,比如如下示例(括号中的值
//即为当前节点的size),查找index = 0,则返回2,查找index = 1,
//则返回4,查找index=2,则返回7.
//       4(2)
//     /     \
//   2(1)   7(1)
//   /  \   /  \
//
int _select(NODE *pNode, int index)
{
    int leftSize = 0;
   
    if (pNode == NULL)
    {
        return -1; 
    }

    leftSize = size(pNode->left);

    if ( leftSize > index )
    {
        return _select(pNode->left, index); 
    }
    else if ( leftSize < index )
    {
        return _select(pNode->right, index - leftSize - 1); 
    }
    else
    {
        return pNode->key; 
    }
}

/* index range is 0 ~ (size(root)-1) */
int select(int index)
{
    return _select(root, index);
}

//删除最小节点
//即然是最小节点,也就表明待删除的节点是没有左子节点的,所以这里
//删除的节点或者仅有一个右子节点,或者没有任何子节点.
//这里实现思路就是递归找到最小节点,将其删除,同时将该节点的右子
//树重新挂接到当前待删除节点的父节点上.比如当前示例,如果要删除
//2,则2的右子树3重新挂接到2的父节点4的左子节点位置.
//       4
//     /   \
//    2     7
//   / \   / \
//      3 5   9
NODE* _deleteMin(NODE *pNode)
{
    if ( pNode == NULL )
    {
        return NULL; 
    }

    if ( pNode->left == NULL )
    {
        NODE *pRightNode = pNode->right;

        printf("delete min key %d\r\n", pNode->key);
        free(pNode);

        return pRightNode; 
    }

    pNode->left = _deleteMin(pNode->left);
    pNode->n = size(pNode->left) + size(pNode->right) + 1;

    return pNode;
}

void deleteMin()
{
    root = _deleteMin(root);
}

//节点删除
//二叉树的删除操作相对有点复杂,主要区分两种情况
//1 当前节点仅有一个子树或者没有子树,此时删除操作比较简单,
//  如果该节点没有左子树,则该节点删除后,将该节点的右子树重
//  新挂接到父节点上即可,如果该节点没有右子树,则该节点删除
//  后,将该节点的左子树重新挂接到父节点即可.
//2 当前节点如果含有两个子节点,此时处理就比较麻烦一些.
//  如下示例所示,比如要删除7
//  a 首先找到待删除节点右子树中最小值,当前示例中,7右子树最
//    小值就是8
//  b 之后将节点8与节点7进行替换,替换后7就跑到8的位置,在这个
//    位置,由于之前8是最小值,所以原来位置一定是仅有一个子树
//    或者没有任何子树
//  c 此时8已经移到了原来7的位置,将8的左节点指向原来7的左节
//    点5
//  d 使用删除最小子节点的算法,针对当前8的新位置(即原来7所在的
//    点),进行最小子节点删除,此时原节点7就在这个位置,所以节点
//    7的删除处理由原来考虑两个节点同时存在情况,变成了仅有一
//    个节点或没有任何节点的删除情况,删除算法见之前删除最小
//    节点算法.
//  e 最后将当前8的新位置重新嫁接到原来7的父节点4上.
//       4
//     /   \
//    2     7
//   / \   / \
//      3 5   9
//           / \
//          8
//         / \
//           
NODE* _delete(NODE *pNode, int key)
{
    if ( pNode == NULL )
    {
        return NULL; 
    }

    if ( key < pNode->key )
    {
        pNode->left = _delete(pNode->left, key); 
    }
    else if ( key > pNode->key )
    {
        pNode->right = _delete(pNode->right, key); 
    }
    else
    {
        if ( pNode->left == NULL || pNode->right == NULL ) 
        {
            NODE* retNode = ((pNode->left == NULL) ? pNode->right : pNode->left);
            printf("delete key %d\r\n", pNode->key);
            free(pNode);
            return retNode; 
        }
        else
        {
            NODE tmp = *pNode; 
            *pNode = *_min(tmp.right);
            pNode->left = tmp.left;
            pNode->right = _deleteMin(tmp.right);
        }
    }

    pNode->n = size(pNode->left) + size(pNode->right) + 1;

    return pNode;
}

void delete(int key)
{
    root = _delete(root, key);
}

//中序遍历
//中序遍历即按从小到大的递增顺序遍历,此时要先不断递归显示
//当前节点的左子树,待左子树全部遍历完后,在显示当前节点,之后
//再递归显示当前节点的右子树.
void showKey(NODE* pNode)
{
    if ( pNode == NULL )
    {
        return;
    }

    showKey(pNode->left); 
    printf("key %d\r\n", pNode->key);
    showKey(pNode->right);
}

//获取满足指定范围的key集合
//这里也采用了中序遍历
//在lo~hi范围内,需要满足如下条件
//1 左边界不能小于lo
//2 右边界不能大于hi
//3 左边界大于等于lo,同时右边界小于等于hi
void _getKeys(NODE* pNode, int lo, int hi)
{
    if ( pNode == NULL )
    {
        return; 
    }

    //当前节点如果大于lo,则其左子树(比当前节点小,即进行左边界
    //向左延伸试探)可能还有满足左边界不能小于lo的希望
    if ( pNode->key > lo ) 
    {
        _getKeys(pNode->left, lo, hi); 
    }

    //当前满足条件3,则完全符合条件
    if ( pNode->key >= lo && pNode->key <= hi )
    {
        printf("key %d in lo[%d] ~ hi[%d]\r\n", pNode->key, lo, hi); 
    }

    //当前节点如果小于hi,则其右子树(比当前节点大,即进行右边界
    //向右延伸试探)可能还有满足右边界不能大于hi的希望
    if ( pNode->key < hi )
    {
        _getKeys(pNode->right, lo, hi); 
    }
}

void getKeys(int lo, int hi)
{
    _getKeys(root, lo, hi);
}

int main(int argc, char *argv[])
{
    int i = 0;
    int findKey = 0;
    int getVal = 0;
    int keys[] = {2,  4,  6,  1,  9,  4,  9,  8,  7,  2,  10};
    int vals[] = {21, 41, 61, 11, 91, 42, 92, 81, 71, 22, 101};

    if ( argc != 2 )
    {
        printf("Usage: %s <findKey>\r\n", argv[0]);
        return 0; 
    }

    for ( i = 0; i < sizeof(keys)/sizeof(keys[0]); i++)
    {
        put(keys[i], vals[i]); 
    }

    findKey = atoi(argv[1]);
    getVal = get(findKey);

    printf("key %d, val %d\r\n", findKey, getVal);
    printf("min key %d\r\n", min());
    printf("select index 3, key is %d\r\n", select(3));

    getKeys(3, 7);

    deleteMin();
    deleteMin();

    delete(4);
    delete(9);

    showKey(root);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值