CD66.【C++ Dev】模拟实现二叉搜索树的非递归版本

目录

1.知识回顾

2.练习:在二叉搜索树中查找指定值

3.模拟实现二叉搜索树

框架

初始化

二叉搜索树的构造函数

节点的构造函数

插入节点,不能插入相同的key

中序遍历

查找节点

★删除节点

画图解释删除的if判断

一般情况

特殊情况

4.练习1: [LintCode]删除二叉查找树的节点

代码

提交结果

6.练习2: [LintCode]在二叉查找树中插入节点

代码

提交结果

5.练习3: 使用二叉搜索树排序

代码

提交结果

6.对比二叉搜索树和有序数组


1.知识回顾

参见CC40.【C++ Cont】二叉搜索树和平衡二叉树文章复习基础知识

2.练习:在二叉搜索树中查找指定值

https://www.lintcode.com/problem/1524/?_from=problem_tag&fromId=358

给定一颗二叉搜索树和 value.

返回这棵树中值等于 value 的节点. 如果不存在这样的节点, 返回 null.

样例 1:

输入: value = 2
        4
       / \
      2   7
     / \
    1   3
输出: 节点 2

样例 2:

输入: value = 5
        4
       / \
      2   7
     / \
    1   3
输出: null

分析:

比较要找的值和根节点的大小关系,沿着二叉搜索树的边遍历就行

代码:

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */

class Solution {
public:
    /**
     * @param root: the tree
     * @param val: the val which should be find
     * @return: the node
     */
    TreeNode* searchBST(TreeNode *root, int val) 
    {
        while(root)
        {
            int root_val=root->val;
            if (root_val==val)
                return root;
            if (root_val<val)
                root=root->right;
            else
                root=root->left;
        }
        return nullptr;
    }
};

提交结果:

3.模拟实现二叉搜索树

框架

构建二叉搜索树类和节点类

template<class K>
class Node
{
public:
	Node<K>* left;
    Node<K>* right;    
	K key;
};
template<class K>
class Binary_Search_Tree
{
	typedef Binary_Search_Tree<K> Node;
public:
	//......
private:
	Node* root;
};

初始化

二叉搜索树的构造函数

Binary_Search_Tree()
	:root(nullptr)
{ }

节点的构造函数

Node(const K& val)
	:left(nullptr)
	,right(nullptr)
	,key(val)
{ }

插入节点,不能插入相同的key

首先要讨论是否为空树,如果为空是无法遍历树的

bool insert(const K& val)
{
	//分根节点是否为空讨论
	if (root)
	{
		//......
	}
	else
	{
		root = new Node<K>(val);
        return true;
	}
}

当树不为空时,寻找合适的位置,让cur指针遍历

Node<K>* cur = root;
while (cur)
{
	if (cur->key > val)
	{
		cur = cur->left;
	}
	else if (cur->key < val)
	{
		cur = cur->right;
	}
	else//cur->key == val,不能插入相同数字
	{
		return false;
	}
}

当cur为nullptr时,退出循环,但找不到cur对应的根节点,因此还需要一个指针来指向cur的根节点,即双指针:

Node<K>* cur = root;
//一开始cur指向树的根节点时,cur是没有父节点的,可以理解为nullptr,当然值也可以不设置
Node<K>* parent = nullptr;
while (cur)
{
	if (cur->key > val)
	{
		parent = cur;
		cur = cur->left;
	}
	else if (cur->key < val)
	{
		parent = cur;
		cur = cur->right;
	}
	else//cur->key == val,不能插入相同数字
	{
		return false;
	}
}
return true;

退出循环后,还需要确定parent的左节点还是右节点能插入val

cur = new Node<K>(val);
//此时不可能有parent->val == val,前面排除过了
if (parent->key < val)
	parent->right = cur;
else
	parent->left = cur;

完整代码:
 

bool insert(const K& val)
{
	//分根节点是否为空讨论
	if (root)
	{
		Node<K>* cur = root;
		Node<K>* parent = nullptr;//一开始cur指向树的根节点时,cur是没有父节点的
		while (cur)
		{
			if (cur->key > val)
			{
				parent = cur;
				cur = cur->left;
			}
			else if (cur->key < val)
			{
				parent = cur;
				cur = cur->right;
			}
			else//cur->key == val,不能插入相同数字
			{
				return false;
			}
		}
		cur = new Node<K>(val);
		//此时不可能有parent->val == val,前面排除过了
		if (parent->key < val)
			parent->right = cur;
		else
			parent->left = cur;
	}
	else
	{
		root = new Node<K>(val);	
	}
	return true;
}

