【C++第十三课-二叉树进阶】二叉搜索树

二叉搜索树

概念

二叉搜索树又称二叉排序树,它或者是一棵空树。

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
中序有序

增,删,查

1、删

bool Erase(const K& data)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		if (_root == nullptr)
			return false;
		//找要删除的对象
		while (cur->_key != data && cur != nullptr)
		{
			if (cur->_key > data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < data)
			{
				parent = cur;
				cur = cur->_right;
			}
		}
		//没找到要删除的对象
		if (cur == nullptr)
			return false;
		//找到了要删除的对象
		//1、删除的是叶子
		if (cur->_left == nullptr && cur->_right == nullptr)
		{
			if (parent->_left == cur)
				parent->_left = nullptr;
			if (parent->_right == cur)
				parent->_right = nullptr;
		}
		//2、只有左孩子或右孩子
		if (cur->_left != nullptr && cur->_right == nullptr)
		{
			if (parent->_left == cur)
				parent->_left = cur->_left;
			if (parent->_right == cur)
				parent->_right = cur->_left;
		}
		if (cur->_right != nullptr && cur->_left == nullptr)
		{
			if (parent->_left == cur)
				parent->_left = cur->_right;
			if (parent->_right == cur)
				parent->_right = cur->_right;
		}
		//3、左右孩子都有,左孩子放到右孩子的最左边
		if (cur->_right != nullptr && cur->_left != nullptr)
		{
			Node* l = cur->_left;
			Node* r = cur->_right;
			Node* p = r;
			while (p->_left != nullptr)
			{
				p = p->_left;
			}
			p->_left = l;
			if (parent->_left == cur)
				parent->_left = r;
			if (parent->_right == cur)
				parent->_right = r;
		}

		delete cur;
		cur = nullptr;
		return true;
	}

问题:
上面这个方法不好,例如当树很深的时候
替换删除法:找一个能替换我的节点,交换值,转换删除他
一个孩子和没有孩子可以看出一类

修改

bool Erase(const K& data)
	{
		//先找到data的节点
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			if (cur->_key > data)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < data)
			{
				parent = cur;
				cur = cur->_right;
			}
			//找到了
			else
			{
				//1、只有一个孩子或没有孩子
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
						parent->_left = cur->_right;
					if (parent->_right == cur)
						parent->_right = cur->_right;
					delete cur;
					cur = nullptr;
					return true;
				}
				if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
						parent->_left = cur->_left;
					if (parent->_right == cur)
						parent->_right = cur->_left;
					delete cur;
					cur = nullptr;
					return true;
				}
				//1、有两个孩子
				Node* rcur = cur->_right;
				Node* rparent = cur;
				while (rcur->_left)
				{
					rparent = rcur;
					rcur = rcur->_left;
				}
				cur->_key = rcur->_key;
				if (rparent->_left == rcur)
					rparent->_left = rcur->_right;
				if (rparent->_right == rcur)
					rparent->_right = rcur->_right;
				delete rcur;
				rcur = nullptr;
				return true;
			}
		}
	}

2、查

3、增

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

拷贝构造

	BSTree<int> t;
	t.InsertR(8);
	t.InsertR(3);
	t.InsertR(6);
	t.InsertR(7);
	t.InsertR(4);
	t.InsertR(1);
	t.InsertR(10);
	t.InsertR(14);
	t.InsertR(13);
	t.InOrder();
	BSTree<int> t2(t);

在这里插入图片描述
上面这个拷贝构造是我自己写的,虽然根据树1深拷贝了树2,但是没法给树2的_root。也就是Copy函数缺少一个返回值
补充

	//强制生成默认构造
	BSTree() = default;
	//强制生成默认构造
	BSTree() = default;

	Node*& Copy(Node*& root1)
	{
		if (root1 == nullptr)
			return nullptr;
		Node*& Newnode = new Node(root1->_key);
		Newnode->_left = Copy(root1->_left);
		Newnode->_right = Copy(root1->_right);
		return Newnode;
	}
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t->_root);
	

析构函数

	~BSTree()
	{
		Destroy(_root);
	}
	void Destory(Node*& root)
	{
		if (root == nullptr)
			return;
		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}

