当前位置: 首页 > news >正文

二叉树进阶

1. 二叉搜索树

1.1 二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

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

二叉搜索树(BST,Binary Search Tree),也称二叉排序或二叉查找树。

image-20220908135214296

image-20220908135324550

1.2 二叉搜索树操作

image-20220908160648740

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
  1. 二叉搜索树的查找

    a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

    b、最多查找高度次,走到到空,还没找到,这个值不存在。

    代码:

    非递归版本:

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

    递归版本:

    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;
    	}
    }
    bool FindR(const K& key)
    {
    	return _FindR(_root, key);
    }
    
  2. 二叉搜索树的插入

    插入的具体过程如下:

    a. 树为空,则直接新增节点,赋值给root指针

    b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

    代码:

    非递归版本:

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

    image-20220908160955150

    递归版本:

    引用实现(更好一些):

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

    添加一个参数实现:

    bool _InsertR(Node* root, const K& key, Node* parent)
    {
    	
    	if (root == nullptr)
    	{
    		if (parent == nullptr)
    		{
    			_root = new Node(key);
    			return true;
    		}
    		if (key < parent->_key)
    		{
    			parent->_left = new Node(key);
    		}
    		else
    		{
    			parent->_right = new Node(key);
    		}
    		return true;
    	}
    	parent = root;
    	if (key > root->_key)
    	{
    		return _InsertR(root->_right, key, parent);
    	}
    	else if (key < root->_key)
    	{
    		return _InsertR(root->_left, key, parent);
    	}
    	else
    	{
    		return false;
    	}
    }
    
    bool InsertR(const K& key)
    {
    	return _InsertR(_root, key, nullptr);
    }
    
  3. 二叉树的删除

    首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

    a. 要删除的结点无孩子结点

    b. 要删除的结点只有左孩子结点

    c. 要删除的结点只有右孩子结点

    d. 要删除的结点有左、右孩子结点

    • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除

    • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除情况

    • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点 中,再来处理该结点的删除问题–替换法删除(寻找左子树的最大值节点或者右子树的最小值节点

      image-20220908190845512

      代码:

      非递归版本:

      bool Erase(const K& key)
      {
      	Node* cur = _root;
      	Node* parent = nullptr;
      	while (cur)
      	{
      		//下面是查找
      		if (cur->_key > key)
      		{
      			parent = cur;
      			cur = cur->_left;
      		}
      		else if (cur->_key < key)
      		{
      			parent = cur;
      			cur = cur->_right;
      		}
      		else//正式删除
      		{
      			//一个孩子左为空或者右为空
      
      			if (cur->_left == nullptr)//要删除的节点左子节点为空
      			{
      				if (_root == cur)//要删除的节点为根节点
      				{
      					_root = cur->_right;
      				}
      				else
      				{
      					if (cur == parent->_left)//要删除的节点在父节点的左边
      					{
      						parent->_left = cur->_right;
      					}
      					else//要删除的节点在父节点的右边
      					{
      						parent->_right = cur->_right;
      					}
      				}
      				delete cur;
      			}
      			else if (cur->_right == nullptr)//要删除的节点右子节点为空
      			{
      				if (_root == cur)//要删除的节点刚好为根节点
      				{
      					_root = cur->_left;
      				}
      				else
      				{
      					if (cur == parent->_left)//要删除的节点在父节点的左边
      					{
      						parent->_left = cur->_left;
      					}
      					else//要删除的节点在父节点的右边
      					{
      						parent->_right = cur->_left;
      					}
      				}
      				delete cur;
      			}
      			else//两个孩子都不为空的情况
      			{
      				Node* minParent = cur;
      				Node* minRight = cur->_right;//此处是选的右子树的最小节点
      				while (minRight->_left)
      				{
      					minParent = minRight;
      					minRight = minRight->_left;
      				}
      				swap(cur->_key, minRight->_key);
      				//Erase(key);
      				//问:为什么不能像上面这样写?答:因为交换之后,二叉搜索树就不符合二叉搜索树的规则了,递归会找不到key节点了
      				if (minParent->_left == minRight)
      				{
      					minParent->_left = minRight->_right;
      				}
      				else
      				{
      					minParent->_right = minRight->_right;
      				}
      				delete minRight;
      			}
      			return true;
      		}
      	}
      	return false;
      }
      

      image-20220908213223724

      image-20220908213258249

    image-20220908214008777

    从3和8两个节点中抽取处两种情况:

    image-20220909133133376

    递归版本:

    bool _EraseR(Node*& root, const K& key)
    {
    	if (root == nullptr)
    	{
    		return false;
    	}
    
    	if (key < root->_key)
    	{
    		return _EraseR(root->_left, key);
    	}
    	else if (key > root->_key)
    	{
    		return _EraseR(root->_right, key);
    	}
    	else
    	{
    		//删除
    		//注意:此时的root是上一个节点的左节点或者右节点的引用
    		Node* del = root;
    		if (root->_left == nullptr)
    		{
    			root = root->_right;
    		}
    		else if (root->_right == nullptr)
    		{
    			root = root->_left;
    		}
    		else
    		{
    			Node* minRight = root->_right;
    			while (minRight->_left)
    			{
    				minRight = minRight->_left;
    			}
    			swap(minRight->_key, root->_key);
    			return _EraseR(root->_right, key);
    		}
    		delete del;
    		return true;
    	}
    }
    bool EraseR(const K& key)
    {
    	return _EraseR(_root, key);
    }
    
  4. 搜索二叉树的构造函数、拷贝析构函数/析构函数和赋值运算符重载

    void DestoryTree(Node* root)
    {
    	if (root == nullptr)
    		return;
    	DestoryTree(root->_left);
    	DestoryTree(root->_right);
    	delete root;
    }
    Node* CopyTree(Node* root)
    {
    	if (root == nullptr)
    		return nullptr;
    	Node* copyNode = new Node(root->_key);
    	copyNode->_left = CopyTree(root->_left);
    	copyNode->_right = CopyTree(root->_right);
    	return copyNode;
    }
    //构造函数
    BSTree()
    	:_root(nullptr)
    {}
    //拷贝构造函数:深拷贝
    BSTree(const BSTree<K>& b)
    {
    	_root = CopyTree(b._root);
    }
    //析构函数
    ~BSTree()
    {
    	DestoryTree(_root);
    	_root = nullptr;
    }
    //赋值运算符重载
    BSTree<K>& operator=(BSTree<K> b)
    {
    	swap(_root, b._root);
    	return (*this);
    }
    //t1 = t2
    //问:为什么此处的参数要使用值拷贝?
    //答:此处调用了构造函数(BSTree<K> b(t2)),即使用t2来构造了一个b对象,然后通过交换b和t1的根节点即可实现我们的目标
    

    注意:如果我们不想写构造函数,那么我们可以使用下面的这条指令来强制编译器自己生成构造

    BSTree() = default;//C++11才支持的,C++98不支持
    

1.3 二叉搜索树的模拟实现

代码:

#include<iostream>
using namespace std;
template<class K>
//struct BinarySearchTreeNode
struct BSTreeNode
{
	BSTreeNode(const K& key)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
	{}
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;

	K _key;
};

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
private:
	void DestoryTree(Node* root)
	{
		if (root == nullptr)
			return;
		DestoryTree(root->_left);
		DestoryTree(root->_right);
		delete root;
	}
	Node* CopyTree(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* copyNode = new Node(root->_key);
		copyNode->_left = CopyTree(root->_left);
		copyNode->_right = CopyTree(root->_right);
		return copyNode;
	}
	//二叉搜索树的查找:递归版本子函数
	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;
		}
	}


	//二叉搜索树的插入:递归版本子函数
	bool _InsertR(Node*& root, const K& key)
	{

		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		if (key > root->_key)
		{
			return _InsertR(root->_right, key);
		}
		else if (key < root->_key)
		{
			return _InsertR(root->_left, key);
		}
		else
		{
			return false;
		}
	}
	//二叉搜索树的删除:递归版本子函数
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;
		}

		if (key < root->_key)
		{
			return _EraseR(root->_left, key);
		}
		else if (key > root->_key)
		{
			return _EraseR(root->_right, key);
		}
		else
		{
			//删除
			//注意:此时的root是上一个节点的左节点或者右节点的引用
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* minRight = root->_right;
				while (minRight->_left)
				{
					minRight = minRight->_left;
				}
				swap(minRight->_key, root->_key);
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

public:


	//构造函数
	BSTree()
		:_root(nullptr)
	{}
	//拷贝构造函数:深拷贝
	BSTree(const BSTree<K> & b)
	{
		_root = CopyTree(b._root);
	}
	//析构函数
	~BSTree()
	{
		DestoryTree(_root);
		_root = nullptr;
	}
	//t2 = t1
	//赋值运算符重载
	BSTree<K>& operator=(BSTree<K> b)
	{
		swap(_root, b._root);
		return (*this);
	}
	//二叉搜索树的插入:非递归
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* cur = _root;
		Node* parant = nullptr;
		while (cur)
		{
			if (cur->_key > key)
			{
				parant = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parant = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (key < parant->_key)
		{
			parant->_left = cur;
		}
		else
		{
			parant->_right = cur;
		}
		return true;
	}
	//二叉搜索树的插入:递归
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
	}
	//二叉搜索树的查找:非递归
	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
	//二叉搜索树的查找:递归
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	//二叉搜索树的删除:非递归
	bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			//下面是查找
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//正式删除
			{
				//一个孩子左为空或者右为空

				if (cur->_left == nullptr)//要删除的节点左子节点为空
				{
					if (_root == cur)//要删除的节点为根节点
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)//要删除的节点在父节点的左边
						{
							parent->_left = cur->_right;
						}
						else//要删除的节点在父节点的右边
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				else if (cur->_right == nullptr)//要删除的节点右子节点为空
				{
					if (_root == cur)//要删除的节点刚好为根节点
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)//要删除的节点在父节点的左边
						{
							parent->_left = cur->_left;
						}
						else//要删除的节点在父节点的右边
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else//两个孩子都不为空的情况
				{
					Node* minParent = cur;
					Node* minRight = cur->_right;//此处是选的右子树的最小节点
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					swap(cur->_key, minRight->_key);
					//Erase(key);
					//问:为什么不能像上面这样写?答:因为交换之后,二叉搜索树就不符合二叉搜索树的规则了,递归会找不到key节点了
					if (minParent->_left == minRight)
					{
						minParent->_left = minRight->_right;
					}
					else
					{
						minParent->_right = minRight->_right;
					}
					delete minRight;
				}
				return true;
			}
		}
		return false;
	}
	//二叉搜索树的删除:递归
	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}