中序遍历

中序遍历的复习参见106.【C语言】数据结构之二叉树的三种递归遍历方式文章,该文章提供的代码段为:

void InOrder(BTNode* root)
{
	//先判断是否为空树(叶节点的左节点和右节点均为空树)
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
 
	//按左子树-->根-->右子树的顺序遍历
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

但不能直接当做成员函数print_inoder,因为root是私有变量,调用print_inoder函数是不能传参的,因此需要将InOrder写成子函数,然后print_inoder调用InOrder

完整代码:

	void	print_inoder()
	{
		InOrder(root);
	}

private:
	void InOrder(Node<K>* root)
	{
		//先判断是否为空树(叶节点的左节点和右节点均为空树)
		if (root == NULL)
			return;

		//按左子树-->根-->右子树的顺序遍历
		InOrder(root->left);
		cout<< root->key<<" ";
		InOrder(root->right);
	}
	Node<K>* root;

测试代码:

#include "BinarySearchTree.h"
int main()
{
	Binary_Search_Tree<int> bst;
	bst.insert(1);
	bst.insert(9);
	bst.insert(10);
	bst.insert(5);
	bst.insert(0);
	bst.insert(7);
	bst.insert(8);
	bst.insert(2);
	bst.insert(5);
	bst.insert(6);
	bst.insert(3);
	bst.insert(4);
	bst.print_inoder();
	return 0;
}

运行结果:中序遍历是有序的

查找节点

借鉴插入节点的部分代码:

bool find(const K& val)
{
	Node<K>* cur = root;
	while (cur)//cur!=nullptr
	{
		if (cur->key > val)
			cur = cur->left;
		else if (cur->key < val)
			cur = cur->right;
		else //cur->key == val)
			return true;
	}
	return false;
}

测试代码:
 

#include "BinarySearchTree.h"
int main()
{
	Binary_Search_Tree<int> bst;
	for (int i=0;i<=10;i++)
		bst.insert(i);
	if (bst.find(2))
		cout << "已找到2" << endl;
	else
		cout << "未找到2" << endl;
	if (bst.find(11))
		cout << "已找到11" << endl;
	else
		cout << "未找到11" << endl;
	return 0;
}

运行结果:

★删除节点

提醒:删除的节点的key可能不存在

首先要找到节点,借鉴find成员函数的代码

//先找节点
Node<K>* cur = root;
//一开始cur指向树的根节点时,cur是没有父节点的,可以理解为nullptr,当然值也可以不设置
Node<K>* parent = nullptr;
while (cur)
{
	if (cur == nullptr)//节点不存在
		return false;
	if (cur->key > val)
	{
		parent = cur;
		cur = cur->left;
	}
	else if (cur->key < val)
	{
		parent = cur;
		cur = cur->right;
	}
	else if (cur->key == val)
	{
		//删除节点......
		return true;
	}
}
//cur为nullptr,没找到
return false;
}

再删除节点,分类讨论: 没有子树、有一个子树、有两个子树

没有子树: 直接delete该节点,然后修改parent->left或parent->right

if (cur->left == cur->right)//只有cur->left==cur->right==nullptr这一种情况
{
    if (parent->left == cur)
		parent->left = nullptr;
	else
		parent->right = nullptr;
}

//......
delete cur;

有一个子树: 要分类讨论是左子树还是右子树

例如要删除3:

循环结束后parent和cur的指向为:

直接将parent->right设置为cur->right

//让删除的节点的左子树或右子树直接替代其位置,需要parent
if (cur->left == nullptr)
{
	if (parent->right == cur)
		parent->right = cur->right;
	else
		parent->left = cur->right;
}

这样做的前提是parent不为空,如果cur==root是需要单独操作的!

else//有一棵子树
{
	//让删除的节点的左子树或右子树直接替代其位置,需要parent	
	if (cur->left == nullptr)
	{
		if (cur == root)
		{
			root = cur->right;
		}
		if (parent->right == cur)
			parent->right = cur->right;
		else
			parent->left = cur->right;
	}
	if (cur->right == nullptr)
	{
		if (cur == root)
		{
			root = cur->left;
		}
		if (parent->right == cur)
			parent->right = cur->left;
		else
			parent->left = cur->left;
	}
}

