数据结构之搜索二叉树

x33g5p2x  于2021-11-25 转载在 其他  
字(9.7k)|赞(0)|评价(0)|浏览(375)

搜索二叉树基本介绍:

二叉搜索树的插入:

二叉搜索树的查找:

二叉搜索树的删除:

普通二叉树的删除:

搜索二叉树基本介绍:

1.二叉搜索树的概念:
二叉搜索树又称二叉查找树,亦称为二叉排序树。设x为二叉查找树中的一个节点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个节点,则key[y] <= key[x];如果y是x的右子树的一个节点,则key[y] >= key[x]。

2.二叉搜索树的性质:

(1)若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
(2)若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
(3)左、右子树也分别为二叉搜索树;

3.二叉搜索树查找的性能:

当节点数目为N,树高度保持logN附近。则平均查找长度与logN成正比,查找平均时间复杂度为O(logN)。 当先后插入的关键字有序时,二叉搜索树退化成单支树结构。此时树高N。平均查找长度为(N+1)/2,查找的平均时间复杂度为O(N)。

4.二叉搜索树插入的性能:

与查找的性能相同

5.二叉树的删除性能:

删除节点时,若节点为叶子节点,或者节点只有单一子树,则时间复杂度为O(1)。若节点既有左子树又有右子树,则需要执行递归过程,对应时间复杂度为O(logN)。

6.节点的结构定义:

对应代码:

template<class K>
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;
public:

private:
	Node* _root = nullptr;
};

二叉搜索树的插入:

实现思路:
1.如果这棵树一个节点也每有直接new 一个节点让根指向它即可,在return true;

2.如果这颗树至少有一个节点那么我们就要利用他的性质来寻找插入位置

搜索二叉树是一个有序树:
·若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
·若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
·它的左、右子树也分别为二叉搜索树。

1.如果key大于根节点,说明值为val的节点应该插入到root节点的右子树上
2.如果key小于根节点,说明值为val的节点应该插入到root节点的左子树上

3.如果val等于根节点,则插入失败
然后再继续执行上面的操作,直到找到叶子节点为止,然后再把它插进去,就以题中示例为例画个图来看一下:

对应代码:

bool Insert(const K&key) {
}

函数体

如果只有一个节点:

if (_root == nullptr) {
			_root = new Node(key);
			return true;
  }

如果有多个节点的话:

寻找插入位置:

bool Insert(const K&key) {
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {

			if (cur->_key < key) {
				parent = cur;
				cur = cur->_right;
		    }

			else if (cur->_key > key) {
				parent = cur;
				cur = cur->_left;
			}

			else
			{
				return false;
			}

		}
          cur = new Node(key);
}

可能有很多铁子认为这样就结束了,但是这样时有问题的。有什么问题了。

问题在于我们虽然找到了插入的文章,并且开了新的节点,但是没有和它的父亲节点链接起来,所以我们需要定义一个指针变量parent记录cur的上一个节点,在通过比较大小来确定是插入在父亲节点的左边还是右边

对应代码:

bool Insert(const K&key) {
		if (_root == nullptr) {
			_root = new Node(key);
			return true;
	     }
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {

			if (cur->_key < key) {
				parent = cur;
				cur = cur->_right;
		    }

			else if (cur->_key > key) {
				parent = cur;
				cur = cur->_left;
			}

			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key > key){
			parent->_left = cur;
		 }

		else{
			parent->_right = cur;
		}
		return true;
	}

二叉搜索树的查找:

基本步骤:
       1)如果树是空的,则查找结束,无匹配,返回nullptr
  (2)如果被查找的值和节点的值相等,查找成功,返回对应的节点
  (3)如果被查找的值小于节点的值,则去查找左子树,
  (4)如果被查找的值大于节点的值,则去查找右子树,

对应代码代码:

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

二叉搜索树的删除:

1.搜索二叉树是一个有序树:
·若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
·若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
·它的左、右子树也分别为二叉搜索树。

2.搜索树的删除一共有五种情况
第一种情况:没找到删除的节点,遍历到空节点直接返回了//可以忽略
(找到删除的节点)
第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点

第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点

第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点

第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。

但上述的第二种情况可以归类成第三种情况或者第四种情况

我们删除的原则是删除节点之后它还是一颗搜索二叉树:

