数据结构-二叉查找树(BST)

本文详细介绍了二叉查找树(BST)的基本概念、实现方式以及其特点,包括插入、删除、中序遍历操作的时间复杂度分析,并强调了保持树平衡的重要性。

   二叉查找树是一种比较特殊的二叉树,表现为任意节点的值都比左孩子的值要大,而且小于等于右孩子的值,采用中序遍历BST(Binary Search Tree)就可以的到排序好的元素集合,而且插入删除的时间消耗也比较合理,但是有一个缺点就是内存开销有点大,下面简单介绍BST。


下面就是两棵二叉查找树


BST实现主要采用递归来实现,所以在学习实现的过程中也可以加深对递归的理解和掌握。具体代码的意思在注释中均有明确的说明,下面看具体的实现


首先是定义一个节点的类Node,文件名为”Node.h“,由于存储的参数类型具体不了解,所以此处采用了模板的实现方法,实现了节点的一些常用操作。

#ifndef NODE_H
#define NODE_H

/*
 *二叉树节点Node,实现了一些简单地节点操作,
 *比如,获取左右子节点,修改左右子节点等,
 *含有三个参数
 *由于节点元素数据类型未知,故采用模板实现
 */

template<typename Elem>
class Node{
private:
	//节点的元素值
	Elem value;
	//节点的左指针
	Node * leftPtr;
	//节点的右指针
	Node * rightPtr;

public:
	Node(Elem e, Node * lp = NULL, Node * rp = NULL){
		value = e;
		leftPtr = lp;
		rightPtr = rp;
	}

	//判断是否为叶子节点
	bool isLeaf() const{
		return (leftPtr == NULL && rightPtr == NULL);
	}

	//获取节点的value值
	Elem getValue() const{
		return value;
	}

	//修改节点的value值
	void setValue(Elem e){
		value = e;
	}

	//获取左节点指针
	Node * getLeft() const{
		return leftPtr;
	}

	//设置左子节点
	void setLeft(Node * left){
		leftPtr = left;
	}

	//获取右节点指针
	Node * getRight() const{
		return rightPtr;
	}

	//设置右节点
	void setRight(Node * right){
		rightPtr = right;
	}
};
#endif

有了节点之后,我们就可以考虑具体的BST实现了


#ifndef BST_H
#define BST_H

#include<iostream>
#include<iomanip>
#include"Node.h"

using namespace std;

template<typename Elem>
class BST{

public:
	BST(){
		rootNode = NULL;
		nodeCount = 0;
	}

	~BST(){
		delete[] rootNode;
	}

	//插入一个元素e
	bool insert(const Elem& e){
		rootNode = insertHelp(rootNode, e);
		++nodeCount;
		return true;
	}

	//移除一个元素e
	bool remove(const Elem & e){
		//mark标记删除元素是否在BST中
		Node<Elem> * mark = NULL;
		rootNode = removeHelp(rootNode, e, mark);
		if (mark == NULL){
			cout << "Error: 所要删除的元素 " << e << " 不在树中!!!" << endl;
			return false;
		}
		else{
			nodeCount--;
			delete [] mark;
			return true;
		}
	}

	//中序遍历打印BST
	void show(){
		showHelper(rootNode);
		cout << endl;
	}

private:
	//指向整个BST的指针
	Node<Elem> * rootNode;
	//BST节点个数
	int nodeCount;

	//实现在bst中正确位置插入元素
	Node<Elem> * insertHelp(Node<Elem> * subroot, const Elem & e){
		//当递归到空位置时,即找到了合适的插入位置,
		//此时返回一个新节点作为上次递归调用subroot的孩子节点
		if (subroot == NULL){
			return new Node<Elem>(e);
		}
		//当前节点值大于要插入值时,说明插入位置在当前位置的左边,递归调用
		if (subroot->getValue() > e){
			subroot->setLeft(insertHelp(subroot->getLeft(), e));
		}
		//当前节点值小于要插入值时,说明插入位置在当前位置的右边,递归调用
		else{
			subroot->setRight(insertHelp(subroot->getRight(), e));
		}
		return subroot;
	}

	//删除最小值,返回删除后的树的根节点指针
	Node<Elem> * deleteMin(Node<Elem> * subroot, Node<Elem> * & min){
		//当左节点为空时,表示已经找到最小元素,此时把最小元素赋值min
		if (subroot->getLeft() == NULL){
			min = subroot;
			return subroot->getRight();
		}
		else{  //当左节点不是空节点的时候,递归调用
			subroot->setLeft(deleteMin(subroot->getLeft(), min));
			return subroot;
		}
	}