有两个子树: 

选择CC40.【C++ Cont】二叉搜索树和平衡二叉树文章的这个方法: 用左子树的最大节点的值替换,再删除左子树最大节点

先找最大节点:

Node<K>* left_max = cur->left;
while (left_max->right)
{
	left_max = left_max->right;
}

循环结束时,left_max指向cur的左子树中的最大节点

删除节点的错误做法:让left_max和要删除的节点的key值交换,然后删除left_max指向的节点

//错误做法:
//swap(cur->key,left_max->key);
//erase(left_max);

交换后,left_max指向的节点会找不到的!

例如删除以下二叉搜索树的8:

8的左子树的最大节点是5,那么有:

正确做法:不能直接删除,交换后要借助left_max的父节点parent,而且parent的初始值为cur,不能为nullptr

//用左子树的最大节点的值替换,再删除左子树最大节点
Node<K>* left_max = cur->left;
Node<K>* parent = cur;
while (left_max->right)
{
		parent = left_max;
		left_max = left_max->right;
}
swap(cur->key, left_max->key);
if (parent->left==left_max)
	parent->left = left_max->left;//left_max->right为空,不可能有parent->left=left_max->left
else
	parent->right = left_max->left;
delete left_max;//交换过了,所以不能删除cur,要删除left_max
return true;

如果parent的初始值为nullptr,那么这个特殊情况会出问题:

例如删除以下二叉搜索树的5,left_max指向3,cur指向5,parent为nullptr,则if (parent->left==left_max)为if (nullptr->left==left_max),空指针无法访问会报错!

画图解释删除的if判断

一般情况

删除3:

解析:

特殊情况

1.删除5:

解析:

2.整个二叉树只有一个节点

3.整个二叉树只有两个节点

4.是空树

完整代码:

	bool erase(const K& val)
	{
		//先找节点
		Node<K>* cur = root;
		//一开始cur指向树的根节点时,cur是没有父节点的,可以理解为nullptr,当然值也可以不设置
		Node<K>* parent = nullptr;
		while (cur)
		{
			if (cur == nullptr)//节点不存在
				return false;
			if (cur->key > val)
			{
				parent = cur;
				cur = cur->left;
			}
			else if (cur->key < val)
			{
				parent = cur;
				cur = cur->right;
			}
			else // (cur->key == val)
			{
				if (cur->left == cur->right)//只有cur->left==cur->right==nullptr这一种情况
				{
					if (parent == nullptr)//该二叉搜索树只有一个节点,特殊情况
					{
						delete cur;
						return nullptr;
					}
					if (parent->left == cur)
						parent->left = nullptr;
					else
						parent->right = nullptr;
					delete cur;
					return true;
				}
				else if (cur->left && cur->right)//有两棵子树
				{
					//用左子树的最大节点的值替换,再删除左子树最大节点
					Node<K>* left_max = cur->left;
					Node<K>* parent = cur;
					while (left_max->right)
					{
						parent = left_max;
						left_max = left_max->right;
					}
					swap(cur->key, left_max->key);
					if (parent->left==left_max)
						parent->left = left_max->left;//left_max->right为空,不可能有parent->left=left_max->left
					else
						parent->right = left_max->left;
					delete left_max;//交换过了,所以不能删除cur,要删除left_max
					return true;
				}
				else//有一棵子树
				{
					//让删除的节点的左子树或右子树直接替代其位置,需要parent	
					if (cur->left == nullptr)
					{
						if (cur == root)//parent==nullptr,特殊情况
						{
							root = cur->right;
							delete cur;
							return root;
						}
						if (parent->right == cur)
							parent->right = cur->right;
						else
							parent->left = cur->right;
					}
					if (cur->right == nullptr)
					{
						if (cur == root)//parent==nullptr,特殊情况
						{
							root = cur->left;
							delete cur;
							return root;
						}
						if (parent->right == cur)
							parent->right = cur->left;
						else
							parent->left = cur->left;
					}
					delete cur;
					return true;
				}
				
			}
		}
		//cur为nullptr,没找到
		return false;
	}
priva

测试代码:

#include "BinarySearchTree.h"
int main()
{
    Binary_Search_Tree<int> bst;
    int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
    for (auto e : a)
    {
        bst.insert(e);
    }
    bst.print_inoder();
    bst.erase(4);
    bst.print_inoder();
    bst.erase(6);
    bst.print_inoder();
    bst.erase(7);
    bst.print_inoder();
    bst.erase(3);
    bst.print_inoder();
    return 0;
}