下面让我们一起来看代码:

首先我们要先找到要删除的节点:

bool Erase(const K& key){
           Node* cur = _root;//变量这个

		Node* parent = nullptr;//记录cur的父亲节点

		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 (cur == _root) {//如果要删除的刚好是根节点
						_root = cur->_right;
					}

					else {
						//要看cur到底是父亲的左还是父亲的右
						if (parent->_right == cur) {
							parent->_right = cur->_right;
						}

						else {
							parent->_left = cur->_right;
						}

					}

					delete cur;//删除节点
					
				}

如果是右为空:

else if (cur->_right == nullptr) {//右为空

					if (cur == _root)//如果要删的刚好是根节点
					{
						_root = cur->_left;
					}

					else {

						if (parent->_right == cur) {
							parent->_right = cur->_left;
						}

						else {
							parent->_left = cur->_left;
						}

					}

					delete cur;
				}

把我的左孩子给父亲,但是是让父亲的左孩子指向我的左孩子,还是右孩子指向我的左孩子了,这就要看我到底是父亲的左孩子还是右 孩子

如果是左孩子和右孩子都不为空那么这个就是最难的了需要使用交换法来删除:

对应代码:

else {//左右都不为空//替换法删除

					Node* rightMin = cur->_right;//找到它的右子树的最左节点
					Node* rightMinParent = cur;//记录它的父亲
					while (rightMin->_left) {
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					
						cur->_key = rightMin->_key;//替代

						if (rightMinParent->_left == rightMin) {
							rightMinParent->_left = rightMin->_right;
				          	}
						else {
							rightMinParent->_right = rightMin->_right;
						}
					delete rightMin;

				}

代码汇总:

bool Erase(const K& key) {
		   //左右都不为空,不能直接删除,使用替换法删除,找左子树的最大节点或者是右子树的最小节点,叶子节点的删除 
		Node* cur = _root;//变量这个

		Node* parent = nullptr;//记录cur的父亲节点

		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 (cur == _root) {//如果要删除的刚好是根节点
						_root = cur->_right;
					}

					else {
						//要看cur到底是父亲的左还是父亲的右
						if (parent->_right == cur) {
							parent->_right = cur->_right;
						}

						else {
							parent->_left = cur->_right;
						}

					}

					delete cur;//删除节点
					
				}

				else if (cur->_right == nullptr) {//右为空

					if (cur == _root)//如果要删的刚好是根节点
					{
						_root = cur->_left;
					}

					else {

						if (parent->_right == cur) {
							parent->_right = cur->_left;
						}

						else {
							parent->_left = cur->_left;
						}

					}

					delete cur;
				}

				else {//左右都不为空//替换法删除

					Node* rightMin = cur->_right;//找到它的右子树的最左节点
					Node* rightMinParent = cur;//记录它的父亲
					while (rightMin->_left) {
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					
						cur->_key = rightMin->_key;//替代

						if (rightMinParent->_left == rightMin) {
							rightMinParent->_left = rightMin->_right;
				          	}
						else {
							rightMinParent->_right = rightMin->_right;
						}
					delete rightMin;

				}
				return true;

			}

		}

		return false;//没有找到这个值

        }

总体而言还是比较复杂的:

普通二叉树的删除:

这里我在介绍一种通用的删除,普通二叉树的删除方式(没有使用搜索树的特性,遍历整棵树),用交换值的操作来删除目标节点。

代码中目标节点(要删除的节点)被操作了两次:

第一次是和目标节点的右子树最左面节点交换。
第二次直接被NULL覆盖了。                      

