推免复习之数据结构与算法 二叉搜索树

二叉搜索树中有几个主要的操作,分别是插入元素,删除元素,查找元素。其中删除元素操作是属于稍微复杂一点的,因为在删除了节点之后,该节点左子树和右子树同样需要处理,而不是就直接粗暴地一起删除了,所以我会分三个部分讲解,主要以代码为主,辅以必要的文字说明。

首先要介绍这个二叉搜索树到底是什么,简单地说,按照左子树节点小于父节点,右子树中的节点大于父节点,递归构建的二叉树就是二叉搜索树。好处是能极大提高搜索效率,因为每次搜索都是根据大小关系从左子树和右子树中二选一,因此每经过一次搜索就几乎是将剩余待搜索范围缩小了一半。二叉搜索树如下图所示:

节点的定义:

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;
	}
}

总结:其实这个二叉搜索树还是有点难度的,光理解这个二叉搜索树可能挺容易,但是如果要编写代码现实还是比较麻烦,毕竟全是指针的操作,一不小心就会出错。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值