运行结果:

4.练习1: [LintCode]删除二叉查找树的节点

https://www.lintcode.com/problem/87/

描述

给定一棵具有不同节点值的二叉查找树,删除树中与给定值相同的节点。如果树中没有相同值的节点,就不做任何处理。你应该保证处理之后的树仍是二叉查找树。

样例 1:

输入:

Tree = {5,3,6,2,4}
value = 3

输出:

{5,2,6,#,4} 或 {5,4,6,2}

解释:

给定了以下二叉搜索树:
    5
   / \
  3   6
 / \
2   4
移去3,你可以返回:
    5
   / \
  2   6
   \
    4
或
    5
   / \
  4   6
 /
2

样例 2:

输入:

Tree = {5,3,6,2,4}
value = 4

输出:

{5,3,6,2}

解释:

给定了以下二叉搜索树:
    5
   / \
  3   6
 / \
2   4
移去4,你应该返回
    5
   / \
  3   6
 /
2

代码

一定要考虑特殊情况!

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */
class Solution {
public:
    /**
     * @param root: The root of the binary search tree.
     * @param value: Remove the node with given value.
     * @return: The root of the binary search tree after removal.
     */
    TreeNode* removeNode(TreeNode *root, int value) 
    {
        TreeNode* cur = root;
        TreeNode* parent = nullptr;
        while (cur)
        {
            if (cur == nullptr)//节点不存在,特殊情况
                return root;
            if (cur->val > value)
            {
                parent = cur;
                cur = cur->left;
            }
            else if (cur->val < value)
            {
                parent = cur;
                cur = cur->right;
            }
            else //cur->val == value
            {
                if (cur->left == cur->right)//只有cur->left==cur->right==nullptr这一种情况
                {
                    if (parent==nullptr)//该二叉搜索树只有一个节点,特殊情况
                    {
                        delete cur;
                        return nullptr;  
                    }
                    if (parent->left == cur)
                        parent->left = nullptr;
                    else
                        parent->right = nullptr;
                    delete cur;
                    return root;
                }
                else if (cur->left && cur->right)
                {
                    TreeNode* left_max = cur->left;
                    TreeNode* parent = cur;
                    while (left_max->right)
                    {
                        parent = left_max;
                        left_max = left_max->right;
                    }
                    swap(cur->val, left_max->val);
                    if (parent->left==left_max)
                        parent->left = left_max->left;
                    else
                        parent->right = left_max->left;
                    delete left_max;
                    return root;
                }
                else
                {
                    if (cur->left == nullptr)
                    {
                        if (cur == root)//parent==nullptr,特殊情况
                        {
                            root = cur->right;
                            delete cur;
                            return root;
                        }
                        if (parent->right == cur)
                            parent->right = cur->right;
                        else
                            parent->left = cur->right;
                    }
                    if (cur->right == nullptr)
                    {
                        if (cur == root)//parent==nullptr,特殊情况
                        {
                            root = cur->left;
                            delete cur;
                            return root;
                        }
                        if (parent->right == cur)
                            parent->right = cur->left;
                        else
                            parent->left = cur->left;
                    }
                    delete cur;
		            return root;
		        }
	        }
        } 
        return root;//cur为nullptr  
    }  
};

提交结果

6.练习2: [LintCode]在二叉查找树中插入节点

https://www.lintcode.com/problem/85/?_from=problem_tag&fromId=358

描述

给定一棵二叉查找树和一个新的树节点,将节点插入到树中。

你需要保证该树仍然是一棵二叉查找树。

样例 1:

输入:

tree = {}
node= 1

输出:

{1}

解释:

在空树中插入一个点,应该插入为根节点。

样例 2:

输入:

tree = {2,1,4,#,#,3}
node = 6

输出:

{2,1,4,#,#,3,6}

解释:

85_1.png

挑战

能否不使用递归?

代码

/**
 * Definition of TreeNode:
 * class TreeNode {
 * public:
 *     int val;
 *     TreeNode *left, *right;
 *     TreeNode(int val) {
 *         this->val = val;
 *         this->left = this->right = NULL;
 *     }
 * }
 */

class Solution {
public:
    /*
     * @param root: The root of the binary search tree.
     * @param node: insert this node into the binary search tree
     * @return: The root of the new binary search tree.
     */
    TreeNode * insertNode(TreeNode * root, TreeNode * node) 
    {
        //分根节点是否为空讨论
        if (root)
        {
            TreeNode *  cur = root;
            TreeNode *  parent = nullptr;//一开始cur指向树的根节点时,cur是没有父节点的
            while (cur)
            {
                if (cur->val > node->val)
                {
                    parent = cur;
                    cur = cur->left;
                }
                else if (cur->val < node->val)
                {
                    parent = cur;
                    cur = cur->right;
                }
                else
                {
                    return root;
                }
            }
            if (parent->val < node->val)
                parent->right = node;
            else
                parent->left = node;
        }
        else
        {
            root = node;
        }
        return root;
    }
};

提交结果

5.练习3: 使用二叉搜索树排序

https://leetcode.cn/problems/sort-an-array/description/

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

    示例 1:

    输入:nums = [5,2,3,1]
    输出:[1,2,3,5]
    解释:数组排序后,某些数字的位置没有改变(例如,2 和 3),而其他数字的位置发生了改变(例如,1 和 5)。
    

    示例 2:

    输入:nums = [5,1,1,2,0,0]
    输出:[0,0,1,1,2,5]
    解释:请注意,nums 的值不一定唯一。
    

    提示:

    • 1 <= nums.length <= 5 * 10^4
    • -5 * 10^4 <= nums[i] <= 5 * 10^4

    代码

    将key换成pair<K,int>,K表示存储的数据,第二个int表示该数字重复出现的次数

    template<class K>
    class Binary_Search_Tree;//前向声明
    template<class K>
    class Node
    {
    public:
    	Node(const K& val)
    		:left(nullptr)
    		, right(nullptr)
    		, key(val,1)
    	{
    	}
    	Node<K>* left;
    	Node<K>* right;
    	pair<K,int> key;
    };
    template<class K>
    class Binary_Search_Tree
    {
    public:
    	Binary_Search_Tree()
    		:root(nullptr)
    	{}
    	void insert(const K& val)
    	{
    		//分根节点是否为空讨论
    		if (root)
    		{
    			Node<K>* cur = root;
    			Node<K>* parent = nullptr;//一开始cur指向树的根节点时,cur是没有父节点的
    			while (cur)
    			{
    				if ((cur->key).first > val)
    				{
    					parent = cur;
    					cur = cur->left;
    				}
    				else if ((cur->key).first < val)
    				{
    					parent = cur;
    					cur = cur->right;
    				}
    				else//(cur->key).first == val,不能插入相同数字
    				{
    					cur->key.second++;
                        return;
    				}
    			}
    			cur = new Node<K>(val);
    			if ((parent->key).first < val)
    				parent->right = cur;
    			else
    				parent->left = cur;
    		}
    		else
    		{
    			root = new Node<K>(val);
    		}
    	}
    
    	void wirte_vector(vector<K>& v)
    	{
    		InOrder(root,v);
    	}
    private:
    	void InOrder(Node<K>* root,vector<K>& v)
    	{
    		//先判断是否为空树(叶节点的左节点和右节点均为空树)
    		if (root == NULL)
    			return;
    
    		//按左子树-->根-->右子树的顺序遍历
    		InOrder(root->left,v);
            while ((root->key).second--)
    		    v.push_back((root->key).first);
    		InOrder(root->right,v);
    	}
    	Node<K>* root;
    };
    
    class Solution {
    public:
        vector<int> sortArray(vector<int>& nums) 
        {
            Binary_Search_Tree<int> bst;
            vector<int> ret;
            for (auto a:nums)
                bst.insert(a);
            bst.wirte_vector(ret);
            return ret;
        }
    };
    

    提交结果

    算法没有问题,但是当数据量大,而且是二叉搜索树的极端情况时,插入节点的时间复杂度为O(N^2),容易超时,具体解决方法之后在讲AVL树和红黑树会提到

    6.对比二叉搜索树和有序数组

    有序数组二分查找速度快,但是插入和删除元素的效率低

    一般情况下的二叉搜索树的增删查改的速度都很快,但极端情况效率低

    极端情况:

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    打赏作者

    zhangcoder

    赠人玫瑰手有余香,感谢支持~

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

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

    打赏作者

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

    抵扣说明:

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

    余额充值