	//删除操作的帮助实现函数
	//当元素e不在树中是mark值为NULL,当e在树中时,mark值为e元素对应的节点指针
	Node<Elem> * removeHelp(Node<Elem> * subroot, const Elem e, Node<Elem> * & mark){
		//当subroot为NULL时表示找不到删除元素对应节点
		if (subroot == NULL){
			return NULL;
		}

		//当前元素值大于要删除的元素值,表示删除当前元素在元素的左边
		else if (subroot->getValue() > e){
			subroot->setLeft(removeHelp(subroot->getLeft(), e, mark));
		}

		//当前元素值小于要删除的元素值,表示删除当前元素在元素的右边
		else if (subroot->getValue() < e){
			subroot->setRight(removeHelp(subroot->getRight(), e, mark));
		}

		//找到删除元素对应节点
		else{
			//删除节点左孩子为空
			//标记mark,表示找到删除元素
			//直接让subroot指针指向subroot的右子节点
			if (subroot->getLeft() == NULL){
				mark = subroot;
				subroot = subroot->getRight();
			}
			//删除节点右孩子为空
			//标记mark,表示找到删除元素
			//直接让subroot指针指向subroot的左子节点
			else if (subroot->getRight() == NULL){
				mark = subroot;
				subroot = subroot->getLeft();
			}
			//删除两个子节点均不为空
			else{
				//删除subroot右子树最小节点temp,然后把subroot值赋为temp的值
				//标记mark,表示找到删除元素
				Node<Elem> * temp;
				subroot->setRight(deleteMin(subroot->getRight(), temp));
				Elem te = subroot->getValue();
				subroot->setValue(temp->getValue());
				temp->setValue(te);
				mark = temp;
			}
		}
		return subroot;
	}

	//中序遍历BST
	Node<Elem> * showHelper(Node<Elem> * subroot){
		if (subroot == NULL){
			return NULL;
		}
		showHelper(subroot->getLeft());
		cout << setw(6) << subroot->getValue();
		showHelper(subroot->getRight());
	}
	
};
#endif


上面的程序实现了BST基本的功能,下面就来简单总结一下BST的特点吧。

  在BST中,insert,remove,find操作的时间消耗平均都是(log n),但在最坏情况下都是(n),所以说保持树的平衡是很重要的,就是保持节点左右两边元素个数基本相同。




### 二叉查找树数据结构中的应用 二叉查找树(Binary Search Tree, BST)是一种重要的数据结构,其节点的值具有特定的顺序关系。对于每个节点,其左子树中所有节点的值均小于该节点的值,而右子树中所有节点的值均大于该节点的值[^1]。这种性质使得二叉查找树在插入、删除和查询操作上表现优异,尤其在动态数据集合中能够提供高效的性能。 #### 1. 插入操作 在二叉查找树中,插入新节点的过程是递归进行的。如果当前树为空,则直接创建一个新节点作为根节点;否则,根据新节点的值与当前节点的值比较,决定向左子树还是右子树继续插入。以下是插入操作的代码示例: ```c BiTreeNode* insertBiSortTree(BiTreeNode* root, int key) { if (!root) { root = (BiTreeNode*)malloc(sizeof(BiTreeNode)); root->data = key; root->left = NULL; root->right = NULL; } else { if (key > root->data) root->right = insertBiSortTree(root->right, key); else if (key < root->data) root->left = insertBiSortTree(root->left, key); } return root; } ``` 此代码片段展示了如何递归地将新节点插入到二叉查找树中[^2]。 #### 2. 创建二叉查找树 通过遍历一个无序数组并依次调用插入函数,可以构建一棵二叉查找树。以下代码展示了如何从数组中创建二叉查找树: ```c BiTreeNode* creatBiSortTree(int* arr, int n) { BiTreeNode* root = NULL; for (int i = 0; i < n; i++) { root = insertBiSortTree(root, arr[i]); } return root; } ``` 这段代码通过循环调用 `insertBiSortTree` 函数,逐步构建出完整的二叉查找树。 #### 3. 中序遍历 中序遍历是一种常见的树遍历方式,特别适用于二叉查找树。由于二叉查找树的性质,中序遍历的结果是一个升序排列的序列。以下是实现中序遍历的代码: ```c int* InOrder(BiTreeNode* root, int* arr, int &i) { if (!root) return NULL; InOrder(root->left, arr, i); arr[i++] = root->data; InOrder(root->right, arr, i); return arr; } ``` 此代码实现了对二叉查找树的中序遍历,并将结果存储到数组中[^3]。 #### 4. 判断数列是否升序 在某些场景下,需要验证中序遍历后的数列是否为升序。以下代码展示了如何判断一个数列是否为升序: ```c bool isOrder(int* arr, int n) { for (int j = 0; j < n - 1; j++) { if (arr[j + 1] < arr[j]) return false; } return true; } ``` 此函数通过遍历数组,逐一比较相邻元素来判断数列是否满足升序条件。 ### 总结 二叉查找树作为一种高效的数据结构,在动态数据集合的操作中表现出色。它支持快速的插入、删除和查询操作,并且通过中序遍历可以轻松获得有序序列。这些特性使其成为许多实际应用场景中的首选数据结构
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值