TreeNode* deleteNode(TreeNode* root, int key) {
        if (root == nullptr) return root;
        if (root->val == key) {
            if (root->right == nullptr) { // 这里第二次操作目标值:最终删除的作用
                return root->left;
            }
            TreeNode *cur = root->right;
            while (cur->left) {
                cur = cur->left;
            }
            swap(root->val, cur->val); // 这里第一次操作目标值:交换目标值其右子树最左面节点。
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }

由于二叉树的前中后序遍历博主在另外一篇博客已经写过了在这里就不过多赘述:

对应总代码搜索二叉树:

#pragma once
#include<iostream>
using namespace std;
template<class K>
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;
public:
	bool Insert(const K&key) {
		if (_root == nullptr) {
			_root = new Node(key);
			return true;
	     }
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {

			if (cur->_key < key) {
				parent = cur;
				cur = cur->_right;
		    }

			else if (cur->_key > key) {
				parent = cur;
				cur = cur->_left;
			}

			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent->_key > key){
			parent->_left = cur;
		 }

		else{
			parent->_right = cur;
		}
		return true;
	}
   void _Inorder(Node* root) {
		if (root == nullptr) {
			return;
		}

		_Inorder(root->_left);

		cout << root->_key << " ";

		_Inorder(root->_right);
	}

	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;//记录cur的父亲节点

		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 (cur == _root) {//如果要删除的刚好是根节点
						_root = cur->_right;
					}

					else {
						//要看cur到底是父亲的左还是父亲的右
						if (parent->_right == cur) {
							parent->_right = cur->_right;
						}

						else {
							parent->_left = cur->_right;
						}

					}

					delete cur;//删除节点
					
				}

				else if (cur->_right == nullptr) {//右为空

					if (cur == _root)//如果要删的刚好是根节点
					{
						_root = cur->_left;
					}

					else {

						if (parent->_right == cur) {
							parent->_right = cur->_left;
						}

						else {
							parent->_left = cur->_left;
						}

					}

					delete cur;
				}

				else {//左右都不为空//替换法删除

					Node* rightMin = cur->_right;//找到它的右子树的最左节点
					Node* rightMinParent = cur;//记录它的父亲
					while (rightMin->_left) {
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					
						cur->_key = rightMin->_key;//替代

						if (rightMinParent->_left == rightMin) {
							rightMinParent->_left = rightMin->_right;
				          	}
						else {
							rightMinParent->_right = rightMin->_right;
						}
					delete rightMin;

				}
				return true;

			}

		}

		return false;//没有找到这个值

        }
private:
	Node* _root = nullptr;
};

搜索二叉树的增删查在letecode均有题:

对应链接:

删除:
https://leetcode-cn.com/problems/delete-node-in-a-bst/submissions/

由于上面已经讲过了在这里就直接给出代码:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(!root)return nullptr;
        
            TreeNode*cur=root;
            TreeNode*parent=nullptr;
            while(cur){

                if(cur->val<key){
                    parent=cur;
                    cur=cur->right;
                }

                else if(cur->val>key){
                    parent=cur;
                    cur=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;
                                delete cur;
                         }
                          
                     }

                     else if(cur->right==nullptr){

                         if(cur==root){
                             root=cur->left;
                         }

                         else{
                             
                             if(parent->right==cur)
                              parent->right=cur->left;
                              else
                                 parent->left=cur->left;
                         }
                     }

                     else{

                         TreeNode*rightMin=cur->right;
                         TreeNode*rightMinParent=cur;
                         while(rightMin->left){
                             rightMinParent=rightMin;
                             rightMin=rightMin->left;
                         }

                         cur->val=rightMin->val;

                         if(rightMinParent->left==rightMin)
                                rightMinParent->left=rightMin->right;
                         
                         else
                             rightMinParent->right=rightMin->right;
                     }
                      break;//删除完成break;跳出循环
                }    
                           
            }

            return root;
    }
};

插入:

https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/ 

对应代码:

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
                 if(root==nullptr){
                     return new TreeNode(val);
                 }
                 TreeNode*cur=root;
                 TreeNode*parent=nullptr;

                 while(cur){
                     if(cur->val<val){
                         parent=cur;
                         cur=cur->right;
                         
                     }
                     else if(cur->val>val){
                         parent=cur;
                           cur=cur->left;
                     }
                     else{
                         return NULL;
                     }
                 }
                 cur=new TreeNode(val);
                 if(parent->val<cur->val){
                     parent->right=cur;
                 }
                 else{
                     parent->left=cur;
                 }
                 return root;
    }
};

查找:

  1. 二叉搜索树中的搜索 - 力扣(LeetCode) (leetcode-cn.com)

    https://leetcode-cn.com/problems/search-in-a-binary-search-tree/ 

对应代码:

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
            TreeNode*cur=root;
            while(cur){
                if(cur->val>val){
                    cur=cur->left;
                }
                else if(cur->val<val){
                    cur=cur->right;
                }
                else{
                    return cur;
                }
            }
            return nullptr;
    }
};

相关文章