private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	Node* _root = nullptr;
};

1.4 二叉搜索树的应用

  1. K模型K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即键值对。该种方式在现实生活中非常常见:
    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对;
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对
template<class K, class V>
struct BSTreeNode
{
	BSTreeNode(const K& key, const V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		, _value(value)
	{}
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;

	K _key;
	V _value;
};

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
private:
public:




	//二叉搜索树的插入:非递归
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}

		Node* cur = _root;
		Node* parant = nullptr;
		while (cur)
		{
			if (cur->_key > key)
			{
				parant = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parant = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key, value);
		if (key < parant->_key)
		{
			parant->_left = cur;
		}
		else
		{
			parant->_right = cur;
		}
		return true;
	}

	//中序遍历
	void InOrder()
	{
		_InOrder(_root);
	}
	//二叉搜索树的查找:非递归
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	//二叉搜索树的删除:非递归
	bool Erase(const K& key)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			//下面是查找
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else//正式删除
			{
				//一个孩子左为空或者右为空

				if (cur->_left == nullptr)//要删除的节点左子节点为空
				{
					if (_root == cur)//要删除的节点为根节点
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)//要删除的节点在父节点的左边
						{
							parent->_left = cur->_right;
						}
						else//要删除的节点在父节点的右边
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				else if (cur->_right == nullptr)//要删除的节点右子节点为空
				{
					if (_root == cur)//要删除的节点刚好为根节点
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)//要删除的节点在父节点的左边
						{
							parent->_left = cur->_left;
						}
						else//要删除的节点在父节点的右边
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else//两个孩子都不为空的情况
				{
					Node* minParent = cur;
					Node* minRight = cur->_right;//此处是选的右子树的最小节点
					while (minRight->_left)
					{
						minParent = minRight;
						minRight = minRight->_left;
					}
					swap(cur->_key, minRight->_key);
					//Erase(key);
					//问:为什么不能像上面这样写?答:因为交换之后,二叉搜索树就不符合二叉搜索树的规则了,递归会找不到key节点了
					if (minParent->_left == minRight)
					{
						minParent->_left = minRight->_right;
					}
					else
					{
						minParent->_right = minRight->_right;
					}
					delete minRight;
				}
				return true;
			}
		}
		return false;
	}


