前两天想看下红黑树,结果发现基础的二叉查找树都快忘的差不多了,就大概回顾了一下。重写了下代码。
二叉查找树,作为平衡二叉树的基础,每个节点最多有两个孩子,因为不会对自身进行平衡维护,所以容易退化成链表。
创建
//创建节点
void createNode(BSTree **node)
{
*node = (BSTree *)malloc(sizeof(BSTree));
(*node)->leftChild = NULL;
(*node)->rightChild = NULL;
(*node)->key = -1;
}
插入
//插入节点
_Bool insertNode(BSTree **node, int key)
{
//先判定是否空指针,是则开辟空间
if(*node == NULL)
createNode(node);
//再进行插入操作
if((*node)->key == -1) //空节点,直接插入当前位置
(*node)->key = key;
else if(key < (*node)->key) //key小于该节点值,递归左孩子
insertNode(&((*node)->leftChild), key);
else if(key > (*node)->key) //key大于该节点值,递归右孩子
insertNode(&((*node)->rightChild),key);
else //key重复,返回错误值
return false;
return true;
}
查找
//查找节点
_Bool selectNode(BSTree * node, int key)
{
if(node == NULL)
{
puts("NOT SUCH A NODE\n");
return true;
}
else if(key == node->key)
return true;
else if(key < node->key)
selectNode(node->leftChild,key);
else if(key > node->key)
selectNode(node->rightChild,key);
}
删除
此处有必要注释一下:
BST中有以下几种节点,涉及到删除的方法不尽相同。
1.该节点有两个孩子
2.该节点只有一个左孩子
3.该节点只有一个右孩子
4.该节点没有孩子(叶子节点 or 没有孩子的root点)
如果该节点有两个孩子,那么我们习惯性的使用其右枝中的最小值对其进行替代。并递归的将该最小值删除。
引用参考资料中的原话:
删除节点有两个儿子:一般的删除策略是用其右子树的最小的数据代替该节点的数据,并递归地删除那个右子树的最小节点。
如图所示(图片来自参考资料中的《【数据结构】二叉查找树...》一篇):
/*
* 6 6 6
* / \ / \ / \
* 2 8 3 8 3 8
* / \ \ --> / \ \ --> / \ \
* 1 4 10 1 4 10 1 4 10
* / /
* 3 3
*/
如果该节点只有左孩子,则将值替换为其左孩子的值,并递归的将其左孩子删除。
该处代码如下:
if((*pnode)->leftChild && !(*pnode)->rightChild) //目标节点只有左儿子
{
tmpNode = (*pnode)->leftChild;
(*pnode)->key = tmpNode->key;
return deleteNode(&(*pnode)->leftChild, (*pnode)->key);//递归的删除该已变为枝点的叶子节点
}
如果该节点为叶子节点,则直接对其进行释放并将指向他的指针设为空。
附上该函数的完整代码如下:
//删除结点
_Bool deleteNode(BSTree ** pnode,int key)
{
BSTree *tmpNode = NULL;
if(NULL == pnode || NULL == *pnode) //指针无目标或树为空
return false;
//查找目标节点
else if(key < (*pnode)->key)
return deleteNode(&(*pnode)->leftChild, key);//&(*pnode)中的&使之重新构成二级指针
else if(key > (*pnode)->key)
return deleteNode(&(*pnode)->rightChild, key);
//已找到目标节点,即key = pnode->key
else
{
if((*pnode)->leftChild && (*pnode)->rightChild) //目标节点有两个儿子
{
tmpNode = findMin((*pnode)->rightChild);
(*pnode)->key = tmpNode->key;
return deleteNode(&(*pnode)->rightChild, (*pnode)->key);//递归的删除该已变为枝点的叶子节点
}
else if((*pnode)->leftChild) //目标节点只有左儿子
{
tmpNode = (*pnode)->leftChild;
(*pnode)->key = tmpNode->key;
return deleteNode(&(*pnode)->leftChild, (*pnode)->key);//递归的删除该已变为枝点的叶子节点
}
else if((*pnode)->rightChild) //目标节点只有右儿子
{
tmpNode = (*pnode)->rightChild;
(*pnode)->key = tmpNode->key;
return deleteNode(&(*pnode)->rightChild, (*pnode)->key);//递归的删除该已变为枝点的叶子节点
}
else //目标节点没有儿子,包括叶子节点以及没有儿子的root点
{
free(*pnode);
(*pnode) = NULL;
}
}
return true;
}
遍历
树的遍历有三种:先序、中序和后序。每次遍历子树时,也要相应的按序遍历该子树。
1. 先序遍历:[首先访问根节点] 先访问根节点,再遍历左子树,最后遍历右子树
2. 中序遍历:[中间访问根节点] 先遍历左子树,再访问根节点,最后遍历右子树
3. 后序遍历:[最后访问根节点] 先遍历左子树,再遍历右子树,最后访问根节点
1. 先序遍历:[首先访问根节点] 先访问根节点,再遍历左子树,最后遍历右子树
2. 中序遍历:[中间访问根节点] 先遍历左子树,再访问根节点,最后遍历右子树
3. 后序遍历:[最后访问根节点] 先遍历左子树,再遍历右子树,最后访问根节点
如图所示(图片来自参考资料中的《【数据结构】二叉查找树...》一篇):
/*
* 6 先序遍历: 6 2 1 4 3 8 10
* / \
* 2 8 中序遍历: 1 2 3 4 6 8 10
* / \ \
* 1 4 10 后序遍历: 1 3 4 2 10 8 6
* /
* 3
*/
先序遍历
//先序遍历
void preorder(BSTree *node)
{
if(node != NULL)
{
printf("%d ", node->key);
preorder(node->leftChild);
preorder(node->rightChild);
}
}
中序遍历
在二叉查找树中,中序遍历的结果恰好可以将该树中所有的元素进行升序输出
//中序遍历
void inorder(BSTree *node)
{
if(node != NULL)
{
inorder(node->leftChild);
printf("%d ", node->key);
inorder(node->rightChild);
}
}
后续遍历
//后序遍历
void postorder(BSTree *node)
{
if(node != NULL)
{
postorder(node->leftChild);
postorder(node->rightChild);
printf("%d ", node->key);
}
}
参考资料: