【C++】二叉搜索树

二叉搜索树

  • 1. 二叉搜索树概念
  • 2. 二叉搜索树的实现
  • 3. 二叉树的应用
  • 4. 练习


1. 二叉搜索树概念

二叉搜索树要么是棵空树,要么满足以下性质:(1)若左子树不为空,左子树上所有节点的键值小于根节点的键值;(2)若右子树不为空,右子树上所有节点的键值大于根节点的键值;(3)左右子树都是二叉搜索树。
通过中序遍历可以得到升序序列,因此二叉搜索树又叫二叉排序树。因其方便查找,又叫做二叉查找树。
【C++】二叉搜索树_第1张图片

2. 二叉搜索树的实现

非递归版本

//节点
template
struct TreeNode
{
	TreeNode(const K&key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}

	TreeNode* _left;
	TreeNode* _right;
	K _key;
};
//二叉搜索树
template
class BSTree
{
public:
	typedef TreeNode Node;
	//查找
	//key大于根节点的键值就往右边找,小于根节点的键值就往左边找
	//找到了返回节点地址,找不到返回nullptr
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	//插入
	//若为空树,直接申请根节点;若不为空,查找合适的位置插入
	//插入成功,返回true;当已有相同的key值,插入失败,返回false
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);//new = operator new + 自定义类型调用构造函数
			return true;
		}
		Node* parent = _root;
		Node* cur = _root;
		//找到合适的位置
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		//插入
		if (key < parent->_key)
		{
			parent->_left = new Node(key);
		}
		else if (key > parent->_key)
		{
			parent->_right = new Node(key);
		}
		return true;
	}
	//删除
	//删除共有三种情况:(1)当要删除节点的左孩子不为空(2)当要删除节点的右孩子不为空,
	//(3)当要删除节点的左右孩子都不为空
	bool Erase(const K& key)
	{
		//先找到要删除的节点
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//找到了考虑三种情况
				//右孩子为空
				if (cur->_right == nullptr)
				{
					//考虑到要删除的节点是根节点
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
				}
				//左孩子为空
				else if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
				}
				//左右孩子都不为空
				//用替换法,找到左子树的最大节点(最右节点),或者找到右子树的最小节点(最左节点),
				//然后替换要删除的节点的键值。最后再删除节点
				else
				{
					Node* LeftMax = cur->_left;
					Node* parentMax;
					while (LeftMax->_right)
					{
						parentMax = LeftMax;
						LeftMax = LeftMax->_right;
					}
					swap(LeftMax->_key, cur->_key);

					if (parentMax->_left == LeftMax)
					{
						parentMax->_left = LeftMax->_left;
					}
					else
					{
						parentMax->_right = LeftMax->_left;
					}
					cur = LeftMax;
				}
				delete cur;
				return true;
			}
		}
		return false;
	}
	//交换
	void swap(K& k1, K& k2)
	{
		std::swap(k1, k2);
	}
	//中序遍历
	//_root是private成员,在类外无法访问
	void _Inorder(Node* _root)
	{
		if (_root == nullptr)
		{
			return;
		}
		Inorder(_root->_left);
		cout << _root->_key << " ";
		Inorder(_root->_right);
	}
	void Inorder()
	{
		_Inorder(_root);
	}
private:
	Node* _root;
};

递归版本补充

template
class BSTree
{
public:
	typedef TreeNode Node;
	//查找
	//key大于根节点的键值就往右边找,小于根节点的键值就往左边找
	//找到了返回节点地址,找不到返回nullptr
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}
	//查找
	Node* FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	//插入
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
	//删除
	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
	//拷贝构造
	//就是复制一个相同的二叉搜索树
	BSTree(const BSTree& b)
	{
		_root = _Copy(b._root);
	}
	//=重载
	BSTree& operator=(BSTree b)
	{
		//_root指向b的空间,b指向_root原来的旧空间,函数结束后销毁
		swap(_root, b._root);
		return *this;
	}
	//析构
	~BSTree()
	{
		Destroy(_root);
	}