private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	Node* _root = nullptr;
};

void TestBSTree1()
{
	// 输入单词,查找单词对应的中文翻译
	key_value::BSTree<string, string> dict;
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("left", "左边、剩余");
	dict.Insert("right", "右边");
	dict.Insert("sort", "排序");
	// 插入词库中所有单词
	string str;
	while (cin >> str)
	{
		key_value::BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		}
		else
		{
			cout << str << "中文翻译:" << ret->_value << endl;
		}
	}
}
void TestBSTree2()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
   "苹果", "香蕉", "苹果", "香蕉" };
	key_value::BSTree<string, int> countTree;
	for (const auto& str : arr)
	{
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		// 2、在,则查找到的节点中水果对应的次数++
		//BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

问:while(cin >> str)代码中的cin >> str为什么能当判断条件?

答:image-20220910123832861

image-20220910124131278

image-20220910124116868

上面的两个函数的意思是istream类型的对象可以转换为void*或者bool,即上面的代码能够作为判断条件的原因就是调用了这个函数。

cin.operator bool();

我们也可以使用上面的方式对自定义类进行类似的函数重载:

class A
{
public:
	operator bool()const
	{
		return true;
	}
};
int main()
{
	A a;
	if (a)
	{
		cout << "hello " << endl;
	}
	return 0;
}

运行结果:

image-20220910125523465

问:为什么不用()作为强制类型转换运算符重载?

答:因为()已经被仿函数使用了。

问:explict是什么意思?
答:是为了防止隐式类型转换发生的。例如我们这样定义A类:

class A
{
public:
	explict operator bool()const
	{
		return true;
	}
};

就不能这样写代码:

A a;
bool ret = a;//此处无法发生从类型A到bool类型的隐式类型的转换。

1.5 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

image-20220910143443894

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N

2. 二叉树进阶面试题

  1. 二叉树创建字符串。

    image-20220910150150447

    代码:

    //方法一:存在多处string拷贝构造,所以并不是特别好
    class Solution {
    public:
        string tree2str(TreeNode* root) {
            if(root == nullptr)
                return "";
            string str;
            str += to_string(root->val);
            //左不为空
            if(root->left)
            {
                str += "(";
                str += tree2str(root->left);
                str += ")";
            }
            
            //右不为空
            if(root->right)
            {
                if(root->left == nullptr)//左为空且右不为空的情况,此时不能省略左子树的()
                    str += "()";
                
                str += "(";
                str += tree2str(root->right);
                str += ")";
            }
    
            return str;
        }
    };
    //方法二:
    class Solution {
    public:
        void _tree2str(TreeNode* root, string& str) {
            if(root == nullptr)
                return;
            str += to_string(root->val);
            //左不为空
            if(root->left)
            {
                str += "(";
                _tree2str(root->left, str);
                str += ")";
            }
            
            //右不为空
            if(root->right)
            {
                if(root->left == nullptr)//左为空右不为空的情况
                    str += "()";
                
                str += "(";
                _tree2str(root->right, str);
                str += ")";
            }
        }
    
        string tree2str(TreeNode* root)
        {
            string ret;
            _tree2str(root, ret);
            return ret;
        }
    };
    
  2. 二叉树的分层遍历1

    image-20220910162827170

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) 
        {
            vector<vector<int>> vv;
            if(root == nullptr)
                return vv;
            queue<TreeNode*> q;
            q.push(root);
            int levelSize = 1;//levelSize控制每层节点的个数
            while(!q.empty())
            {
                vector<int> levelV;//存储每一层的节点存储的值
                while(levelSize--)//当levelSize为0时就说明当前这一层已经没有节点了
                {
                    TreeNode* front = q.front();
                    levelV.push_back(front->val);
                    if(front->left != nullptr)
                    {
                        q.push(front->left);
                    }
                    if(front->right != nullptr)
                    {
                        q.push(front->right);
                    }
                    q.pop();
                }
                levelSize = q.size();
                vv.push_back(levelV);
            }
            return vv;
        }
    };
    
  3. 二叉树的分层遍历2

    image-20220910163302798

    image-20220910165128630

    代码:

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) 
        {
            vector<vector<int>> vv;
            if(root == nullptr)
                return vv;
            queue<TreeNode*> q;
            q.push(root);
            int levelSize = 1;//levelSize控制每层节点的个数
            while(!q.empty())
            {
                vector<int> levelV;//存储每一层的节点存储的值
                while(levelSize--)//当levelSize为0时就说明当前这一层已经没有节点了
                {
                    TreeNode* front = q.front();
                    levelV.push_back(front->val);
                    if(front->left != nullptr)
                    {
                        q.push(front->left);
                    }
                    if(front->right != nullptr)
                    {
                        q.push(front->right);
                    }
                    q.pop();
                }
                levelSize = q.size();
                vv.push_back(levelV);
            }
            return vv;
        }
    };
    
  4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先

    image-20220910163452741

    代码:

    class Solution {
    public:
        //判断x节点是否在root这颗树中
        bool IsInSubtree(TreeNode* root, TreeNode* x)
        {
            if(root == nullptr)
                return false;
            
            if(root == x)
                return true; 
    
            return IsInSubtree(root->left, x) || IsInSubtree(root->right, x);
        }
    
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if(root == nullptr)
                return nullptr;
            
            if(root == p || root == q)//判断的是某个节点就是它们的最近公共祖先的情况
                return root;
            bool pInLeft = IsInSubtree(root->left, p);
            bool pInRight = !pInLeft;
    
            bool qInLeft = IsInSubtree(root->left, q);
            bool qInRight = !qInLeft;
            if((pInLeft && qInRight) || (pInRight && qInLeft))//是最近公共祖先的情况
                return root;
            else if(pInLeft && qInLeft)//两个节点都在左子树的情况
            {
                return lowestCommonAncestor(root->left, p, q);
            }
            else if(pInRight && qInRight)//两个节点都在右子树的情况
            {
                return lowestCommonAncestor(root->right, p, q);
            }
            else
            {
                return nullptr;
            }
        }
    };
    

    总共有两种情况:

    情况一(正常情况):

    image-20220910195513817

    情况二(非正常情况):

    image-20220910195832922

    上面代码的时间复杂度非常高,能达到O(n2),

    下面是能够让时间复杂度能够到达O(n)的方法:

    • 该树为搜索树

    • 该树为三叉树

    • 方法如下:

      图示:

      image-20220910221847819

      image-20220910221857951

      代码:

      class Solution {
      public:
          bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
          {
              if(root == nullptr)
              {
                  return false;
              }
              path.push(root);
      
              if(root == x)
              {
                  return true;
              }
      
              if(FindPath(root->left, x, path))
                  return true;
      
              if(FindPath(root->right, x, path))
                  return true;
              //root不是要找的节点,左子树和右子树都没有找到,那么root不是x的路径中的节点,要出栈
              path.pop();//对应前面的push
              return false;
             
          }
          TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
          {
              stack<TreeNode*> pathP;
              stack<TreeNode*> pathQ;
              FindPath(root, p, pathP);
              FindPath(root, q, pathQ);
              //路径长的先走
              while(pathP.size() > pathQ.size())
              {
                  pathP.pop();
              }
              while(pathP.size() < pathQ.size())
              {
                  pathQ.pop();
              }
              while(pathQ.top() != pathP.top())
              {
                  pathQ.pop();
                  pathP.pop();
              }        
              return pathP.top();
          }
      };
      
  5. 二叉树搜索树转换成排序双向链表

    image-20220911120833636

