C++ 实现BSTree

时间:2024-03-24 20:20:39

目录

BSTree.h

框架

insert

insertR

find

findR

erase

eraseR

InOrder

拷贝构造

赋值重载 


BSTree.h

框架

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
};
template<class K>
class BSTree
{
	typedef BSTreeNode<K> node;
public:
private:
	node* _root = nullptr;
};

insert

1.在空位置插入,所以就要记录父节点

2.第一次插入时候要判断(_root == nullptr) 因为第一个节点没有父亲,我通过缺省参数初始化_root

3. node* parent = cur; parent初始化什么都可以,分析:与_root相同,则return,不同就延续cur

4.进行链接新节点,上面的while只是找到新节点在的父亲,parent与new node之前只是父子关系,左右孩子还关系需要比较_key的值

5.返回的值是bool,因为BSTree的结构和插入顺序有关

bool insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new node(key);
		return true;
	}
	node* cur = _root;
	node* parent = cur;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	if (parent->_key < key)
		parent->_right = new node(key);
	else
		parent->_left = new node(key);
	return true;
}

insertR

通过循环来插入

1.通过 & 就可以直接与父节点链接上,分析:上一个栈帧里root的指向,当前的root的地址,执行到nullptr时候,root所存放的值就是new 出来节点的地址,这样就链接上了

2.仍然是在nullptr的位置插入、

3.对于返回值(函数出口)来说,两个地方:new出来了就是true,相等就是false,其他递归均是过程

4. _insertR(root->_right, key);仔细想了想,这个地方也是返回,不然最后返回的时候没有返回值啊,但是也不报错,也没有警告(vs编译器会自动处理函数的返回值 ,不是像oj上那样 每个返回路径都需要严格加return)但还是加上有味道(因为这是不同的栈帧,不是循环)

bool insertR(const K& key)
{
	return _insertR(_root, key);
}
bool _insertR(node*& root, const K& key)
{
	if (root == nullptr)
	{
		root = new node(key);
		return true;
	}
	if (root->_key < key)
	{
		return _insertR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _insertR(root->_left, key);
	}
	else
	{
		return false;
	}
}

find

bool find(const K& key)
{
	node* cur = _root;
	while (cur)
	{
		if (cur->_key == key)
		{
			return true;
		}
		else if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
	}
	return false;
}

findR

1.返回的是能否找到,即bool

2.函数出口nullptr 返回false,相等返回 true

3.&可以有也可以没有,其一:这里的值拷贝只是一个指针,其二:不需要有链接关系

bool findR(const K& key)
{
	return _findR(_root, key);
}
bool _findR(node* root, const K& key)//不起到链接作用,但引用也是可以的
{
	if (root == nullptr) return false;
	if (root->_key < key)
	{
		return _findR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _findR(root->_left, key);
	}
	else
	{
		return true;
	}
}

erase

分析:

1.删3,3的左为空,就需要将3的右给5的左(叶子节点也算在在里面)

2.删18,18的右为空,就需要将18的左给15的右(叶子节点也算在里面)

3.删5,那么就需要有值来代替5,符合条件的是左树的最大节点,或右树的最小节点,随之转换删除的是4或7,而不是5了

4.在右边这种情况,当然也可以选择,与9交换,然后删9,但是直接将_root转换为5更舒服

代码注意事项

1.对于1和2两种删除的情况,因他们可以有儿子,所以需要借助父亲,链接上他们的儿子

2.如果只有一个节点,在清除语句的第一个if,会将_root置空

3.找到的待删除的值,与父亲只有链接关系,链接到左还是右,还需要通过指针进一步判断

4.pmaxleft需要初始化成当cur(不能为nullptr),因为maxleft = pmaxleft -> _left,如果,maxleft没有右树,pmaxleft为空,那么maxleft就找不到父亲,在解引用的时候就会报错,这种情况也正好是pmaxleft->_left == maxleft,其他情况都是右树(也就是while进去了)

5.当左右都不为空的时候 del 需要改成maxleft,因为只是交换了值,cur还在原来位置,不然下面的节点就全丢了