private:
	Node* _root;
	//查找
	Node* _FindR(Node* root ,const K& key)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		if ( key == root->_key )
		{
			return root;
		}
		else if (key > root->_key)
		{
			return _FindR(root->_right,key);
		}
		else
		{
			return _FindR(root->_left,key);
		}
	}
	//插入
	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			//传引用的好处,不用记住其父节点,返回之后自动链接到二叉搜索树
			root = new Node(key);
			return true;
		}
		if (key == root->_key)
		{
			return false;
		}
		else if (key > root->_key)
		{
			return _InsertR(root->_right, key);
		}
		else
		{
			return _InsertR(root->_left, key);
		}
	}
	//删除
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (key == root->_key)
		{
			//左孩子为空
			if (root->_left == nullptr)
			{
				delete root;
				root = root->_right;
			}
			//右孩子为空
			else if (root->_right == nullptr)
			{
				delete root;
				root = root->_left;
			}
			//都不为空
			else
			{
				Node* LeftMax = root->_left;
				while (LeftMax->_right)
				{
					LeftMax = LeftMax->_right;
				}
				swap(LeftMax->_key, root->_key);
				//此时将左右孩子都不为空的问题,转换成右孩子为空的问题
				return _EraseR(root->_left, key);
			}
		}
		else if (key > root->_key)
		{
			return _EraseR(root->_right, key);
		}
		else
		{
			return _EraseR(root->_left, key);
		}
	}
	//拷贝
	Node* Copy(Node* root)
	{
		if (root == nullptr)
		{
			return root;
		}
		_root = new Node(b->_key);
		_root->_left = Copy(_root->_left);
		_root->_right = Copy(root->_right);
	}
	//销毁
	void Destroy(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}
};

3. 二叉树的应用

  1. K模型:节点只有key一个键值。KV模型:节点中有键值对,每个key都对应一个value。
template
struct BSTreeNode
{
	BSTreeNode* _left;
	BSTreeNode* _right;
	K _key;
	V _value;
};
template
	class BSTree
	{
		typedef BSTreeNode Node;
	public:
		BSTree() : _root(nullptr) {}
		Node* Find(const K& key)
		{
			return _FindR(_root,key);
		}
		bool Insert(const K& key, const V& value)
		{
			return _InsertR(_root, key, value);
		}
		bool Erase(const K& key)
		{
			return _EraseR(_root, key);
		}
	private:
		Node* _root;

		//查找
		Node* _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
			{
				return nullptr;
			}
			if (key == root->_key)
			{
				return root;
			}
			else if (key > root->_key)
			{
				return _FindR(root->_right, key);
			}
			else
			{
				return _FindR(root->_left, key);
			}
		}
		//插入
		bool _InsertR(Node*& root, const K& key, const V& value)
		{
			if (root == nullptr)
			{
				root = new Node(key,value);
				return true;
			}
			if (key == root->_key)
			{
				return false;
			}
			else if (key > root->_key)
			{
				return _InsertR(root->_right, key, value);
			}
			else
			{
				return _InsertR(root->_left, key, value);
			}
		}
		//删除
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (key == root->_key)
			{
				//左孩子为空
				if (root->_left == nullptr)
				{
					delete root;
					root = root->_right;
				}
				//右孩子为空
				else if (root->_right == nullptr)
				{
					delete root;
					root = root->_left;
				}
				//都不为空
				else
				{
					Node* LeftMax = root->_left;
					while (LeftMax->_right)
					{
						LeftMax = LeftMax->_right;
					}
					swap(LeftMax->_key, root->_key);
					swap(LeftMax->_value, LeftMax->_value);
					return _EraseR(root->_left, key);
				}
			}
			else if (key > root->_key)
			{
				return _EraseR(root->_right, key);
			}
			else
			{
				return _EraseR(root->_left, key);
			}
		}
	};
  1. K模型可以用来判断在不在的场景。例如检查一篇英文文章的单词是否拼写正确,将词库的单词以及单词的各种变化读取到二叉树中,从文章读取单词,能够在二叉树找到,说明拼写正确,不在说明拼写错误。
  2. KV模型可以通过key找到value。例如从词库中读取英文单词以及对应的中文放到二叉树中,输入单词,查找对应的中文。
void test()
{
	BSTree dict;//词典
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("left", "左边、剩余");
	dict.Insert("right", "右边");
	dict.Insert("sort", "排序");
	// 插入词库中所有单词
	string str;
	while (cin>>str)
	{
		BSTreeNode* ret = dict.Find(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" <_value << endl;
		}
	}
}