赋值

	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

增,删,查(递归)

引用不能改变指向

1、增(递归)

关键点:如何和父亲链接

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

上面还是有冗余

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

引用是关键

2、删

	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)
		{
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _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* RightMin = root->_right;
				while (RightMin->_left)
				{
					RightMin = RightMin->_left;
				}
				swap(root->_key, RightMin->_key);
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

3、查

	bool FindR(const K*& key)
	{
		return _FindR(_root, key);
	}
	bool _FindR(Node* _root, 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;
	}

k搜索模型

快速查找一个值是否存在?
例子
小区内部车库:进出都有快速查看你的车牌号是否是小区住户
门禁

kv搜索模型

快速通过一个值(key)查找另一个值(value)是否存在?
例子
商场的车库:进去是都可以进,出去的时候需要通过车牌号快速查找你停入的时间,判断是否超过半个小时。若没有超过半小时直接抬杆,若超过半个小时计算应付费用再抬杆
字典查询
高铁用身份证进站

namespace key_value
{
	template<class K, class V>
	class BSTreeNode
	{
		typedef BSTreeNode<K, V> Node;
	public:
		BSTreeNode(const K& key = 0, const V& value = 0)
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
		Node* _left;
		Node* _right;
		K _key;
		V _value;

	};

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		void InOrder()
		{
			_InOrder(_root);
		}
		bool Erase(const K& data)
		{
			//先找到data的节点
			Node* cur = _root;
			Node* parent = _root;
			while (cur)
			{
				if (cur->_key > data)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < data)
				{
					parent = cur;
					cur = cur->_right;
				}
				//找到了
				else
				{
					//1、只有一个孩子或没有孩子
					if (cur->_left == nullptr)
					{
						if (parent->_left == cur)
							parent->_left = cur->_right;
						if (parent->_right == cur)
							parent->_right = cur->_right;
						delete cur;
						cur = nullptr;
						return true;
					}
					if (cur->_right == nullptr)
					{
						if (parent->_left == cur)
							parent->_left = cur->_left;
						if (parent->_right == cur)
							parent->_right = cur->_left;
						delete cur;
						cur = nullptr;
						return true;
					}
					//1、有两个孩子
					Node* rcur = cur->_right;
					Node* rparent = cur;
					while (rcur->_left)
					{
						rparent = rcur;
						rcur = rcur->_left;
					}
					swap(cur->_key, rcur->_key);
					swap(cur->_value, rcur->_value);
					if (rparent->_left == rcur)
						rparent->_left = rcur->_right;
					if (rparent->_right == rcur)
						rparent->_right = rcur->_right;
					delete rcur;
					rcur = nullptr;
					return true;
				}
			}
		}
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}
		bool InsertR(const K& key, const V& value)
		{
			return _InsertR(_root, key, value);
		}
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

	private:
		Node* _FindR(Node* _root, K key)
		{
			if (_root == nullptr)
				return nullptr;
			if (_root->_key < key)
				return _FindR(_root->_right, key);
			else if (_root->_key > key)
				return _FindR(_root->_left, key);
			else
				return _root;
		}
		void _InOrder(Node* _root)
		{
			if (_root == nullptr)
				return;
			_InOrder(_root->_left);
			cout << _root->_key << " ";
			_InOrder(_root->_right);
		}
		Node*& Copy(Node*& root1)
		{
			if (root1 == nullptr)
				return nullptr;
			Node*& Newnode = new Node(root1->_key);
			Newnode->_left = Copy(root1->_left);
			Newnode->_right = Copy(root1->_right);
			return Newnode;
		}
		bool _InsertR(Node*& root, const K& key, const V& value)
		{
			if (root == nullptr)
			{
				root = new Node(key, value);
				return true;
			}
			if (root->_key < key)
			{
				return _InsertR(root->_right, key, value);
			}
			else if (root->_key > key)
			{
				return _InsertR(root->_left, key, value);
			}
			else
				return false;
		}
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;
			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _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* RightMin = root->_right;
					while (RightMin->_left)
					{
						RightMin = RightMin->_left;
					}
					swap(root->_key, RightMin->_key);
					swap(root->_value, RightMin->_value);
					return _EraseR(root->_right, key);
				}
				delete del;
				return true;
			}
		}
		Node* _root = nullptr;
	};
}

