前言:这个结构不难,但是删除的时候的逻辑思维难,建议这个博客配合着自己画图理解来实现你的二叉搜索树
二叉搜索树的概念
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
两句话:左节点比根节点的关键值小,右节点比根节点的关键值大
我决定用二元组来当作数据,一个是键值,一个是数据(我用字符串来表示)
我们要实现的是二叉搜索树的基本功能:打印(也就是普通的中序遍历)插入(insert)删除(erase)
好,我们开始动手吧:
首先是我的数据的结构体部分:
typedef struct pair {
int first;
char* second;
}PA,*LPPA;
然后是二叉树的节点:
typedef struct node {
LPPA data;
struct node* lchild;
struct node* rchild;
}NODE,*LPNODE;
然后是二叉搜索树封装的结构体:
typedef struct serachtree {
LPNODE rootnode;
int size;
}STREE,*LPSTREE;
然后是创建节点:
LPNODE createnode(int first, char* second) {
LPNODE newnode = (LPNODE)malloc(sizeof(NODE));
LPPA newdata = (LPPA)malloc(sizeof(PA));
newdata->first = first;
newdata->second = second;
newnode->data = newdata;
newnode->lchild = NULL;
newnode->rchild = NULL;
return newnode;
}
创建二叉搜索树:
LPSTREE createtree() {
LPSTREE newtree = (LPSTREE)malloc(sizeof(STREE));
newtree->rootnode = NULL;
newtree->size = 0;
return newtree;
}
这些看过我的数据结构专栏的朋友都知道,或者神犇,都是基本操作了,我也不过多解释了
然后是万金油函数两个:
int size(LPSTREE pstree) {
return pstree->size;
}
int isempty(LPSTREE pstree) {
return pstree->size == 0;
}
好了,基本工作我们完毕了。
接下来是二叉搜索树的插入:
插入很简单,让插入的数据的键值逐一比较,按照二叉搜索树的性质,键值大的往右走,键值小的往左走,这里我们遇到第一个问题,如果等于怎么办?这个问题好啊,这个看你自己处理,我用的方法是替换,你也可以让他插入在左节点上,让他走右边。但是这个必须处理。
那么我们插入的时候是不是要找合适的位置。所以我们需要移动的指针来寻找这个位置,既然要插入,我们肯定要有他的父节点才能实现插入,所以我们需要两个指针去寻找这个位置并插入。
所以原理很简单,我来上代码和注释:
void insertnode(LPSTREE pstree, int first, char* second) {
LPNODE newnode = createnode(first, second);//创建要插入的节点
LPNODE pmove = pstree->rootnode;//寻找合适位置的指针
LPNODE pmovedad = NULL;//合适位置的父节点
while (pmove!=NULL) {//不到底,一直找
pmovedad = pmove;
if (first < pmove->data->first) {
pmove = pmove->lchild;//如果键值小于,那么往左子树走
}
else if (first > pmove->data->first) {
pmove = pmove->rchild;//如果键值大于,那么往右子树走
}
else {
pmove->data->second = second;//如果等于的情况
return;
}
}
if (pstree->rootnode == NULL) {
pstree->rootnode = newnode;
}//也就是空树的情况
else {//不空树
if (pmovedad->data->first > first) {
pmovedad->lchild = newnode;
}//找到了要插入的节点,与其父节点比较,按照性质来插入
else {
pmovedad->rchild = newnode;
}
}
pstree->size++;//大小增加
}
插入的工作完成了,我们来检验一下,那么我们就需要用到二叉搜索树的打印了,一般我们采用中序遍历,为什么呢?
因为 左<根<右。这样是不是中序遍历的话就是刚好排好序,真如其名,二叉排序树。
来吧也就是中序遍历,我们用递归写一下:
void printnodedata(LPNODE node) {
printf("%d,%s\n", node->data->first, node->data->second);//打印二元组的数据
}
void midprint(LPNODE pstreenode) {
if (pstreenode != NULL) {
midprint(pstreenode->lchild);
printnodedata(pstreenode);
midprint(pstreenode->rchild);
}
}
测试:
int main() {
LPSTREE my = createtree();
PA data1[9] = {
50,(char*)"wuw",
10,(char*)"w9",
80,(char*)"yyx",
990,(char*)"wxm",
10,(char*)"ll",
5,(char*)"nn",
70,(char*)"99",
20,(char*)"ss",
1,(char*)"qq",
};
for (int i = 0; i < 9; i++) {
insertnode(my, data1[i].first, data1[i].second);
}
midprint(my->rootnode);
return 0;
}
如果看到结果是正好排序着下来的,那么就是正确的。
最关键的删除来了,这里的逻辑有点复杂
删除一个节点,为了保持,该二叉搜索树的性质,我们需要对其进行调整位置。
那么我们如何调整呢。首先我们删除一个节点,它的孩子节点是不是需要往上移动,或者找个节点来替代这个节点?
很显然,找个节点来替代这个节点是非常适合的。也是我们经常做的
我们如何找到这个节点,这里我们就用到了二叉搜索树的性质,左子树一定比右子树小,那么该节点的左节点就是小于根节点的数值,该节点的左节点的子节点一定不会大于该节点,
那么就是:该节点的左节点的左节点<该节点
该节点<该节点的左节点的右节点<该节点的左节点
举个例子吧,好抽象的感觉。
如下图:
就拿15那个节点为例,它的父节点是10,那么它的子节点如果是9,肯定就不是往这里插,肯定是大于了10,才往这条路走。15的左节点又小于15,那么它的范围是10<x<15。以此类推,下面的范围与1相比越来精确,也就是最左边的子节点是刚好能替代10的位置>10&&<15,这样我们可以移除10这个节点了。这个是我们最常采用的在右子树找最左边的节点的方法。
相对的,就是在左子树找最右边的节点的方法。原理如上一样,可以自己推理一下。
好了,这个逻辑我们完成了。我们就开始代码实现:
我先理一下删除的大致步骤:
首先找到要删除的节点,
然后是找到要替换的替身节点,
然后是以替身节点来创建新节点,并代替原节点位置,
这里应该还要判断要删除的节点是其父节点的左节点还是右节点(所以我们需要两个移动指针)
删除原节点,还有替身节点的原来位置停留的节点
好了,大致是这样子,具体看代码和我的注释:
void erase(LPSTREE tree, int first) {
//首先查找要删除的节点的位置
LPNODE pmove = tree->rootnode;
LPNODE pmovedad = NULL;
//利用查找树的特性
while (pmove != NULL && pmove->data->first != first) {//前面加的pmove!=null 是造成短路,排除空树情况来避免出现bug
pmovedad = pmove;
if (first < pmove->data->first) {
pmove = pmove->lchild;
}
else if(first >pmove->data->first){
pmove = pmove->rchild;
}
else {
break;//找到了
}
}
if (pmove == NULL) {
printf("没有找到指定位置,无法删除\n");
return;
}
//左右都有节点,找左子树最右边的数。我没讲的那个,可以来理解了,哈哈
if (pmove->lchild != NULL && pmove->rchild != NULL) {
LPNODE movenode = pmove->lchild;
LPNODE movenodedad = pmove;//利用两个指针来查找,记住,二叉树都要带爹带娘,不要忘本,我们是孝子,为什么下面写的代码必须需要
while (movenode->rchild!=NULL) {
movenodedad = movenode;
movenode == movenode->rchild;
}//找到最右边的节点
LPNODE newnode = createnode(movenode->data->first, movenode->data->second);
newnode->lchild = pmove->lchild;
newnode->rchild = pmove->rchild;//创建出新的节点准备替代
if (movenodedad == NULL) {
tree->rootnode = newnode;//考虑到删除的节点是根节点的情况
}
//下面是判断这个删除的节点是它的根节点的左节点还是右节点,来让我们的指针好指向它,来建立联系替代它
else if (pmove == pmovedad->lchild) {
pmovedad->lchild = newnode;
}
else {
pmovedad->rchild = newnode;
}
//调整删除的地方
if (movenodedad == pmove) {
pmovedad = newnode;
}
else {
pmovedad = movenodedad;
}
free(pmove);//释放要删除的节点的内存
pmove = movenode;
}
LPNODE snode = NULL;
//如果删除节点左右有节点,把他们存下来
//左边有节点
if (pmove->lchild != NULL) {
snode = pmove->lchild;
}
//右边有节点
else {
snode = pmove->rchild;
}
if (tree->rootnode == pmove) {
tree->rootnode = snode;
}
else {
//让其父节点接管它的孩子
if (pmove == pmovedad->lchild) {
pmovedad->lchild = snode;
}
else {
pmovedad->rchild = snode;
}
}
free(pmove);
tree->size--;
}
好了,删除完成了,我们的工作也完成了
害,多画画图吧,的确不好理解的。
下面是我的整体程序,带上我的测试:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct pair {
int first;
char* second;
}PA,*LPPA;
typedef struct node {
LPPA data;
struct node* lchild;
struct node* rchild;
}NODE,*LPNODE;
typedef struct serachtree {
LPNODE rootnode;
int size;
}STREE,*LPSTREE;
LPNODE createnode(int first, char* second) {
LPNODE newnode = (LPNODE)malloc(sizeof(NODE));
LPPA newdata = (LPPA)malloc(sizeof(PA));
newdata->first = first;
newdata->second = second;
newnode->data = newdata;
newnode->lchild = NULL;
newnode->rchild = NULL;
return newnode;
}
LPSTREE createtree() {
LPSTREE newtree = (LPSTREE)malloc(sizeof(STREE));
newtree->rootnode = NULL;
newtree->size = 0;
return newtree;
}
int size(LPSTREE pstree) {
return pstree->size;
}
int isempty(LPSTREE pstree) {
return pstree->size == 0;
}
void printnodedata(LPNODE node) {
printf("%d,%s\n", node->data->first, node->data->second);
}
void midprint(LPNODE pstreenode) {
if (pstreenode != NULL) {
midprint(pstreenode->lchild);
printnodedata(pstreenode);
midprint(pstreenode->rchild);
}
}
void insertnode(LPSTREE pstree, int first, char* second) {
LPNODE newnode = createnode(first, second);
LPNODE pmove = pstree->rootnode;
LPNODE pmovedad = NULL;
while (pmove!=NULL) {
pmovedad = pmove;
if (first < pmove->data->first) {
pmove = pmove->lchild;
}
else if (first > pmove->data->first) {
pmove = pmove->rchild;
}
else {
pmove->data->second = second;
return;
}
}
if (pstree->rootnode == NULL) {
pstree->rootnode = newnode;
}
else {
if (pmovedad->data->first > first) {
pmovedad->lchild = newnode;
}
else {
pmovedad->rchild = newnode;
}
}
pstree->size++;
}
LPNODE serchstree(LPSTREE pstree, int first) {
LPNODE pmove = pstree->rootnode;
if (pmove == NULL) {
return pmove;
}
else {
while (pmove->data->first != first) {
if (first > pmove->data->first) {
pmove = pmove->rchild;
}
else {
pmove = pmove->lchild;
}
if (pmove == NULL) {
return pmove;
}
}
return pmove;
}
}
void erase(LPSTREE tree, int first) {
LPNODE pmove = tree->rootnode;
LPNODE pmovedad = NULL;
while (pmove != NULL && pmove->data->first != first) {
pmovedad = pmove;
if (first < pmove->data->first) {
pmove = pmove->lchild;
}
else if(first >pmove->data->first){
pmove = pmove->rchild;
}
else {
break;
}
}
if (pmove == NULL) {
printf("没有找到指定位置,无法删除\n");
return;
}
//左右都有节点,找左子树最右边的数
if (pmove->lchild != NULL && pmove->rchild != NULL) {
LPNODE movenode = pmove->lchild;
LPNODE movenodedad = pmove;
while (movenode->rchild!=NULL) {
movenodedad = movenode;
movenode == movenode->rchild;
}
LPNODE newnode = createnode(movenode->data->first, movenode->data->second);
newnode->lchild = pmove->lchild;
newnode->rchild = pmove->rchild;
if (movenodedad == NULL) {
tree->rootnode = newnode;
}
else if (pmove == pmovedad->lchild) {
pmovedad->lchild = newnode;
}
else {
pmovedad->rchild = newnode;
}
//调整删除的地方
if (movenodedad == pmove) {
pmovedad = newnode;
}
else {
pmovedad = movenodedad;
}
free(pmove);
pmove = movenode;
}
LPNODE snode = NULL;
//如果删除节点左右有节点,把他们存下来
//左边有节点
if (pmove->lchild != NULL) {
snode = pmove->lchild;
}
//右边有节点
else {
snode = pmove->rchild;
}
if (tree->rootnode == pmove) {
tree->rootnode = snode;
}
else {
if (pmove == pmovedad->lchild) {
pmovedad->lchild = snode;
}
else {
pmovedad->rchild = snode;
}
}
free(pmove);
tree->size--;
}
int main() {
LPSTREE my = createtree();
PA data1[9] = {
50,(char*)"wuw",
10,(char*)"w9",
80,(char*)"yyx",
990,(char*)"wxm",
10,(char*)"ll",
5,(char*)"nn",
70,(char*)"99",
20,(char*)"ss",
1,(char*)"qq",
};
for (int i = 0; i < 9; i++) {
insertnode(my, data1[i].first, data1[i].second);
}
midprint(my->rootnode);
LPNODE zhe = serchstree(my,5);
if (zhe == NULL) printf("null");
else printf("%d,%s\n", zhe->data->first, zhe->data->second);
erase(my, 10);
midprint(my->rootnode);
}
好了,我总结的二叉树的博客结束了。