4. 练习

  1. 根据二叉树创建字符串
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 };
//本题的重点在于括号的省略
//1. 左边为空,右不为空。左边括号保留。
//2. 左边为空,右边为空。括号不保留。
//3. 左边不为空,右边为空。右边括号不保留。
class Solution {
public:
    string tree2str(TreeNode* root) {
        if(root==nullptr)
        {
            return "";
        }
        string str = to_string(root->val);
        if(root->left!=nullptr || root->right!=nullptr)
        {
            str+="(";
            str+=tree2str(root->left);
            str+=")";
        }
        if(root->right!=nullptr)
        {
            str+="(";
            str+=tree2str(root->right);
            str+=")";
        }      
        return str;
    }
};
  1. 二叉树的最近公共祖先

法1

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 };
class Solution {
public:
    bool FindTreeNode(TreeNode*root,TreeNode*x)
    {
        if(root==nullptr)
        {
            return false;
        }
        if(root->val==x->val)
        {
            return true;
        }   
        return FindTreeNode(root->left,x)||FindTreeNode(root->right,x);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //思路:如果root是p和q的公共节点,那么p和q一定在root的左右子树,如果在同一棵树,判断在哪棵树,进行递归,直到找到为止。
        if(root==p||root==q)
        {
            return root;
        }
        if(root == nullptr)
        {
            return nullptr;
        }
        //判断p在哪棵树
        int pInLeft = FindTreeNode(root->left,p);
        int pInRight = FindTreeNode(root->right,p);
        //判断q在哪棵树
        int qInLeft = FindTreeNode(root->left,q);
        int qInRight = FindTreeNode(root->right,q);
        //
        if(pInLeft==1 && pInLeft == qInLeft)
        {
            return lowestCommonAncestor(root->left,p,q);
        }
        else if(pInRight ==1 && pInRight == qInRight)
        {
            return lowestCommonAncestor(root->right,p,q);
        }
        else
        {
            return root;
        }
    }
};
//这种方法适用于普通二叉树,时间复杂度是O(N)。
//若是二叉搜索树,则只需比较大小,就能判断两个节点是在左树还是右树,
//不用遍历找节点,时间复杂度是O(N)。

法2

//思路:将问题转换成链表的相交问题,用两个栈来存储p和q的路径,再找出第一个相交的节点。
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //创建两个栈,用来存放p和q的两条路径
        stack pPath;
        stack qPath;
        //找到p和q的节点前,将路径上的节点入栈
        FindPath(pPath,root,p);
        FindPath(qPath,root,q);
        //将两条路径变得一样长
        while(pPath.size()>qPath.size())
        {
            pPath.pop();
        }
        while(pPath.size()& Path,TreeNode*& root,TreeNode* x)
    {
        if(root==nullptr)
        {
            return false;
        }
        //先进栈,再判断
        //因为root可能是路径上的节点
        Path.push(root);
        if(root == x)
        {
            return true;
        }
        //往左子树找
        if(FindPath(Path,root->left,x))
        {
            return true;
        }
        //往右子树找
        if(FindPath(Path,root->right,x))
        {
            return true;
        }
        //找不到就将节点弹出
        Path.pop();
        return false;
    }
};
  1. 二叉搜索树转换成双向循环链表
struct TreeNode 
{
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) 
	:val(x)
	,left(NULL)
	,right(NULL) 
	{}
};
class Solution {
public:
	//prev记得引用接收,因为prev要发生变化
	void inorder(TreeNode*root,TreeNode*&prev)
	{
		if(root==nullptr)
		{
			return;
		}
		inorder(root->left,prev);
		root->left = prev;
		//将上一个与现在链接起来,如果是null就不用
		if(prev)
		{
			prev->right = root;
		}
		prev = root;
		inorder(root->right,prev);
	}

    TreeNode* Convert(TreeNode* pRootOfTree) {
        //有序->中序遍历
		//在双向链表中,left表示prev,right表示next
		//在中序遍历中,顺便链接双向链表
		//来一个prev记录前一个节点
		TreeNode*prev = nullptr;
		inorder(pRootOfTree,prev);
		TreeNode*head = pRootOfTree;
		//还要防止pRootOfTree为null的情况
		while(head&&head->left)
		{
			head = head->left;
		}
		return head;
    }
};
  1. 从前序和中序列构造二叉树
