为什么会有二叉树进阶
1、二叉树在数据讲过了,但是又把二叉树提出来,是因为给二叉树又加了新的规则
2、map.set特性需要先铺垫二叉搜索树,二叉搜索树也是树形结构
3、对二叉搜索树有了了解,可以更好的了解map set的特性
4、二叉树部分面试题较难,前边讲的解题方法不容易理解,所以学习map set 二叉搜索树对解题有帮助
二叉搜索树
1、当然,在实际中也可以是左边大右边小,也是二叉搜索树。
2、如果是一个完全二叉树或者满二叉树,符合二叉搜索树的性质,那么它的查找复杂度就是一个log(n)
二叉搜索树操作
1、二叉搜索树的查找
//查找
Node* find(const T& val)
{
int count = 0;//可以记录找到这个数总共查找了几次
if(_root == nullptr)
{
return _root;
}
//根不为空,从根开始找,由于需要向下移动,所以定义一个cur
Node* cur = _root;
while(cur)
{
count++:
if(cur->_data == val)
{
cout<<"count:"<<count<<endl;
return cur;
}
else if(cur->_data > val)
{
cur = cur->left;
}
else
{
cur = cur->right;
}
}
//没有找到
cout<<"count:"<<count<<endl;
return nullptr;
}
2、二叉搜索树的插入
1、二叉搜索树的插入只能插入到叶子结点
2、插入分为 找位置,然后插入
3、并且二叉搜索树不会放重复的数据
4、中序遍历是一个有序序列
5、二叉树插入的时候应该插入的是val值,然后用val构造结点,然后进行插入
//插入就是成功与否
bool Insert(const T& val)
{
//根为空
if(_root == nullptr)
{
_root = new Node(val);
return true;
}
//不为空,则进行查找插入,
//有查找,则需要cur
Node* cur = _root;
//因为要进行插入,所以要记录一下该点插入的上一个结点,也就是父节点parent
Node* parent = nullptr;
while(cur)
{
parent = cur;
if(cur->_data == val)
{return false;}
else if(cur->_data > val)
{
cur = cur->_left;
}
else
{
cur = cur->_right;
}
}
//将val值定义成Node,创建新节点,因为cur为空了,所以可以用来创建新节点
cur = new Node(val);
if(parent ->_data > val)
{
parent->_left = cur;
}
else
parent->_right = cur;
return true;
}
1、二叉树的插入其实就是在构造一颗二叉搜索树
3、二叉搜索树的删除
上图将abcd四种情况 结合三种情况,可以写条件是
1、左为空–处理右孩子情况
2、右为空–处理左孩子情况
3、左右都不为空
将左右孩子都为空放在1 或者2都可以处理 看徐婧航的代码 比较好
上图是当删除结点的时候,该结点有左右孩子的情况。删除左子树的最右结点的四种情况。
1、因为插入和删除 都是要先进行查找,在进行插入和删除的操作的
2、插入 查找的是一个合适的叶子结点位置,记录parent结点,根据val与父节点大小对比进行插入
3、删除 查找的是当前要删除的结点,记录parent结点,然后处理被删除结点 父亲与孩子之间的关系。
4、因为插入删除之间的查找 需要parent的更新,为了后续的操作,所以不能复用查找的代码
5、插入删除更新parent什么时候更新,要根据实际情况而定,parent插入可以在while下之后更新,但是删除必须在判断了之后传递的时候更新parent,防止parnet与cur指向相同位置,插入是可以让cur和parent指向相同的位置
6、删除的时候,一定要注意如果删除的是根节点,是没有parent的,需要_root直接接收被删除结点的左右孩子
//要进行删除 首先是先找到该结点,由于要进行删除操作,所以需要将parent记录下来
bool erase(const T& val)//找到值相同 直接delete结点就可以了
{
if(_root == nullptr)
{
return false;
}
//先查找,定义一个cur,还进行删除操作,在定义一个parent
Node* parent = nullptr;
Node* cur = _root;
//因为上边的查找的代码没有parent的更新,所以查找的代码不能够进行复用
//需要重新写查找代码
while(cur)
{
parent = cur;//parnet更新不能写在这,因为每次一这样,parent与cur指向相同的位置,则无法删除操作
if(cur->_data == val)
{
break;
}
else if(cur->_data > val)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//没有找到结点
if(cur == nullptr)
{
return false;
}
//1、非叶子结点
//a、左右孩子都有
//b、有左孩子
//c、有右孩子
if(cur ->_left && cur->_right)//a、左右孩子都有
{
//该结点的左子树的最大值也就是向右找 或者 找该节点右子树的最小值也就是向左找
//我的方法是将cur位置保存了一下,然后用cur去找左子树的最右结点,用cur替换tmp,删除cur就可以了
//老师的方法是重新定义一个mostRight结点,让cur待在删除的结点位置,然后mostRight去找,最后替换mostRight 和cur,删除mostRight
/* Node* tmp = cur;
parent = nullptr;
cur = cur->_left;
while(cur->_right)
{
parent = cur;
cur = cur ->_right;
}
tmp->_data = cur->_data;
parent->_right = cur->_left;
delete cur;*/ //我的想法
Node* mostRight = cur->_left;
parent = cur;
while(mostRight->_right)
{
parent = mostRight;
mostRight = mostRight->_right;
}
cur->_data = mostRight->_data;
//删除最右结点,如果mostRight在parent的左边,或者mostRight在parent的右边,都是有可能的,因为是去左子树找最右结点的。
if(parent->_left == mostRight)
{
parent->_left = mostRight->_left;
}
else
parent->_right = mostRight->_left;
delete mostRight;
}
else if(cur->_left)//b、有左孩子
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = cur->_left;
else
parent ->_right = cur->_left;
}
else
{
_root = cur->_left;
}
delete cur;
}
else if(cur->_right)//c、有右孩子
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = cur->_right;
else
parent ->_right = cur->_right;
}
else
{
_root = cur->_right;
}
delete cur;
}
2、叶子结点
else
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = nullptr;
else
parent ->_right = nullptr;
}
//根结点
else
_root = nullptr;
delete cur;
}
return true;
}
二叉搜索树的实现
//二叉搜索树首先要有一个结点,结点有数值 左右指针,所以reeee7e体
//由于结点里边的数据可以多种类型,所以泛型
template<class T>
struct BSTNode
{
T _data;
BSTNode<T>* _left;
BSTNode<T>* _right;
//构造一个结点
BSTNode(const T& val = T())
:_data(val)
,_left(nullptr)
,_right(nullptr)
{}
}
//二叉搜索树 我们需要构建一棵树,然后对其进行操作
//实现左子树小于根,右子树大于根
template<class T>
class BSTree
{
public:
//给树的结点取一个别名
typedef BSTNode<T> Node;
//1、查找在上边
//2、插入在上边
//3、删除在上边
//为了验证一下这个二叉搜索树的中序遍历是一个递增序列,写一个中序遍历
//4、中序遍历就是一个动作,没有返回值
void inorder()//这里是因为中序遍历的时候要访问_root,但是_root是私有的,所以需要外一个接口,内也应该有一个接口。
{
_inorder(_root);
cout<<endl;
}
void _inorder(Node* root)//遍历应该是结点 不是BSTree
{
if(root == nullptr)
{
return;
}
_inorder(root->_left);
cout<<root->_data<<" ";
_inorder(root->_right);
}
//8、构造函数
BSTree()
:_root(nullptr)
{}
//5、析构
//用递归进行析构二叉搜索树,先析构左子树再是右子树,然后根节点,由于析构从外界传值,又要访问_root。所以定义一个内接口
void destory(Node* root)
{
destory(root->_left);
destory(root->_right);
delete root;
}
~BSTree()//析构函数不传参,但是要递归析构,可以考虑重写一个destory函数,进行递归析构
{
destory(_root);
_root = nullptr;
}
//6、二叉树的拷贝构造---拷贝构造肯定要创建结点
Node* copyTree(Node* root)
{
if(root)
{
Node* node = new Node(root->_data);
node->left = copyTree(root->_left);
node->_right = copyTree(root->_right);
return node;
}
return nullptr;
}
BSTree(const BSTree<T>& bst)//
{
//因为在copyTree已经创建好树并且连接起来了,所以呢 最后只返回_root结点就可以了
_root = copyTree(bst._root);
}
//7 二叉树的赋值运算
BSTree<T>& operator=(const BSTree<T>& bst)
{
if(this != &bst)
{
destory(_root);
_root = copyTree(bst._root);
}
return *this;
}
//现代写法
BSTree<T>& operator=(const BSTree<T> bst)//这里要进行交换,如果传&的话,会让原来的bst树发生变化的,所以赋值传值。
{
swap(_root,bst._root);
return *this;
}
private:
//给树定义一个_root的结点
//再给一个缺省值,这样的话在构造_root的时候就不用调用结点的构造函数。
Node* _root = nullptr;
}
//插入随机数,然后中序遍历,在查找那个数字
void test
{
srand(time(nullptr));//让数更加随机
int num;
cout<<"i请输入一个num"<<endl;
cin>> num;
BSTree<int> b;
for(int i = 0;i < num;i++)
{
b.insert(rand());
}
b.inorder();//将随机数打印出来
cout<<"请输入一个查找的数字"<<endl;
cin >> num;
b.find(num);
}
void test
{
BSTree<int> b;
b.insert(10);
b.insert(5);
b.insert(15);
b.insert(3);
b.insert(0);
b.insert(2);
b.insert(13);
b.insert(17);
b.inorder();
b.inorder();
b.erase(3);
b.inorder();
b.erase(10);
b.inorder();
}
void test()
{
BSTree<int> b;
b.insert(10);
b.insert(5);
b.insert(15);
b.insert(3);
b.insert(0);
b.insert(2);
b.insert(13);
b.insert(17);
b.inorder();
BSTree<int> copy(b);
copy.inorder();
BSTree<int> b2;
b2 = b;
b2.inorder();
}
1、二叉树的拷贝构造不好理解,代码也不太好想,看图
2、二叉搜索树的拷贝构造和析构一样,都是递归拷贝构造 递归析构的,所以都能重新写个函数实现递归的要求。
3、析构函数与构造函数必须成对出现。所以在上边方程写了析构函数,也就必须再写一个构造函数。
二叉搜索树的应用
1、K模型
1、二叉搜索树就是一个K的模型
2、KV模型
2、对于KV模型,是需要改变二叉搜索的内部一些东西的
template<class K,class V>
struct BSTNode
{
//数据的位置
k _key;
//数据
v _data;
BSTNode<K,V>* _left;
BSTNode<K,V>* _right;
//构造一个结点
BSTNode(const K& key,const V& val)
:_key(key)
,_data(val)
,_left(nullptr)
,_right(nullptr)
{}
}
//二叉搜索树 我们需要构建一棵树,然后对其进行操作
//实现左子树小于根,右子树大于根
template<class K,class V>
class BSTree
{
public:
//给树的结点取一个别名
typedef BSTNode<K,V> Node;
//1、查找
Node* find(const K& key)
{
int count = 0;//可以记录找到这个数总共查找了几次
if(_root == nullptr)
{
return _root;
}
//根不为空,从根开始找,由于需要向下移动,所以定义一个cur
Node* cur = _root;
while(cur)
{
count++:
if(cur->_key == key)
{
cout<<"count:"<<count<<endl;
return cur;
}
else if(cur->_key > key)
{
cur = cur->left;
}
else
{
cur = cur->right;
}
}
//没有找到
cout<<"count:"<<count<<endl;
return nullptr;
}
//2、插入
bool Insert(const K& key,const V& data)
{
//根为空
if(_root == nullptr)
{
_root = new Node(key,data);
return true;
}
//不为空,则进行查找插入,
//有查找,则需要cur
Node* cur = _root;
//因为要进行插入,所以要记录一下该点插入的上一个结点,也就是父节点parent
Node* parent = nullptr;
while(cur)
{
parent = cur;
if(cur->_key == key)
{return false;}
else if(cur->_key > key)
{
cur = cur->_left;
}
else
{
cur = cur->_right;
}
}
//将val值定义成Node,创建新节点,因为cur为空了,所以可以用来创建新节点
cur = new Node(key,data);
if(parent ->_key > key)
{
parent->_left = cur;
}
else
parent->_right = cur;
return true;
}
//3、删除
bool erase(const K& key)//找到值相同 直接delete结点就可以了
{
if(_root == nullptr)
{
return false;
}
//先查找,定义一个cur,还进行删除操作,在定义一个parent
Node* parent = nullptr;
Node* cur = _root;
//因为上边的查找的代码没有parent的更新,所以查找的代码不能够进行复用
//需要重新写查找代码
while(cur)
{
parent = cur;//parnet更新不能写在这,因为每次一这样,parent与cur指向相同的位置,则无法删除操作
if(cur->_key == key)
{
break;
}
else if(cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//没有找到结点
if(cur == nullptr)
{
return false;
}
//1、非叶子结点
//a、左右孩子都有
//b、有左孩子
//c、有右孩子
if(cur ->_left && cur->_right)//a、左右孩子都有
{
//该结点的左子树的最大值也就是向右找 或者 找该节点右子树的最小值也就是向左找
//我的方法是将cur位置保存了一下,然后用cur去找左子树的最右结点,用cur替换tmp,删除cur就可以了
//老师的方法是重新定义一个mostRight结点,让cur待在删除的结点位置,然后mostRight去找,最后替换mostRight 和cur,删除mostRight
/* Node* tmp = cur;
parent = nullptr;
cur = cur->_left;
while(cur->_right)
{
parent = cur;
cur = cur ->_right;
}
tmp->_data = cur->_data;
parent->_right = cur->_left;
delete cur;*/ //我的想法
Node* mostRight = cur->_left;
parent = cur;
while(mostRight->_right)
{
parent = mostRight;
mostRight = mostRight->_right;
}
cur->_key = mostRight->_key;***~~//这块替换结点应该也要,老师还没有发现~~ ***
cur->_data = mostRight->_data;
//删除最右结点,如果mostRight在parent的左边,或者mostRight在parent的右边,都是有可能的,因为是去左子树找最右结点的。
if(parent->_left == mostRight)
{
parent->_left = mostRight->_left;
}
else
parent->_right = mostRight->_left;
delete mostRight;
}
else if(cur->_left)//b、有左孩子
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = cur->_left;
else
parent ->_right = cur->_left;
}
else
{
_root = cur->_left;
}
delete cur;
}
else if(cur->_right)//c、有右孩子
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = cur->_right;
else
parent ->_right = cur->_right;
}
else
{
_root = cur->_right;
}
delete cur;
}
2、叶子结点
else
{
if(cur != _root)
{
if(parent->_left == cur)
parent->_left = nullptr;
else
parent ->_right = nullptr;
}
//根结点
else
_root = nullptr;
delete cur;
}
return true;
}
//为了验证一下这个二叉搜索树的中序遍历是一个递增序列,写一个中序遍历
//4、中序遍历就是一个动作,没有返回值
void inorder()//这里是因为中序遍历的时候要访问_root,但是_root是私有的,所以需要外一个接口,内也应该有一个接口。
{
_inorder(_root);
cout<<endl;
}
void _inorder(Node* root)//遍历应该是结点 不是BSTree
{
if(root == nullptr)
{
return;
}
_inorder(root->_left);
cout<<root->_key<<" "<<root->_data<<" ";
_inorder(root->_right);
}
//8、构造函数
BSTree()
:_root(nullptr)
{}
//5、析构
//用递归进行析构二叉搜索树,先析构左子树再是右子树,然后根节点,由于析构从外界传值,又要访问_root。所以定义一个内接口
void destory(Node* root)
{
destory(root->_left);
destory(root->_right);
delete root;
}
~BSTree()//析构函数不传参,但是要递归析构,可以考虑重写一个destory函数,进行递归析构
{
destory(_root);
_root = nullptr;
}
//6、二叉树的拷贝构造---拷贝构造肯定要创建结点
Node* copyTree(Node* root)
{
if(root)
{
Node* node = new Node(root->_key,root->_data);
node->left = copyTree(root->_left);
node->_right = copyTree(root->_right);
return node;
}
return nullptr;
}
BSTree(const BSTree<K,V>& bst)//
{
//因为在copyTree已经创建好树并且连接起来了,所以呢 最后只返回_root结点就可以了
_root = copyTree(bst._root);
}
//7 二叉树的赋值运算
BSTree<K,V>& operator=(const BSTree<K,V>& bst)
{
if(this != &bst)
{
destory(_root);
_root = copyTree(bst._root);
}
return *this;
}
//现代写法
BSTree<T>& operator=(const BSTree<T> bst)//这里要进行交换,如果传&的话,会让原来的bst树发生变化的,所以赋值传值。
{
swap(_root,bst._root);
return *this;
}
private:
//给树定义一个_root的结点
//再给一个缺省值,这样的话在构造_root的时候就不用调用结点的构造函数。
Node* _root = nullptr;
}
void test()
{
BSTree<int,int> b;
b.insert(10,10);
b.insert(5,5);
b.insert(15,15);
b.insert(3,3);
b.insert(0.0);
b.insert(2.2);
b.insert(13.13);
b.insert(17,13);//key不能相同,只能data可以相同
b.insert(17,17);//这个插入不进去,key不能相同
b.inorder();
BSTree<int,int> copy(b);
copy.inorder();
BSTree<int,int> b2;
b2 = b;
b2.inorder();
}
二叉搜索树的性能分析
如何将单支树 进行改进 **不论按照什么次序进行插入,都可以是二叉搜索树呢?**下节课看
使用了旋转 深度一致相同的方式?应该是在红黑树那一块有解决方法。