Day7:有序二叉树(二叉搜索树)

目录

一、有序二叉树基础

        1.    有序的含义:

        2.每棵二叉搜索树的子树也是一棵二叉搜索树​编辑

        3.二叉搜索树的特性

二、常用操作:

        1.构建基本类型

        2.查找结点findNode

        3.遍历二叉树(中序->有序)

        4.插入节点

        5.删除节点

        6.释放各个节点

三、存放自定义类型的二叉搜索树(完整代码

一、有序二叉树基础

        1.    有序的含义:

                升序    :
            左 < 根 < 右  
                降序    :
            左 > 根 > 右      

        2.每棵二叉搜索树的子树也是一棵二叉搜索树

        3.二叉搜索树的特性

     中序遍历是一个有序序列,是从小到大排序的,2-3-4-7-8-10-12-18

二、常用操作:

        1.构建基本类型

        2.查找结点findNode

根据二叉搜索树的有序的性质,可以利用二分查找的方式->找到(此处采用迭代方式实现)

//功能五:查找指定节点
template<class T>
Node<T>* Tree<T>::findNode(const T& data)
{
	Node<T>* pTemp = pRoot;
	while (pTemp)
	{
		if (data < pTemp->data)
		{
			pTemp = pTemp->pLeft;
		}
		else if (data > pTemp->data)
		{
			pTemp = pTemp->pRight;
		}
		else
			return pTemp;
	}
	return nullptr;
}

        3.遍历二叉树(中序->有序)

根据type的值,然后递归(调整位置即可),实现遍历

//功能四:遍历
template<class T>
void Tree<T>::travel(int type)
{
	switch (type)
	{
	case -1:
		_preTravel(pRoot); break;
	case 0:
		_midTravel(pRoot); break;
	case 1:
		_behTravel(pRoot); break;
	}
	cout << endl;
}
template<class T>
void Tree<T>::_preTravel(Node<T>*p)
{
	if (p == nullptr)return;
	cout << p->data<<" ";
	_preTravel(p->pLeft);
	_preTravel(p->pRight);
}
template<class T>
void Tree<T>::_midTravel(Node<T>* p)
{
	if (p == nullptr)return;
	_midTravel(p->pLeft);
	cout << p->data<<" ";
	_midTravel(p->pRight);
}
template<class T>
void Tree<T>::_behTravel(Node<T>* p)
{
	if (p == nullptr)return;
	_behTravel(p->pLeft);
	_behTravel(p->pRight);
	cout << p->data<<" ";
}

        4.插入节点

在左根右的情况下,需要不断递归,找到合适的插入位置

注意:递归的终止条件,子根为nullptr,然后创建新的节点,然后赋值。

//功能一:插入节点
template <class T>
void Tree<T>::_insert(Node<T>*&pRoot,const T& data)
{//插入的顺序:左< 根 <右
	if (pRoot == nullptr)
	{
		Node<T>* pNew = new Node<T>(data);
		pRoot = pNew;
		return;//   7.23别忘记return
	}
	if (pRoot->data > data) { _insert(pRoot->pLeft, data); }
	else { _insert(pRoot->pRight, data); }
}

        5.删除节点

/*Args:pDel待删除节点 pRoot树的根节点  pDelParent待删节点的父节点*/

情况一:findNode没找到该节点->return

情况二:需要删除的是根节点->(统一规则)

        (i)若如果pDel有右孩子,循环遍历出右孩子的最小左孩子,将pDel的左孩子(就算没有为nullptr也是可行的)给其最小左孩子领养->释放pDel

        (ii)若pDel没有右孩子,让左孩子直接成为新的pRoot->释放pDel

情况三:需要删除的是子树中的结点

                ->重写二分查找,定位pDel和pDelParent(相当于pre指针)位置

        (i)若pDel在parent的左孩子

                (a)若pDel有右孩子->寻找其pDel右孩子的最小左孩子

                          (方便pDel的左孩子被其领养)->让父节点领养pDel的右孩子

                                (此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)

                (b)若pDel无右孩子->让父亲节点直接领养pDel的左孩子

        (ii)pDel是parent的右孩子

                (a)若pDel有右孩子->寻找其pDel右孩子的最小左孩子

                                (方便pDel的左孩子被其领养)

                                ->让父节点领养pDel的右孩子

                                (此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)

                (b)若pDel无右孩子->让父亲节点直接领养pDel的左孩子

//功能三:删除指定节点
template<class T>
void Tree <T>::_deleteNode(const T & data)
{
	Node<T>* pDel = findNode(data);
	//情况一:没找到
	if (pDel == nullptr)
	{
		cout << "未找到,插入失败" << endl;
	}
	//情况二:找到的是根节点
	if (pDel == pRoot)
	{
		if (pDel->pRight)//若根节点有右孩子->寻找pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
		{
			Node<T>* pMove = pDel->pRight;
			while(pMove->pLeft)
			{
				pMove = pMove->pLeft;
			}//找到最小左孩子
			pMove->pLeft = pDel->pLeft;//领养
			pRoot = pDel->pRight;//让右孩子成为新的根节点
		}
		else//根节点无右孩子->让根节点的左孩子成为新的根节点
		{
			pRoot = pDel->pLeft;
		}
		delete pDel;//处理掉原先的旧根节点
		pDel = nullptr;
	}//情况三:找到的是子树的结点
	else
	{//优先先将pDel和其父节点pDelParent找出来(必定存在)->二分
		Node<T>* pDelParent = pRoot;
		while (pDelParent->pLeft != pDel && pDelParent->pRight != pDel)//找到pDel退出
		{
			if (pDelParent->data < data)pDelParent = pDelParent->pRight;
			else if (pDelParent->data > data)pDelParent = pDelParent->pLeft;
		}//跳出循环后,此时的parent指针指向pdel
		//(i)若pDel在parent的左孩子
		if (pDel == pDelParent->pLeft)
		{
			if (pDel->pRight)//若pDel有右孩子->寻找其pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
			{
				Node<T>* pMove = pDel->pRight;
				while (pMove->pLeft)
				{
					pMove = pMove->pLeft;
				}//找到最小左孩子
				pMove->pLeft = pDel->pLeft;//领养pDel的左孩子
				pDelParent->pLeft = pDel->pRight;//让父节点领养pDel的右孩子(此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)
			}
			else//pDel无右孩子->让父亲节点直接领养pDel的左孩子
			{
				pDelParent->pLeft = pDel->pLeft;
			}
			delete pDel;//处理掉待删除节点
			pDel = nullptr;
		}
		else//(ii)pDel是parent的右孩子
		{
			if (pDel->pLeft)//若pDel有右孩子->寻找其pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
			{
				Node<T>* pMove = pDel->pRight;
				while (pMove->pLeft)
				{
					pMove = pMove->pLeft;
				}//找到最小左孩子
				pMove->pLeft = pDel->pLeft;//领养pDel的左孩子
				pDelParent->pRight = pDel->pRight;//让父节点领养pDel的右孩子(此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)
			}
			else//pDel无右孩子->让父亲节点直接领养pDel的左孩子
			{
				pDelParent->pRight = pDel->pLeft;
			}
			delete pDel;//处理掉待删除节点
			pDel = nullptr;
		}
	}
}

有关此方法为何能保证删除之后还能保证有序的一些思考: 

        基本核心操作待删节点的思路->看待删节点是否有右孩子,有,就将pDel的左孩子归位?归哪?左<根<右 显然左孩子要被右孩子领养,还要保证二叉搜索树的结构的话,右边都是大于左边的,所以肯定要找到右孩子的最小左孩子,才是pDel的左孩子的归宿。若没有右孩子,直接移动即可。

        6.释放各个节点

利用二叉树的递归特性,逐一递归释放即可。

//功能二:释放各个节点
template<class T>
void Tree<T>::_clear(Node<T>*pDel)
{
	if (pDel == nullptr)return;
	_clear(pDel->pLeft);
	_clear(pDel->pRight);
	delete pDel;
	pDel = nullptr;
}

三、存放自定义类型的二叉搜索树(完整代码)

bug:(以及重载时,const 的处理!!!)C++报错:友元重载<<无法输出const char*类型&&一堆string中的未定义&&模板未用T表示(内部直接int....)_https://blog.csdn.net/zjjaibc/article/details/125955646?spm=1001.2014.3001.5501

Stu.h

#pragma once
#include<string>
#include<iostream>
using namespace std;
class Stu
{
public:
	Stu(int id = -1, std::string name = (string)"default"):id(id),name(name){}
	friend ostream& operator<<(ostream& out, const Stu& s);
	bool operator>( Stu& t);
private:
	int id;
	string name;
};

Stu.cpp

#include"Stu.h"
#include<string>
std::ostream& operator<<(ostream& out, const Stu& s)
{
	out << string("学号:") << s.id <<string(" 姓名:") << s.name<<"  ";
	return out;
}
bool Stu::operator>( Stu& t)
{
	return id>t.id;
}

Tree.h

#pragma once
#pragma once
#include<iostream>
#include<string>
using namespace std;
class Stu;
template <class T>
struct Node
{
	T data;
	Node* pLeft;
	Node* pRight;
	Node(const T& data)
	{
		this->data = data;
		pLeft = pRight = nullptr;
	}
};
template <class T>
class Tree
{
public:
	Tree() { pRoot = nullptr; }
	~Tree() { clear(pRoot); }
	Node<T>* findNode( T& data);
	//释放结点
	void clear(Node<T>* pDel) { _clear(pDel); }
	//插入节点
	void insert( T& data) { _insert(pRoot, data); }
	//删除节点
	void deleteNode( T& data) { _deleteNode(data); }
	//遍历
	void travel(int type = 0);//-1表示前序遍历,默认中序遍历,1表示后序遍历
private:
	void _clear(Node<T>* pDel);
	void _insert(Node<T>*& pRoot, T& data);
	void _deleteNode( T& data);
private:
	void _preTravel(Node<T>* p);//先序
	void _midTravel(Node<T>* p);//中序遍历
	void _behTravel(Node<T>* p);//后序遍历
private:
	Node<T>* pRoot;
};

//功能一:插入节点
template <class T>
void Tree<T>::_insert(Node<T>*& pRoot, T& data)
{//插入的顺序:左< 根 <右
	if (pRoot == nullptr)
	{
		Node<T>* pNew = new Node<T>(data);
		pRoot = pNew;
		return;//   7.23别忘记return
	}
	if (pRoot->data > data) { _insert(pRoot->pLeft, data); }
	else { _insert(pRoot->pRight, data); }
}
//功能二:释放各个节点
template<class T>
void Tree<T>::_clear(Node<T>* pDel)
{
	if (pDel == nullptr)return;
	_clear(pDel->pLeft);
	_clear(pDel->pRight);
	delete pDel;
	pDel = nullptr;
}
//功能三:删除指定节点
template<class T>
void Tree <T>::_deleteNode( T& data)
{
	Node<T>* pDel = findNode(data);
	//情况一:没找到
	if (pDel == nullptr)
	{
		cout << "未找到,插入失败" << endl;
	}
	//情况二:找到的是根节点
	if (pDel == pRoot)
	{
		if (pDel->pRight)//若根节点有右孩子->寻找pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
		{
			Node<T>* pMove = pDel->pRight;
			while (pMove->pLeft)
			{
				pMove = pMove->pLeft;
			}//找到最小左孩子
			pMove->pLeft = pDel->pLeft;//领养
			pRoot = pDel->pRight;//让右孩子成为新的根节点
		}
		else//根节点无右孩子->让根节点的左孩子成为新的根节点
		{
			pRoot = pDel->pLeft;
		}
		delete pDel;//处理掉原先的旧根节点
		pDel = nullptr;
	}//情况三:找到的是子树的结点
	else
	{//优先先将pDel和其父节点pDelParent找出来(必定存在)->二分
		Node<T>* pDelParent = pRoot;
		while (pDelParent->pLeft != pDel && pDelParent->pRight != pDel)//找到pDel退出
		{
			if (data > pDelParent->data)pDelParent = pDelParent->pRight;
			else if (pDelParent->data > data)pDelParent = pDelParent->pLeft;
		}//跳出循环后,此时的parent指针指向pdel
		//(i)若pDel在parent的左孩子
		if (pDel == pDelParent->pLeft)
		{
			if (pDel->pRight)//若pDel有右孩子->寻找其pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
			{
				Node<T>* pMove = pDel->pRight;
				while (pMove->pLeft)
				{
					pMove = pMove->pLeft;
				}//找到最小左孩子
				pMove->pLeft = pDel->pLeft;//领养pDel的左孩子
				pDelParent->pLeft = pDel->pRight;//让父节点领养pDel的右孩子(此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)
			}
			else//pDel无右孩子->让父亲节点直接领养pDel的左孩子
			{
				pDelParent->pLeft = pDel->pLeft;
			}
			delete pDel;//处理掉待删除节点
			pDel = nullptr;
		}
		else//(ii)pDel是parent的右孩子
		{
			if (pDel->pLeft)//若pDel有右孩子->寻找其pDel右孩子的最小左孩子(方便pDel的左孩子被其领养)
			{
				Node<T>* pMove = pDel->pRight;
				while (pMove->pLeft)
				{
					pMove = pMove->pLeft;
				}//找到最小左孩子
				pMove->pLeft = pDel->pLeft;//领养pDel的左孩子
				pDelParent->pRight = pDel->pRight;//让父节点领养pDel的右孩子(此时已经将pDel的左孩子归pDel右孩子的最小左孩子领养了)
			}
			else//pDel无右孩子->让父亲节点直接领养pDel的左孩子
			{
				pDelParent->pRight = pDel->pLeft;
			}
			delete pDel;//处理掉待删除节点
			pDel = nullptr;
		}
	}
}
//功能四:遍历
template<class T>
void Tree<T>::travel(int type)
{
	switch (type)
	{
	case -1:
		_preTravel(pRoot); break;
	case 0:
		_midTravel(pRoot); break;
	case 1:
		_behTravel(pRoot); break;
	}
	cout << endl;
}
template<class T>
void Tree<T>::_preTravel(Node<T>* p)
{
	if (p == nullptr)return;
	cout << p->data << " ";
	_preTravel(p->pLeft);
	_preTravel(p->pRight);
}
template<class T>
void Tree<T>::_midTravel(Node<T>* p)
{
	if (p == nullptr)return;
	_midTravel(p->pLeft);
	cout << p->data << " ";
	_midTravel(p->pRight);
}
template<class T>
void Tree<T>::_behTravel(Node<T>* p)
{
	if (p == nullptr)return;
	_behTravel(p->pLeft);
	_behTravel(p->pRight);
	cout << p->data << " ";
}
//功能五:查找指定节点
template<class T>
Node<T>* Tree<T>::findNode( T& data)
{
	Node<T>* pTemp = pRoot;
	while (pTemp)
	{
		if (pTemp->data>data)
		{
			pTemp = pTemp->pLeft;
		}
		else if (data > pTemp->data)
		{
			pTemp = pTemp->pRight;
		}
		else
			return pTemp;
	}
	return nullptr;
}

Main:

#include"Stu.h"
#include"Tree.h"
//#include<string>
#include<vector>

using namespace std;

int main()
{
	Tree<Stu> m;
	vector<Stu> arr = { Stu(4,"a"),Stu(2,"b"),Stu(9,"li"),Stu(1, string("ddd")),Stu(7,"kk")};
	for (auto v : arr)
	{
		m.insert(v);
	}
	m.travel();
	//arr.reserve(4);
	for (auto v : arr)
	{
		m.deleteNode(v);
		m.travel();
	}
	m.travel();
	return 0;
}

输出:

学号:1 姓名:ddd   学号:2 姓名:b   学号:4 姓名:a   学号:7 姓名:kk   学号:9 姓名:li
学号:1 姓名:ddd   学号:2 姓名:b   学号:7 姓名:kk   学号:9 姓名:li
学号:1 姓名:ddd   学号:7 姓名:kk   学号:9 姓名:li
学号:1 姓名:ddd   学号:7 姓名:kk
学号:7 姓名:kk

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值