class Solution {
public:
    TreeNode*build(vector&preorder,vector&inorder,int& previ,int inbegin, int inend)
    {
        if(inbegin>inend)
        {
            return nullptr;
        }
        
        TreeNode* root = new TreeNode(preorder[previ]);
        
        int rooti = 0;
        while(inorder[rooti]!=preorder[previ])
        {
            ++rooti;
        }
        ++previ;
        root->left = build(preorder,inorder,previ,inbegin,rooti-1);
        root->right = build(preorder,inorder,previ,rooti+1,inend);
        return root;
    }
    TreeNode* buildTree(vector& preorder, vector& inorder) {
        //思路:前序遍历确定根结点,用来构建二叉树
        //中序遍历用来分割区间
        //用递归来实现
        //细节:(1)需要一个下标来实时记录在前序列表中的位置,
        //方便遍历
        //(2)需要一个区间来分割中序列表
        int index = 0;
        int begin = 0;
        int end = inorder.size()-1;
        return build(preorder,inorder,index,begin,end);
    }
};
  1. 从中序和后序序列构造二叉树
class Solution {
public:

    TreeNode* build(vector&inorder,vectorpostorder,int& posti,int inbegin,int inend)
    {
        if(inbegin>inend)
        {
            return nullptr;
        }
        TreeNode*root = new TreeNode(postorder[posti]);
        int rooti = 0;
        while(inorder[rooti]!=postorder[posti])
        {
            ++rooti;
        }
        --posti;
        root->right = build(inorder,postorder,posti,rooti+1,inend);
        root->left = build(inorder,postorder,posti,inbegin,rooti-1);
        return root;
    }
    TreeNode* buildTree(vector& inorder, vector& postorder) {
        //思路:后序遍历:从后往前,可以确定根结点
        //中序可以用来分割
        //构造二叉树时,用一个从后往前的下标,且先构造右子树
        int begin = 0;
        int end = inorder.size()-1;
        int index = postorder.size()-1;
        return build(inorder,postorder,index,begin,end);
    }
};
  1. 二叉树的前序遍历(非递归)
class Solution {
public:
    vector preorderTraversal(TreeNode* root) {
        //思路:将这棵树分为左路节点和右子树,再创建一个栈,
        //将左路节点入栈,同时节点的键值加到vector,直到左路节点为空,
        //再出栈,从出栈节点开始,分为左路节点和右子树,继续入栈,
        //一直循环,直到栈为空
        vector vt;
        stack st;
        TreeNode*cur = root;

    while(cur||!st.empty())
    {
        while(cur)
        {
            vt.push_back(cur->val);
            st.push(cur);
            cur = cur->left;
        }
        TreeNode* tmp = st.top();
        st.pop();
        cur = tmp->right;
    }
        return vt;
    }
};

【C++】二叉搜索树_第2张图片

  1. 二叉树的中序遍历(非递归)
class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        //思路:依次将左路节点全部入栈后,再出栈将数据入vector,相当于先访问左子树,再访问右子树,一直循环,直到栈为空。
        vector vt;
        stack st;
        TreeNode*cur = root;
        while(cur||!st.empty())
        {
            while(cur)
            {
                //入栈
                st.push(cur);
                cur = cur->left;
            }
            TreeNode*tmp = st.top();
            vt.push_back(tmp->val);
            st.pop();
            cur = tmp->right;
        }
        return vt;
    }
};
  1. 二叉树的后序遍历(非递归)
class Solution {
public:
    vector postorderTraversal(TreeNode* root) {
        //该节点已经访问右子树,那么就将入vector
        //如果没有访问右子树就访问右子树。
        //用一个指针来表示有没有访问
        vector vt;
        stack st;
        TreeNode* cur = root;
        TreeNode* prev = nullptr;
        while(cur||!st.empty())
        {
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }
            TreeNode*tmp = st.top();
            //如果右子树为空,或者右子树已经访问,就直接入vector
            if(tmp->right==nullptr||prev==tmp->right)
            {
                vt.push_back(tmp->val);
                st.pop();
                prev = tmp;
            }
            else
            {
                cur = tmp->right;
                prev = tmp;
            }
        }
        return vt;
    }
};

你可能感兴趣的:(c++)