图示:

image-20220911121141377

代码:

class Solution {
public:
  void InOrderConvert(TreeNode* cur, TreeNode*& prev)//通过中序遍历的方式进行转换
  {
      if(cur == nullptr)
          return;
      InOrderConvert(cur->left, prev);//左子树
      cur->left = prev;
      if(prev)
          prev->right = cur;
      prev = cur;//根节点
      InOrderConvert(cur->right, prev);//右子树
  }
  TreeNode* Convert(TreeNode* pRootOfTree) {
      if(pRootOfTree == nullptr)//当所给的树为空的时候
          return nullptr;
      TreeNode* prev = nullptr;
      InOrderConvert(pRootOfTree, prev);
      TreeNode* head = pRootOfTree;
      
      while(head->left)//找到链表的起始节点
      {
          head = head->left;
      }
      return head;
  }
};
  1. 根据一棵树的前序遍历与中序遍历构造二叉树

    image-20220911122327935

    代码:

    class Solution {
    public:
        TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int inBegin, int inEnd)
        {
            //中序区间不存在,说明递归创建的子树是空
            if(inBegin > inEnd)
                return nullptr;
            TreeNode* root = new TreeNode(preorder[prei]);
            ++prei;
            size_t rooti = inBegin;
            while(rooti <= inEnd)
            {
                if(root->val == inorder[rooti])
                    break;
                else 
                    rooti++; 
            }
            //[inBegin, rooti - 1]rooti[rooti + 1, inEnd]
            root->left = _buildTree(preorder, inorder, prei, inBegin, rooti - 1);
            root->right = _buildTree(preorder, inorder, prei, rooti + 1, inEnd);
            return root;
            
        }
        TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
            int prei = 0;
            TreeNode* root = _buildTree(preorder, inorder, prei, 0, inorder.size() - 1);
            return root;
        }
    };
    
  2. 根据一棵树的中序遍历与后序遍历构造二叉树

    class Solution {
    private:
        TreeNode* build(vector<int>& inorder, vector<int>& postorder,int root,int start,int end){
            if(start>end){
                return nullptr;
            }
            //创建根节点
            TreeNode* newTree = new TreeNode(postorder[root]);
            int i = start;
            while((i < end) && inorder[i] != postorder[root]) 
                i++;
            newTree->left = build(inorder,postorder,root - 1 - (end - i),start,i-1);
            newTree->right = build(inorder,postorder,root-1,i+1,end);
            return newTree;
        }
    
    public:
        TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
            int n = postorder.size();
            return build(inorder,postorder,n-1,0,n-1);
        }
    };
    
  3. 二叉树的前序遍历,非递归迭代实现

    image-20220911145033526

    图示:

    image-20220911153050543

    代码:

    class Solution {
    public:
        vector<int> preorderTraversal(TreeNode* root) {
            vector<int> v;
            stack<TreeNode*> s;
            TreeNode* cur = root;
            while(cur || !s.empty())
            {
                //遍历左路节点,左路节点入栈 -- 访问一棵树的开始
                while(cur)
                {
                    v.push_back(cur->val);
                    s.push(cur);
                    cur = cur->left;
                }
                //此时cur为空
                //依此取左路节点的右子树访问
                TreeNode* top = s.top();
                s.pop();
                //访问左路节点的右子树:子问题
                cur = top->right;
            }
            return v;
        }
    };
    
  4. 二叉树中序遍历 ,非递归迭代实现

    思路和上一个题类似,改变的只是访问根节点的位置。

    代码:

    class Solution {
    public:
        vector<int> inorderTraversal(TreeNode* root) {
            vector<int> v;
            stack<TreeNode*> s;
            TreeNode* cur = root;
            while(cur || !s.empty())
            {
                //遍历左路节点,左路节点入栈 -- 访问一棵树的开始
                while(cur)
                {
                    s.push(cur);
                    cur = cur->left;
                }
                //此时cur为空
                //依此取左路节点的右子树访问
                TreeNode* top = s.top();
                s.pop();
                
                v.push_back(top->val);
                //访问左路节点的右子树:子问题
                cur = top->right;
            }
            return v;
        }
    };
    
  5. 二叉树的后序遍历 ,非递归迭代实现

    image-20220911162950021

    图示:

    image-20220911163447393

    代码:

    class Solution {
    public:
        vector<int> postorderTraversal(TreeNode* root) {
    
            vector<int> v;
            stack<TreeNode*> s;
            TreeNode* cur = root;
            TreeNode* prev = nullptr;//记录上一个访问过的节点
            while(cur || !s.empty())
            {
                while(cur)
                {
                    s.push(cur);
                    cur = cur->left;
                }
                TreeNode* top = s.top();
                //1、左子树已经访问过了,如果top的右子树为空或者右子树已经被访问
                //2、top->right != nullptr且右子树还没有访问,子问题迭代访问
                if(top->right == nullptr || top->right == prev)
                {
                    v.push_back(top->val);
                    s.pop();
                    prev = top;
                }
                else
                {
                    cur = top->right;
                }
            }
            return v;
        }
    };
    