二叉树习题

1、自底向上的层序遍历
方法一:t不直接push_back到tt中,而是放入栈st,更改一下顺序

    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        vector<vector<int>> tt;
        queue<TreeNode*> q;
        queue<int> qlayer;
        stack<vector<int>> st;
        int layer = 1;
        if(root)
        {
            q.push(root);
            qlayer.push(layer);
        }
        while(!q.empty())
        {
            vector<int> t;
            while(qlayer.front() == layer)
            {
                TreeNode* ret = q.front();
                t.push_back(ret->val);
                if(ret->left)
                {
                    q.push(ret->left);
                    qlayer.push(layer+1);
                }
                if(ret->right)
                {
                    q.push(ret->right);
                    qlayer.push(layer+1);
                }
                q.pop();
                qlayer.pop();
            }
            layer++;
            st.push(t);
        }
        while(!st.empty())
        {
            tt.push_back(st.top());
            st.pop();
        }
        return tt;
    }

方法二:用算法逆置一下

reverse(tt.begin(), tt.end());

2、二叉树的最近公共祖先
在这里插入图片描述

最近公共祖先一定是两个节点一个在左子树一个在右子树
5也是7和4的公共祖先,但不是最近公共祖先

在这里插入图片描述

如果一个是另一个的子孙,那么祖先那个节点就是公共祖先

在这里插入图片描述
对于函数的构想:
因为当要求的两个节点分布在此时节点的左右两侧,那么此时的这个节点就是最近公共祖先。
(1)写一个函数-判断节点是否在这棵子树上 bool NodeinTree()
(2)在上一层函数里面去递归的走判断

class Solution {
public:
    bool NodeinTree(TreeNode*& root, TreeNode*& r)
    {
        if(root == NULL)
            return false;
        return root == r || NodeinTree(root->left, r) || NodeinTree(root->right, r);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //两个节点其中一个节点自己就是最近公共祖先
        if(root == p || root == q)
            return root;
        bool pInLeft = NodeinTree(root->left, p);
        bool pInRight = NodeinTree(root->right, p);
        bool qInLeft = NodeinTree(root->left, q);
        bool qInRight = NodeinTree(root->right, q);
        //两个节点一左一右,那次是root就是最近公共祖先
        if((pInLeft && qInRight) || (pInRight && qInLeft))
            return root;
        //两个节点都在左边,去root的左树上找
        else if(pInLeft && qInLeft)
            return lowestCommonAncestor(root->left, p, q);
        else if(pInRight && qInRight)
            return lowestCommonAncestor(root->right, p, q);
        
        assert(false);
        return NULL;
    }
};

时间复杂度:O(n^2)
这个思路效率太低了
方法二
把给定的两个节点p、q的路径构建出来,放到stack里面。
在这里插入图片描述

    bool NodePath(TreeNode* root, TreeNode* r, stack<TreeNode*>& sr)
    {
        if(root == NULL)
            return false;
        sr.push(root);
        //r在左孩子或右孩子里面
        if(root == r)
            return true;
        if(NodePath(root->left, r, sr) || NodePath(root->right, r, sr) )
        {
            return true;
        }
        else
        {
            sr.pop();
            return false;
        }

    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> sp;
        stack<TreeNode*> sq;
        NodePath(root, p, sp);
        NodePath(root, q, sq);

        while(sp.size() != sq.size())
        {
            if(sp.size() > sq.size())
                sp.pop();
            else
                sq.pop();
        }
        while(!sp.empty())
        {
            if(sp.top() == sq.top())
                return sp.top();
            else
            {
                sp.pop();
                sq.pop();
            }
        }
        assert(false);
        return NULL;
    }
};

3、二叉搜索树与双向链表
在这里插入图片描述

这个想法太麻烦了
对root去找左子树的最大节点右子树的最小节点
root和左子树的最大节点、右子树的最小节点互指
递归走
问题:想不出递归的停止条件

引用的使用

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值