bool erase(const K& key)
{
	node* cur = _root;
	node* parent = cur;
	while (cur)
	{
		if (cur == nullptr) return false;
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			break;   //等于准备删除
		}
	}
	if (cur == nullptr) return false;// 空就是没找到
	node* del = cur;
	if (cur->_left == nullptr)
	{
		if (cur == _root)
		{
			_root = cur->_right;
		}
		if (parent->_left == cur)
		{
			parent->_left = cur->_right;
		}
		else
		{
			parent->_right = cur->_right;
		}
	}
	else if (cur->_right == nullptr)
	{
		if (cur == _root)
		{
			_root = cur->_left;
		}
		if (parent->_left == cur)
		{
			parent->_left = cur->_left;//这个地方  ;  没写,报编译器内部错误
		}
		else
		{
			parent->_right = cur->_left;
		}
	}
	else
	{
		node* pmaxleft = cur;
		node* maxleft = cur->_left;
		while (maxleft->_left)
		{
			pmaxleft = maxleft;
			maxleft = maxleft->_right;
		}
		cur->_key = maxleft->_key;
		if (pmaxleft->_left == maxleft)
		{
			pmaxleft->_left = maxleft->_right;
		}
		else
		{
			pmaxleft->_right = maxleft->_right;
		}
		del = maxleft;//因为只是换的节点的值
	}
	delete del;
	return true;
}

eraseR

1.通过循环解决,删除操作本身就需要链接,&很合适

2.如果为nullptr就是没有对应的值,返回false

3.仔细分析下,是怎么删3的,和怎么链接的

这是我第一次画的,其实这个root写在这个位置,并不是很好

应该是这样:在引用传参的时候传的就是5节点里_left,再次进入一个栈帧root->_right就是节点4的地址(需要沉淀一下,从调试的结果是这样的)对于&root自然是在节点10里,因为10->_left存放的是5节点的地址

bool eraseR(const K& key)
{
	return _eraseR(_root, key);
}
bool _eraseR(node*& root, const K& key)
{
	if (root == nullptr) return false;
	if (root->_key < key)//找的过程
	{
		_eraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		_eraseR(root->_left, key);
	}
	else//找到
	{
		node* del = root;
		if (root->_left == nullptr)
		{
			root = root->_right;//赋值重载,就是要只拷贝
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			node* pminright = root;
			node* minright = root->_right;
			while (minright->_left)
			{
				pminright = minright;
				minright = minright->_left;
			}
			swap(minright->_key, root->_key);
			return _eraseR(root->_right, key);
		}
		delete del;
		return true;
	}
}

InOrder

中序遍历

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
void _InOrder(node* root)
{
	if (root == nullptr) return;

	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

拷贝构造

copy例不能把tmp换成_root,不然每一次递归_root都会被覆盖,链接不上,而tmp每次都是一个新的

问题:t是const,那么t._root也是const,为什么copy里的t不需要const修饰

node*可以接收node*const,而且下面的copy里的const怎么加都不影响

比如:node* test = t._root;这是被允许的,这只是一个赋值;node*& test = t._root;这样就不行,关键因为我只是用了他的值,它加了const只是不能修改它,但是可以去他的值

BSTree(const BSTree<K>& t)
{
	_root = copy(t._root);
}
node* copy(node* t)
{
	if (t == nullptr) return nullptr;
	node* tmp = new node(t->_key);
	tmp->_left = copy(t->_left);
	tmp->_right = copy(t->_right);
	return tmp;
}

赋值重载 

我遇到的问题:这个改正了我之前错误的理解:我之前认为这里的t是不会被析构的,因为他是在堆上的,在栈上出了作用域会自己弹掉;但是t(t是拷贝构造出来的)出了这个函数作用域就被应该销毁(这是我在调试的时候看到的),而且堆不就是要被析构吗,

问题就在swap上:swap没有吧t._root换掉就会被析构掉,导致_root也没了,所以要把t._root置空

//①用不用std里的会用自己写的,就会无限递归
//②void swap(node*& t)和void swap(node* t)会调用不明确
void swap(node*& t)// 因为这里的t会被析构掉,它已经出了它的函数作用域
{//		这也是为什么刚进入析构_root就是nullptr,所以这个t必须是&传参,要换掉这个t
	//swap(_root, t);
	std::swap(_root, t);
}
void swap(node** t)//和swap(&t._root)对应
{
	std::swap(_root, *t);
}
//void swap(node* t)//和swap(t._root)对应,传拷贝的也不是不可以,出去之后要把t._root改成nullptr☆
//{
//	std::swap(_root, t);
//}
//BSTree<K>& operator=(const BSTree<K> t)//swap掉不了
BSTree<K>& operator=(BSTree<K> t)//
{
	//swap(&t._root);//就是要改掉原来的t ->void swap(node** t)
	swap(t._root);
	t._root = nullptr;
	//swap(_root, t._root);//可以
	return *this;
}