目录
前言:二叉搜索树是为学习map和set做的铺垫,了解了二叉搜索树的特性,有助于更好的理解map和set。
二叉搜索树有称二叉排序树,它可以是一颗空树,或者是具有以下行者的二叉树:
① 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值
② 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值
③ 它的左右子树也分别为二叉搜索树
_key为该二叉树值,_left为左结点,_right为右结点。
- template<class K>
- struct BSTreeNode
- {
- BSTreeNode
* _left; - BSTreeNode
* _right; -
- K _key;
-
- BSTreeNode(const K& key)
- : _left(nullptr)
- , _right(nullptr)
- , _key(key)
- {}
- };
实现二叉搜索树的构造、拷贝构造、赋值重载、析构函数。
拷贝构造是一定要实现的,通过子函数CopyTree通过前序遍历进行依次构造结点,而构造函数用编译器默认生成的就够了,但是因为实现了拷贝构造,编译器就不会生成默认的构造,因此使用C++11的关键字default来强制编译器生成构造。
赋值重载是现代写法,析构函数也是通过子函数DestoryTree,通过递归依次释放结点。
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - private:
- void DestroyTree(Node* root)
- {
- if (root == nullptr)
- {
- return;
- }
-
- DestroyTree(root->_left);
- DestroyTree(root->_right);
- delete root;
- }
-
- Node* CopyTree(Node* root)
- {
- if (root == nullptr)
- {
- return nullptr;
- }
-
- Node* copyNode = new Node(root->_key);
- CopyTree(root->_left);
- CopyTree(root->_right);
-
- return copyNode;
- }
- public:
- // C++11,强制让编译器自己生成构造
- BSTree() = default;
-
- BSTree(const BSTree
& t) - {
- _root = CopyTree(t._root);
- }
-
- BSTree
& operator=(BSTree t) - {
- swap(_root, t._root);
- return *this;
- }
-
- ~BSTree()
- {
- DestroyTree(_root);
- _root = nullptr;
- }
- private:
- Node* _root = nullptr;
- };
遍历寻找,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走。
- bool Find(const K& key)
- {
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- cur = cur->_right;
- }
- else if(cur->_key > key)
- {
- cur = cur->_left;
- }
- else
- {
- return true;
- }
- }
-
- return false;
- }
找到位置就插入进去。,如果是空树,直接插入作为根结点。
需要定义一个parent变量,这样在找到插入的位置后,可以让该插入结点的父节点parent链接上该新插入的结点。
前面与查找类似,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走,如果相等了,就不能插入了,直接返回false。
找到要插入的位置之后,new出要插入的新结点,然后判断这个结点的值是大于parent还是小于parent,如果大于就放在右边,如果小于就放在左边。
- bool Insert(const K& key)
- {
- if (_root == nullptr)
- {
- _root = new Node(key);
- return true;
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- 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->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
-
- return true;
- }
删除需要考虑的比较多。
首先也是需要定义一个parent变量,这样删除掉结点后,可以让其parent连接到删除结点的孩子结点。
接着也是与查找类似,在找到之后,就要分情况讨论。
第一种情况是要删除的结点的左孩子为空:这种情况首先要注意要删除的结点是根结点的情况,因为是根结点,所有parent是空,因此要单独写,让新的根结点变为原根结点的右结点。另外就是正常情况,要判断删除的这个结点的值是parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让其parent的左孩子结点链接上该结点的右孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的右孩子结点。
第二种情况是要删除的结点的右孩子为空:这种情况与第一种情况类似,也是要注意特殊情况,但是这种情况是让新的根结点变为原根结点的左结点。然后再判断删除的这个结点时parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让parent的左孩子结点链接上该结点的左孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的左孩子结点。
第三种情况是要删除的结点的左右孩子都不为空:出现这种情况时,不能直接删除掉需要删除的结点,而是需要替代删除,保证二叉搜索树的特性。这种替代有两种方法(第一种是用该结点的左子树的最大结点替代,第二种是用该结点的右子树的最小结点替代),这里我们采用第二种方法。首先需要定义两个变量,一个是用来找到这个去替代的结点minRight,另一个是该替代的结点的父节点minParent,这里要注意定义minParent时,要让其等于cur,如果让其等于nullptr,那么如果该结点的左结点没有结点了就会导致minParent一直为空,出现野指针问题。定义了之后,通过while循环找到该结点右子树的最小结点(即该结点右子树的最左的结点),接下来可以交换要删除结点的值和替代结点的值,也可以直接赋值,因为替代结点即将被删除,是否交换并不重要。接下来再判断替代结点时minParent的左孩子结点还是右孩子结点,如果是左孩子结点,就让minParent的左孩子节点链接上替代结点的右孩子节点,如果是右孩子结点,就让minParent的右孩子结点链接上替代结点的右孩子结点。
- bool Erase(const K& key)
- {
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- // 找到要删除的结点了
- else
- {
- // 左孩子为空
- if (cur->_left == nullptr)
- {
- if (cur == _root)
- {
- _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 (cur == _root)
- {
- _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(minRight->_key, cur->_key);
-
- if (minParent->_left == minRight)
- {
- minParent->_left = minRight->_right;
- }
- else
- {
- minParent->_right = minRight->_right;
- }
-
- delete minRight;
- }
-
- return true;
- }
- }
-
- return false;
- }
递归写法就采用子函数的方法,查找、插入、删除都是如此。
如果要查找的值比此结点大,就递归调用该结点的右子树结点,如果要查找的值比此结点小,就递归调用该结点的左子树结点,如果相等了就返回true,遇到空结点就返回false。
- bool FindR(const K& key)
- {
- return _FindR(_root, key);
- }
-
- private:
- 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;
- }
- }
与递归查找法类似,只是插入是遇到空会创建插入结点,返回true,相等时返回false。
- bool InsertR(const K& key)
- {
- return _InsertR(_root, key);
- }
-
- private:
- 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;
- }
- }
前面就是查找,找到之后,也是要分三种情况讨论。
这里我们注意形参root的变量类型是Node*&,是指针的引用。
第一种左为空:因为是指针引用,因此直接让root等于root的右子树结点,因为是引用,所以如果上一个递归调用的是root->_right那么这个root就是上一个root->_right的别名,因此就相当于上一个的root->_right = 当前的root->_right,这样就相当于删除掉了这个root。
第二种右为空:与第一种类似,root = root->_left,把_right改为_left即可。
第三种左右都不为空:这个就只需要定义一个替代变量minRight即可,然后通过while找到这个变量,这个一定要交换,不能赋值更改,因为接下来是要使用递归的。要注意这里递归的是root的右子树结点(不能是当前结点,原因是在交换之后,该结点已经不满足二叉搜索树的特性了,用该结点就会出错),其右子树结点及其右子树所有结点都是满足二叉搜索树的特性的,这样递归之后,就满足上面两种情况之一,就可以完成删除了。
- bool EraseR(const K& key)
- {
- return _EraseR(_root, key);
- }
-
- private:
- 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* minRight = root->_right;
- while (minRight->_left)
- {
- minRight = minRight->_left;
- }
-
- swap(root->_key, minRight->_key);
-
- return _EraseR(root->_right, key);
- }
-
- delete del;
- return true;
- }
- }
- namespace key
- {
- template<class K>
- struct BSTreeNode
- {
- BSTreeNode
* _left; - BSTreeNode
* _right; -
- K _key;
-
- BSTreeNode(const K& key)
- : _left(nullptr)
- , _right(nullptr)
- , _key(key)
- {}
- };
-
- template<class K>
- class BSTree
- {
- typedef BSTreeNode
Node; - private:
- void DestroyTree(Node* root)
- {
- if (root == nullptr)
- {
- return;
- }
-
- DestroyTree(root->_left);
- DestroyTree(root->_right);
- delete root;
- }
-
- Node* CopyTree(Node* root)
- {
- if (root == nullptr)
- {
- return nullptr;
- }
-
- Node* copyNode = new Node(root->_key);
- CopyTree(root->_left);
- CopyTree(root->_right);
-
- return copyNode;
- }
- public:
- // C++11,强制让编译器自己生成构造
- BSTree() = default;
-
- BSTree(const BSTree
& t) - {
- _root = CopyTree(t._root);
- }
-
- BSTree
& operator=(BSTree t) - {
- swap(_root, t._root);
- return *this;
- }
-
- ~BSTree()
- {
- DestroyTree(_root);
- _root = nullptr;
- }
-
- bool Insert(const K& key)
- {
- if (_root == nullptr)
- {
- _root = new Node(key);
- return true;
- }
-
- Node* parent = nullptr;
- Node* cur = _root;
- 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->_right = cur;
- }
- else
- {
- parent->_left = cur;
- }
-
- return true;
- }
-
- // const Node* Find(const K& key)
- bool Find(const K& key)
- {
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- cur = cur->_left;
- }
- else
- {
- return true;
- }
- }
-
- return false;
- }
-
- bool Erase(const K& key)
- {
- Node* parent = nullptr;
- Node* cur = _root;
- while (cur)
- {
- if (cur->_key < key)
- {
- parent = cur;
- cur = cur->_right;
- }
- else if (cur->_key > key)
- {
- parent = cur;
- cur = cur->_left;
- }
- // 找到要删除的结点了
- else
- {
- // 左孩子为空
- if (cur->_left == nullptr)
- {
- if (cur == _root)
- {
- _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 (cur == _root)
- {
- _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(minRight->_key, cur->_key);
-
- if (minParent->_left == minRight)
- {
- minParent->_left = minRight->_right;
- }
- else
- {
- minParent->_right = minRight->_right;
- }
-
- delete minRight;
- }
-
- return true;
- }
- }
-
- return false;
- }
-
- // 递归写法:
- bool 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);
- }
-
- void InOrder()
- {
- _InOrder(_root);
- }
- private:
- 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 (root->_key < key)
- {
- return _InsertR(root->_right, key);
- }
- else if (root->_key > key)
- {
- return _InsertR(root->_left, key);
- }
- 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* minRight = root->_right;
- while (minRight->_left)
- {
- minRight = minRight->_left;
- }
-
- swap(root->_key, minRight->_key);
-
- return _EraseR(root->_right, key);
- }
-
- delete del;
- return true;
- }
- }
-
- Node* _root = nullptr;
-
- void _InOrder(Node* root)
- {
- if (root == nullptr)
- {
- return;
- }
-
- _InOrder(root->_left);
- cout << root->_key << " ";
- _InOrder(root->_right);
- }
- };
- }
这个和K模型的很类似,就大致实现一下,这里用修改的是递归版本的。
只需要增加一下value变量,增加一下模板,注意key加const。
- namespace key_value
- {
- template<class K, class V>
- struct BSTreeNode
- {
- BSTreeNode
* _left; - BSTreeNode
* _right; -
- const K _key;
- V _value;
-
- BSTreeNode(const K& key, const V& value)
- :_left(nullptr)
- , _right(nullptr)
- , _key(key)
- , _value(value)
- {}
- };
-
-
- template<class K, class V>
- class BSTree
- {
- typedef BSTreeNode
Node; - public:
-
- void InOrder()
- {
- _InOrder(_root);
- cout << endl;
- }
- 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:
- 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* minRight = root->_right;
- while (minRight->_left)
- {
- minRight = minRight->_left;
- }
-
- swap(root->_key, minRight->_key);
-
- return _EraseR(root->_right, key);
- }
-
- delete del;
- return true;
- }
- }
-
- 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;
- }
-
- Node* _FindR(Node* root, const 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 << ":" << root->_value << endl;
- _InOrder(root->_right);
- }
- private:
- Node* _root = nullptr;
- };
- }
K模型只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值。
例子:判断单词是否拼写正确
用词库中的单词集合中的每个单词作为key,构建二叉搜索树。
每一个关键码key,都有与之对应的值value,即
例子:①英汉词典,中英文的对应关系, 通过英文可以快速找到对应的中文。
②统计单词的次数,单词与其出现次数构成键值对。
对于相同的元素,如果key插入的次序不同,可能会得到不同结果的二叉搜索树。
这里要注意二叉搜索树的效率是O(logN),而不是O(log2N) 。
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其比较次数是log2N。
最差情况下,二叉搜索树退化为单支树(或者类似单支),其比较次数为logN
因此,我们接下来要学习平衡二叉搜索树(AVL树、红黑树),这个就使二叉搜索树趋近于完全二叉树,尽可能保证搜索效率。