数据结构之二叉搜索树

前言:这个结构不难,但是删除的时候的逻辑思维难,建议这个博客配合着自己画图理解来实现你的二叉搜索树


二叉搜索树的概念

二叉查找树(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);
}

好了,我总结的二叉树的博客结束了。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值