二叉搜索树中有几个主要的操作,分别是插入元素,删除元素,查找元素。其中删除元素操作是属于稍微复杂一点的,因为在删除了节点之后,该节点左子树和右子树同样需要处理,而不是就直接粗暴地一起删除了,所以我会分三个部分讲解,主要以代码为主,辅以必要的文字说明。
首先要介绍这个二叉搜索树到底是什么,简单地说,按照左子树节点小于父节点,右子树中的节点大于父节点,递归构建的二叉树就是二叉搜索树。好处是能极大提高搜索效率,因为每次搜索都是根据大小关系从左子树和右子树中二选一,因此每经过一次搜索就几乎是将剩余待搜索范围缩小了一半。二叉搜索树如下图所示:
节点的定义:
class Node
{
public:
int data;
Node * leftChild;
Node * rightChild;
Node(int value , Node * left = NULL, Node * right = NULL)
{
data = value;
leftChild = left;
rightChild = right;
}
};
插入节点
插入节点操作其实思路很简单,就是一直查找,如果最后查找到的地方value值与想要插入的值相等,则不操作,因为二叉搜索树中一个键值最多只能出现一次,所以不能重复插入元素。如果查找到空值说明可以进行插入操作,建立一个新的节点插入到这个地址为空的位置上。
在这个地方,我对最后代码里的return root理解了好久,我刚开始觉得如果当前节点为空值,则肯定能把刚创建的节点地址返回到上一层,而地址已经赋完值的节点也不需要再重复再赋一遍地址,所以为什么要多此一举呢?hhh,说来惭愧,其实这个地方是我C++理解得不到家,因为insertNode函数是肯定会返回一个地址的,如果我不返回当前的地址,系统就会随机返回一个地址,也就是说,就算原本节点的地址是对的,当返回一个错误的地址后,节点的地址就变错的了。想要了解的可以试一下,当然我也做了实验,结果如下图所示:
把return root删去后地址的变化,上面的是原地址,下面的是递归进行后变化的地址,能看出来,地址都被改变了,所以这个二叉搜索树自然就出问题了。
保留return root后的地址变化,能看出来地址的值没有变化:
插入节点代码:
Node * insertNode(Node *root,int value) //插入节点
{
if (root == NULL)
{
return new Node(value);
}
else if(value < root->data)
{
root->leftChild = insertNode(root->leftChild,value);
}
else if (value > root->data)
{
root->rightChild = insertNode(root->rightChild, value);
}
return root;
//问:为什么要返回当前节点?
//答:因为要保持地址前后一致,不返回当前节点的话,
// 父节点的左子树或者右子树的地址会赋一个任意的地址
}
删除节点
删除节点操作有一部分思路是和查找节点操作一样的,那就是寻找节点的过程,如果查找不到自然就是不用删除了,如果查找到了这个节点,我们不能直接把这个节点粗暴地删去,因为这个节点可能还会有左子树或者右子树,如果直接删去这个节点的话,会导致把之后的左子树或者右子树也一起删去了,所以我们还需要一些额外的判断和操作。
首先判断左子树和右子树是否为空,如果都不为空则,则使用FindMin函数找到右子树的最小值节点,把值赋给原本要删除的节点,再把这个右子树的最小值节点删去,完成删除节点操作。
如果其中的左子树和右子树至少有一个为空,则直接把剩下的一个非空的子树的地址赋给当前节点。那如果恰好左子树和右子树都为空呢?那也是一样的,如果左子树为空,则按照思路就是把右子树的地址赋给当前节点,此时右子树地址也为空,那也就是把当前节点的地址置为空,同样能够达到删除当前节点的效果
删除节点代码:
Node* FindMin(Node *root) //查找最小节点
{
if (root == NULL)
{
return NULL;
}
else if (root->leftChild == NULL)
{
return root;
}
else
{
return FindMin(root->leftChild);
}
}
Node * deleteNode(Node *root, int value) //删除节点函数
{
Node * TP = NULL;
if (root == NULL)
{
return root;
}
if (value < root->data)
{
root->leftChild = deleteNode(root->leftChild,value);
}
else if (value > root->data)
{
root->rightChild = deleteNode(root->rightChild,value);
}
else
{
if (root->leftChild != NULL && root->rightChild != NULL) //要删除的节点左右节点都在
{
TP = FindMin(root->rightChild);
root->data = TP->data;
root->rightChild = deleteNode(root->rightChild,root->data);
}
else
{
TP = root;
if (root->leftChild == NULL)
{
root = root->rightChild;
}
else if (root->rightChild==NULL)
{
root = root->leftChild;
}
delete TP;
}
}
return root;
}
查找节点
查找节点就是简化版的插入节点,一直进行递归查找,如果要查找的value值比根节点小,则递归查找左子树,反之则查找右子树,具体看代码。
查找节点代码:
Node * searchNode(Node *root, int value) //根据键值,返回节点
{
if (root == NULL)
{
return NULL;
}
if (root->data > value)
{
return searchNode(root->leftChild,value); //在左子树中递归查找
}
else if (root->data < value)
{
return searchNode(root->rightChild, value); //在右子树中递归查找
}
else
{
return root;
}
}
总结:其实这个二叉搜索树还是有点难度的,光理解这个二叉搜索树可能挺容易,但是如果要编写代码现实还是比较麻烦,毕竟全是指针的操作,一不小心就会出错。