相关文章:

  • 【leetcode刷题日记】lc.560-和为 K 的子数组
  • 深入解析 JVM 内存区域及核心概念
  • 掌握Linux项目自动化构建:从零入门make与Makefile
  • 基于LLM的Agent框架全面比较分析:MGX(MetaGPT X)、AutoGen、OpenHands与秒哒(MiaoDa)
  • [C++面试] span<char>和string_view的差别
  • MySQL数据库入门
  • 【JavaScript】金丹期功法
  • LLM动态Shape实现原理与核心技术
  • 【银河麒麟系统常识】命令:dotnet run(运行)
  • 远程医疗的现状如何?
  • C++ 之 SOCKET 通信详解
  • “自动驾驶背后的数学” 专栏导读
  • 推陈换新系列————java8新特性(编程语言的文艺复兴)
  • 【现代深度学习技术】现代卷积神经网络04:含并行连接的网络(GoogLeNet)
  • 每日总结3.26
  • 算法题(108):
  • IM腾讯Trtc与vod云点播:实现合流录制并上传,根据参数返回视频地址
  • JSON简介及C++中的JSON使用指南
  • LangChain4j(1):初识LangChain4j
  • 【Linux】POSIX信号量与基于环形队列的生产消费者模型
  • 以军证实空袭也门多个港口
  • 阿里上财年营收增6%,蒋凡:会积极投资,把更多淘宝用户转变成即时零售用户
  • 百色一女子称家委会强制排班被迫抱婴儿校门口站岗?区教育局:自愿参与
  • 视频丨中国海警成功救助8名外籍遇险渔民,韩方向中方致谢
  • 夜读丨读《汉书》一得
  • 陕西河南山西等地将现“干热风”灾害,小麦产区如何防范?