#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;
}
二叉查找树
最新推荐文章于 2024-07-16 16:37:19 发布