二叉搜索树
待定
平衡搜索树
定义:在二叉搜索树的基础上,增加节点左右子树高度差的特点。平衡二叉树中的每个节点的左右子树高度差小于等于1
在插入和删除时会出现不平衡的情况,这个时候就需要根据不平衡的节点进行调整,有四种情况:LL,RR,LR,RL
LL
蓝色节点左右子树相差为2,此处应该使用单左旋进行调整
因为平衡树又有搜索树的特性,能够看出橘黄色大于黄色小于蓝色,因此应该调整橘黄色为根节点,蓝色调整为橘黄色节点的右子树。如果橘黄色原先有右子树,那么该右子树的值都大于橘黄色节点且小于蓝色节点,所以可以作为调整后蓝色节点的左子树
RR
蓝色节点左右子树相差为2,此处应该使用单右旋进行调整
LR
在LR的情况下,应该从下向上的方向进行调整,先将橘黄色和和黄色节点进行RR操作,在对蓝色节点实施LL操作
RL
在RL的情况下,同样应该从下向上的方向进行调整,先将橘黄色和和黄色节点进行LL操作,在对蓝色节点实施RR操作
在平衡二叉树中,其实主要的操作还是插入和删除节点,在这个过程中需要注意调整每个节点的高度
插入
在插入操作中,因为不确定最终会插入到哪个叶子节点上,因此经过的叶子节点的高度只能在插入后才能确定
因此使用递归的方式
在未找到指定插入地点时,一直查找,直到插入后,从底向上依次返回对应节点执行未完成的操作,调整平衡和更新高度
当xdata,有两种情况:LL,LR 也可能不需要调整
1、当xleft->data,则属于LL
2、当x>root->left->data,则属于LR
当x>root->data,有两种情况:RR,RL
3、当x > root->right->data,则属于RR
4、当x < root->right->data,则属于RL
删除
删除的话不同于插入,删除的节点有可能是叶子节点也可能是中间的某个节点。
因为不确定性,因此同样使用递归的方式进行删除
递归的方式寻找删除的节点,在找到对应的节点后,首先判断他的左右子树是否为空
1、若都不为空,则为了二叉树的平衡,需要判断两个子树哪个更高
若左树更高,则找到左树的最大值,将其替换掉要删除的节点,然后使用递归删除左树最大值节点
若右树更高,则找到右树的最小值,将其替换掉要删除的节点,然后使用递归删除右树最小值节点
2、若其中一个为空,则直接将另一个子树的根节点替换掉该节点即可,为了指针实参能够跟随形参指向地址的改变而改变,因此传参时使用的引用指针 : *&p
因为删除节点的位置下方,由于Delete替换的叶子节点,将下方的子树进行了调整和更新高度,因此该位置无需操作
直接依次返回上一个节点实施调整和更新高度的操作
当xdata,有两种情况:LL,LR 也可能不需要调整
1、当xleft->data,则属于LL
2、当x>root->left->data,则属于LR
当x>root->data,有两种情况:RR,RL
3、当x > root->right->data,则属于RR
4、当x < root->right->data,则属于RL
代码:
AvlTree.h
#include <iostream>
#include <algorithm>
using namespace std;
#pragma once
//平衡二叉树节点
struct AvlNode
{
int data;
int height;//节点的高度
AvlNode *left;
AvlNode *right;
AvlNode(const int theData) :data(theData), left(NULL), right(NULL), height(0) {}
};
//平衡二叉树
class AvlTree
{
public:
AvlTree() {};
~AvlTree() {};
AvlNode *root;//根节点
//1、插入节点
void Insert(AvlNode *&t, int x);
//2、删除节点
bool Delete(AvlNode *&t, int x);
//3、查找是否存在给定值的节点
bool Contains(AvlNode *t, const int x)const;
//4、中序遍历
void InorderTraversal(AvlNode *t);
//5、前序遍历
void PreorderTraversal(AvlNode *t);
//6、最小值节点
AvlNode *FindMin(AvlNode *t) const;
//7、最大值节点
AvlNode *FindMax(AvlNode *t) const;
private:
//8、求树的高度
int GetHeight(AvlNode* t);
//9、单旋转 左
AvlNode* LL(AvlNode* t);
//10、单旋转 右
AvlNode* RR(AvlNode* t);
//11、双旋转 右左
AvlNode* RL(AvlNode* t);
//12、双旋转 左右
AvlNode* LR(AvlNode* t);
};
//1、插入节点
void AvlTree::Insert(AvlNode *&t, int x)
{
if (t == NULL) t = new AvlNode(x);
else if(x<t->data)
{
Insert(t->left, x);
//判断平衡情况
if (GetHeight(t->left) - GetHeight(t->right) > 1)
{
//分两种情况 左左 左右
if (x < t->left->data)
{
t=LL(t);
}
else
{
t = LR(t);
}
}
}
else if (x > t->data)
{
Insert(t->right, x);
//判断平衡情况
if (GetHeight(t->left) - GetHeight(t->right) < -1)
{
//分两种情况 右右 右左
if (x > t->right->data)
{
t = RR(t);
}
else
{
t = RL(t);
}
}
}
else//数据重复
{
//no operate
}
//更新t的height
t->height = max(GetHeight(t->left), GetHeight(t->right))+1;
}
//2、删除节点
bool AvlTree::Delete(AvlNode *&t, int x)
{
bool ans = false;
//t为空 未找到要删除的节点
if (t == NULL) return false;
//找到要删除的节点
else if (t->data == x)
{
//左右子树都非空
if (t->left != NULL && t->right != NULL)
{
//在高度更高的子树上进行删除操作,实际上就是将更高的子树的节点替代要删除的节点
if (GetHeight(t->left) > GetHeight(t->right))
{
t->data = FindMax(t->left)->data;//更新节点值
Delete(t->left, t->data);//删除左子树中的最大值
}
else
{
t->data = FindMin(t->right)->data;//更新节点值
Delete(t->right, t->data);//删除右子树中的最小值
}
//t->height=max(GetHeight(t->left),GetHeight(t->right));
}
else
{
//左右子树有一个不为空,直接用需要删除的节点的子节点替换即可 当然也可能两个子树都为空
AvlNode *old = t;
t = t->left ? t->left : t->right;
delete old;
}
}
else if (x < t->data)//要删除的节点在左子树上
{
Delete(t->left, x);
//判断是否满足平衡条件
if (GetHeight(t->left) - GetHeight(t->right)<-1)
{
if (t->left->data < x)
{
t=LL(t);
}
else
{
t = LR(t);
}
}
else
{
t->height = max(GetHeight(t->left), GetHeight(t->right))+1;
}
}
else
{
Delete(t->right, x);
//判断是否满足平衡条件
if (GetHeight(t->left) - GetHeight(t->right) > 1)
{
if (t->right->data > x)
{
t = RR(t);
}
else
{
t = RL(t);
}
}
else
{
t->height = max(GetHeight(t->left), GetHeight(t->right))+1;
}
}
return true;
}
//3、查找是否存在给定值的节点
bool AvlTree::Contains(AvlNode *t, const int x)const
{
if (t == NULL) return false;
if (x < t->data)
return Contains(t->left, x);
else if (x > t->data)
return Contains(t->right, x);
else
return true;
}
//4、中序遍历
void AvlTree::InorderTraversal(AvlNode *t)
{
if (t)
{
InorderTraversal(t->left);
cout << t->data << " ";
InorderTraversal(t->right);
}
}
//5、前序遍历
void AvlTree::PreorderTraversal(AvlNode *t)
{
if (t)
{
cout << t->data << " ";
PreorderTraversal(t->left);
PreorderTraversal(t->right);
}
}
//6、最小值节点
AvlNode * AvlTree::FindMin(AvlNode *t) const
{
/*if (t == NULL) return NULL;
while (t->left != NULL)
{
t = t->left;
}
return t;*/
if (t == NULL) return NULL;
if (t->left == NULL) return t;
return FindMin(t->left);
}
//7、最大值节点
AvlNode * AvlTree::FindMax(AvlNode *t) const
{
/*if (t == NULL) return NULL;
while (t->right != NULL)
{
t = t->right;
}
return t;*/
if (t == NULL) return NULL;
if (t->right == NULL) return t;
return FindMax(t->right);
}
//8、求树的高度
int AvlTree::GetHeight(AvlNode* t)
{
if (t != NULL)
{
return t->height;
}
return -1;
}
//9、单旋转 左
AvlNode* AvlTree::LL(AvlNode* t)
{
AvlNode *q = t->left;
t->left = q->right;
q->right = t;
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
q->height = max(GetHeight(q->left), GetHeight(q->right)) + 1;
return q;
}
//10、单旋转 右
AvlNode* AvlTree::RR(AvlNode* t)
{
AvlNode *q = t->right;
t->right = q->left;
q->left = t;
t->height = max(GetHeight(t->left), GetHeight(t->right))+1;
q->height = min(GetHeight(q->left), GetHeight(q->right))+1;
return q;
}
//11、双旋转 右左
AvlNode* AvlTree::RL(AvlNode* t)
{
//双旋转可以通过两次单旋转实现
//对t的右节点施行LL旋转,在对t实施RR
LL(t->right);
return RR(t);
}
//12、双旋转 左右
AvlNode* AvlTree::LR(AvlNode* t)
{
该方法也可以
//AvlNode* q = t->left;
//AvlNode* p = q->right;
//q->right = p->left;
//t->left = p->right;
//p->left = q;
//p->right = t;
//t->height = max(GetHeight(t->left), GetHeight(t->right));
//q->height = max(GetHeight(q->left), GetHeight(t->right));
//p->height = max(GetHeight(p->left), GetHeight(p->right));
//return p;
//使用两次单旋实现
//对t的左节点先施行RR,再对t实施LL
RR(t->left);
return LL(t);
}
测试:
AvlTree.cpp
#include "AvlTree.h"
int main()
{
AvlTree Tree;
int val;
int tmp;
cout << "请输入整数建立二叉树(-1结束):" << endl;
while (cin >> val)
{
if (val == -1) break;
Tree.Insert(Tree.root, val);
}
cout << "中序遍历"<<endl;
Tree.InorderTraversal(Tree.root);
cout << "前序遍历"<<endl;
Tree.PreorderTraversal(Tree.root);
cout << "请输入要查找的节点:" << endl;
cin >> tmp;
if (Tree.Contains(Tree.root, tmp))
cout << "已找到" << endl;
else
cout << "值为" << tmp << "的节点不存在" << endl;
cout << "请输入要删除的节点:";
cin >> tmp;
Tree.Delete(Tree.root, tmp);
cout << "删除后的中序遍历:"<<endl;
Tree.InorderTraversal(Tree.root);
cout << "删除后的前序遍历:"<<endl;
Tree.PreorderTraversal(Tree.root);
}
红黑树
待定
前缀树(字典树)
前缀树,通过复用节点,能够极大地节省为保存大量字符串的存储开销
每个节点中含有
1、字符串结束标志isend:true代表结尾 false代表不是结尾
2、根据下一个字符指向的 节点指针数组
主要的操作:
插入:实际上就是遍历前缀树,已经有的前缀直接查找下一个节点,不匹配的为该字符创建一个指针指向新建的节点
最后一个节点注意把isend置为true
搜索:除了需要全部匹配,还要检查最后一个节点的isend的标识是不是true
前缀匹配:只需要字符全部匹配就可以
删除:这个的话比较麻烦,我选择的是递归
删除一般分为三种情况:
1、从头到尾都要删除,只留一个头节点
2、较长,删除一半,中间有isend为true
3、较短,不需要删除,只需要将isend置为false即可
过程:通过递归遍历到最后一个节点,先将该节点isend置为false,如果该节点包含其他节点指针,直接返回false;否则返回true
上一层收到返回的false,则直接返回false;收到true,说明需要删除字符对应的指针。删除后需要判断是否是头节点,是否isend为true,是否还有其他字符对应的节点指针,是的话全部返回false;否则需要删除该节点,返回true。再执行上一层递归,直到到头节点。
代码:
Trie.h
#include<unordered_map>
#include<string>
using namespace std;
#pragma once
class Trie
{
private:
bool isend= false;//是否是字符串结尾
unordered_map<char, Trie*> dict;//保存的分叉
public:
Trie()
{
isend = false;
}
//向前缀树中插入字符串
void insert(string word)
{
Trie* p = this;//遍历指针
for (auto ch : word)
{
if (p->dict.count(ch) == 0)
{
p->dict[ch] = new Trie();//为该节点增加分叉
}
p = p->dict[ch];
}
p->isend = true;
}
//字符串是否已经在前缀树中
bool search(string word)
{
Trie* p = this;
for (auto ch : word)
{
if (p->dict.count(ch) == 0)
{
return false;
}
p=p->dict[ch];
}
if (p->isend == false)
{
return false;
}
return true;
}
//前缀匹配
bool startsWith(string prefix)
{
Trie* p = this;
for (auto ch : prefix)
{
if (p->dict.count(ch) == 0)
{
return false;
}
p = p->dict[ch];
}
return true;
}
//删除字符串:首先判断字符串是否在前缀树中,然后使用递归的方式删除字符串
//会存在三种情况:1、删除整条2、删除一部分3、只是修改最后一个节点的isend状态为false
bool del_word(string word)
{
if (!search(word)) return false;
Trie* p = this;
del(p, word, 0);
return true;
}
bool del(Trie* p ,string word,int i)
{
if (word.size() == i)
{
p->isend = false;
if (p->dict.size() != 0)
{
return false;
}
else
{
delete p;
return true;
}
}
if (del(p->dict[word[i]], word, i + 1))
{
p->dict.erase(word[i]);//删除对应字符
if (p->isend == true)//防止删除比自己短的字符串的结尾节点
{
return false;
}
if (p->dict.size() != 0)//防止删除还有其他字符的节点
{
return false;
}
else
{
if (p == this)//防止删掉最后一个节点
{
return true;
}
delete p;//删除对应节点
return true;
}
}
return false;
}
};
测试:
Trie.cpp
#include "Trie.h"
#include<iostream>
int main()
{
string s1 = "qwert";
string s2 = "qwe";
string s3 = "qwerdc";
Trie tree;
tree.insert(s1);
tree.insert(s2);
tree.insert(s3);
if (tree.search(s1))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
if (tree.search(s2))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
if (tree.search(s3))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
tree.del_word(s1);
//tree.del_word(s2);
tree.del_word(s3);
if (tree.search(s1))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
if (tree.search(s2))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
if (tree.search(s3))
{
cout << "true" << endl;
}
else
{
cout << "false" << endl;
}
}