(一)BST树(二叉搜索树)
1.BST二叉搜索树
1.1BST树的定义
1 BST树也是一个二叉树,故满足递归定义;
2 其次每个节点只存在一个值;
3 需满足左子树值<=根值<=右子树,故按照中序遍历会得到一个非递减序列。
BST涉及操作主要增,删,查,除了删麻烦一点,其他操作均可递归实现,二叉查找树的一般性质:
1.在一棵二叉查找树上,执行查找、插入、删除等操作,的时间复杂度为O(log2n)。因为,一棵由n个结点,随机构造的二叉查找树的高度为log2n,所以顺理成章,一般操作的执行时间为O(log2n)。
2.但若是一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)。
1.2基本操作:
①插入,删除,元素个数,前/中/后/层序遍历,
②二叉树镜像,判断当前二叉树是否是一颗BST树,
③找出指定区间的元素,
④判断一颗BST树是不是平衡树,
==》每一个左右子树的高度差不超过1,
⑤找出两个元素的LCA公共祖先节点并返回,
⑥判断参数传入的二叉树是否是当前二叉树的一颗子树
⑦找第K小/大的元素值 ===》 中序遍历
1.3BST树结构定义
// BST树类型定义
template<typename T>
class BSTree
{
public:
BSTree() :root(nullptr) {}
private:
// 定义节点类型
struct BSTNode
{
// T() 零构造
BSTNode(T val = T())
:data(val)
, left(nullptr)
, right(nullptr)
{}
T data; // 数据域
BSTNode *left; // 指向左孩子
BSTNode *right; // 指向右孩子
};
BSTNode *root = nullptr;
};
2.代码实现
2.1插入,删除,遍历,查询
2.1.1插入
非递归:
如果root为空,直接将root指向新申请节点并返回,否则寻找合适的位置,用parent指针保存当前节点的父节点,当当前节点为空则找到合适位置,用父节点链起新节点。
// BST树的插入操作
void insert(const T &val)
{
BSTNode *n = new BSTNode(val);
if (root == nullptr)
{
root = n;
return;
}
BSTNode *parent = nullptr;
BSTNode *cur = root;
while (cur != nullptr)
{
if (val > cur->data)
{
parent = cur;
cur = cur->right;
}
else if (val < cur->data)
{
parent = cur;
cur = cur->left;
}
else
{
return;
}
}
if (val < parent->data)
{
parent->left = n;
}
else
{
parent->right = n;
}
}
递归:
root为空,申请节点返回,
否则,>val 新节点在当前节点左边,<val 在右边
在寻找时最后要将当前层都返回给上一层,保持链表连接
//递归插入
void insert02(const T &val)
{
root = insert02(root, val);
}
BSTNode* insert02(BSTNode* root, const T& val)
{
if (root == nullptr)
{
return new BSTNode(val);
}
if (root->data < val)
{
root->right = insert02(root->right, val);
}
else if (root->data > val)
{
root->left = insert02(root->left, val);
}
return root;//①不管是那种情况,最后都要把链表链回去,其他节点的指针域不能变 ==>包含重复的情况,返回就ok了
/*②错误:==>并不是每次都将节点链回去==》本来想把重复情况去掉但没想到把链表打散了
if(root->data==val)//如果没有重复节点插入那么这样只有最后一次插入节点会返回给根节点,相当于只插入了最后一次的节点
{
return root;
}
*/
}
2.1.2删除

删除有三种情况:①该节点无子节点,②有一个,③有两个
当有两个子节点时,删除该节点则需:
用前驱节点或者后继节点的值覆盖该节点,接下来就需要处理前驱节点或者后继节点:
对于前驱节点或者后继节点:同样有①②两种情况,所以先处理情况③,最后统一处理情况①②
非递归:
//非递归目的就是从上往下找到要删除的节点,只需要找到节点对其进行处理即可,不需要考虑递归时每一层递归的不同返回情况
//删除
void remove(const T& val)
{
if (root == nullptr)
return;
BSTNode* cur = root;
BSTNode* parent = nullptr;
while (cur != nullptr)//寻找目标节点
{
if (cur->data > val)
{
parent = cur;
cur = cur->left;
}
else if (cur->data < val)
{
parent = cur;
cur = cur->right;
}
else
break;
}
if (cur == nullptr)//没有要删除的节点
return;
if (parent == nullptr)//!!!当只有一个根节点时,对其删除则将root=nullptr
{
delete cur;
root = nullptr;
return;
}
if(cur->left != nullptr && cur->right != nullptr) //情况③!!!前驱节点没有右子树,故必定到达情况②或① 所以不用while用if就够了
{
BSTNode* pioneer = cur->left;//前驱节点
parent = cur;//!!! parent = cur not pioneer
while (pioneer->right != nullptr)
{
parent = pioneer;
pioneer = pioneer->right;
}
cur->data = pioneer->data;
cur = pioneer;
}
if (cur->left != nullptr&&cur->right==nullptr)//情况② 只有左孩子
{
if (parent->left==cur)//是父节点的左孩子
{
parent->left = cur->left;
}
else if (parent->right ==cur)//是父节点的右孩子
{
parent->right = cur->left;
}
}
else if (cur->right != nullptr&&cur->left==nullptr)//只有右孩子
{
if (parent->left == cur)
{
parent->left = cur->right;
}
else if (parent->right == cur)
{
parent->right = cur->right;
}
}
else//没有孩子
{
if (parent->left == cur)
{
parent->left = nullptr;
}
else if (parent->right == cur)
{
parent->right = nullptr;
}
}
delete cur;
/*
优化:
//处理#1和#2两种情况
BSTNode *child = cur->left;
if (child == nullptr)
child = cur->right;
//把child写入cur的地址域中
if (parent == nullptr)//如果要删除的是一个只有一个孩子节点的根节点 root==cur
{
root = child;
}
else if (parent->left == cur)
{
parent->left = child;
}
else
{
parent->right = child;
}
delete cur;
*/
}
递归:
//递归就是要在多次探索的时候,找到合适的情况处理,但注意要想让递归顺利进行就不能改变原数据结构,就必须在每一层将原始状态返回回去
//这也是处理合适情况时(找到合适位置时)需要特别注意的,在处理合适情况每一种细分的情况时,返回的节点就可能不一样,要区别处理
BSTNode* remove02(BSTNode* cur, const T& val)
{
if (cur == nullptr)
return nullptr;
if (cur->data > val)
{
cur->left = remove02(cur->left, val);
}
else if(cur->data<val)
{
cur->right = remove02(cur->right, val);
}
else//找到了要删除的节点
{
BSTNode* parent = nullptr;
if (cur->left != nullptr && cur->right != nullptr)//有两个孩子
{
parent = cur;
BSTNode* pre = cur->left;
while (pre->right != nullptr)
{
parent = pre;
pre = pre->right;
}
cur->data = pre->data;
//pre前驱节点即是要删除的节点
//法一:手动做
BSTNode* child = pre->left;
if (child == nullptr)
child = pre->right;
if (parent->left == pre)
{
parent->left = child;
}
else if (parent->right == pre)
{
parent->right = child;
}
delete pre;
//法二(优化):利用递归去我的左面删除我的前驱节点
//cur->left = remove02(cur->left, pre->data);
}
else//有一个或没有
{
if (cur->left != nullptr)//有左孩子把左孩子返回给上一层
{
BSTNode* left = cur->left;
delete cur;
return left;
}
else if (cur->right!= nullptr)//有右孩子把右孩子返回给上一层
{
BSTNode* right = cur->right;
delete cur;
return right;
}
else//没有孩子
{
delete cur;
return nullptr;
}
}
}
return cur;
}
2.1.3遍历
1.前序遍历
递归:
//前序遍历
void PreOrder()
{
cout << "前序遍历:";
preOrder(root);
cout << endl;
}
void preOrder(BSTNode* root)
{
if (root == nullptr)
return;
cout << root->data << " ";
preOrder(root->left);
preOrder(root->right);
}
非递归:
法一:
void preOrder01()
{
cout << "前序遍历:";
stack<BSTNode*> s;
BSTNode* cur = root;
while (!s.empty() || cur != nullptr)
{
while (cur != nullptr)边遍历边打印,并存入栈中,以后需要借助这些节点进入右子树
{
cout << cur->data << " ";
s.push(cur);
cur = cur->left;
}
//当p为空时,说明根和左子树都遍历完了,该进入右子树了,
//那么原来入栈的左子树出栈,直到有右子树,进入右子树接着进行先根遍历
if (!s.empty())
{
cur = s.top();
s.pop();
cur = cur->right;
}
}
cout << endl;
}
法二:
void preOrder02()
{
cout << "前序遍历:";
stack<BSTNode*> s;
s.push(root);
while (!s.empty())
{
BSTNode* cur = s.top();
cout << cur->data << " ";
s.pop();
if (cur->right != nullptr)//栈,先进后出,所以先进右边
{
s.push(cur->right);
}
if (cur->left != nullptr)
{
s.push(cur->left);
}
}
cout << endl;
}
2.中序遍历:
递归:
//中序遍历
void InOrder()
{
cout << "中序遍历:";
inOrder(root);
cout << endl;
}
void inOrder(BSTNode* root)
{
if (root == nullptr)
return;
inOrder(root->left);
cout << root->data << " ";
inOrder(root->right);
}
递归:
//中序遍历非递归
void InOrder02()
{
cout << "中序遍历";
stack<BSTNode*> s;
BSTNode* cur = root;
while (!s.empty()||cur!=nullptr)
{
while (cur != nullptr)//先走左子树,将左孩子全入栈 ===>前
{
s.push(cur);
cur = cur->left;
}
//当cur==nullptr说明已经到达当前分支的最左子节点,紧接着就该打印栈顶节点了
if (!s.empty())
{
cur = s.top();
cout << cur->data << " "; //====>中
s.pop();
cur = cur->right; //====>后
//如果cur->right==nullptr,说明该打印当前节点的父节点(当前树的根节点)了,继续进入这个判断,出栈,打印
//如果!=nullptr则进入右子树,开始新一轮的左子树遍历(递归的自我实现)
}
}
}
3.后序遍历:
递归:
//后序遍历
void PostOrder()
{
cout << "后序遍历:";
postOrder(root);
cout << endl;
}
void postOrder(BSTNode* root)
{
if (root == nullptr)
return;
postOrder(root->left);
postOrder(root->right);
cout << root->data << " ";
}
非递归:
void PostOrder02()
{
cout << "后序遍历:";
stack<BSTNode*> s;
BSTNode* cur = root;
BSTNode* tag = nullptr;
while(!s.empty() || cur != nullptr)
{
while (cur != nullptr)
{
s.push(cur);
cur = cur->left;
}
cur = s.top(); s.pop();//到最左节点就拿出来,进行分情况讨论
if (cur->right == nullptr || cur->right == tag)//没右孩子或者右面已经访问过了
{
cout << cur->data << " ";
tag = cur;//记录上一次访问的元素,为访问其根节点时顺利打印做准备,表示已经访问过了,可以后根打印根节点了
cur = nullptr;//要置空,打印过就不再引用,否则卡在这里一直打印最左节点
}
else//有右子树就往右子树走,进入右子树重新开始对其的后根遍历 (有右子树并且没有访问过)
{
s.push(cur);
cur = cur->right;
}
}
cout << endl;
}
4.层序遍历
递归:
int high(BSTNode* root)
{
if (root == nullptr)
return 0;
int left = high(root->left);
int right = high(root->right);
return left > right ? left + 1 : right + 1;
}
void levelOrder(BSTNode* root)
{
if (root == nullptr)return;
cout << "层序遍历:";
int h = high(root);
for (int i = 0; i < h; ++i)
{
levelOrder(root, i);
}
cout << endl;
}
void levelOrder(BSTNode* root, int level)
{
if (root == nullptr)
return;
if (level == 0)
{
cout << root->data << " ";
return;
}
levelOrder(root->left, level - 1);
levelOrder(root->right, level - 1);
}
非递归:
void levelOrder02(BSTNode* root)
{
queue<BSTNode*> que;
que.push(root);
while (!que.empty())
{
BSTNode* front = que.front();
if (front->left != nullptr)
{
que.push(front->left);
}
if (front->right != nullptr)
{
que.push(front->right);
}
cout << front->data << " ";
que.pop();
}
cout << endl;
}
2.1.4常见操作
// 查询操作
bool query(const T &val)
{
BSTNode *p = root;
bool flag = false;
while (p != nullptr)
{
if (val > p->data)
{
p = p->right;
}
else if (val < p->data)
{
p = p->left;
}
else
{
flag = true;
break;
}
}
return flag;
}
//递归查询
bool query02(BSTNode *node, const T &val)
{
if (node == nullptr)
return false;
if (node->data > val)
return query02(node->left);
else if (node->data < val)
return query02(node->right);
else
return true;
}
//二叉树的镜像
void mirror()
{
mirror(root);
}
void mirror(BSTNode* root)
{
if (root == nullptr)
return;
BSTNode* p = root->left;
root->left = root->right;
root->right = p;
mirror(root->left);
mirror(root->right);
}
//判断当前二叉树是否是一颗二叉搜索树
void IsBSTree()
{
cout << isBSTree(root) << endl;
}
//判断是否是二叉搜索树
bool isBSTree(BSTNode* root)
{
if (root == nullptr)
return true;
if (!isBSTree(root->left)) //L
{
return false;
}
if (tmp != nullptr) //V
{
if (tmp->data > root->data)
{
return false;
}
}
tmp = root;
return isBSTree(root->right); //R
//错误的代码:只满足做小于中,中小于右并不是一个BST树,有可能node的后继节点比node的父节点(根节点)还大
//因此需要时刻保持当前树的所有左节点或者根节点小于根节点或右节点
//left->data<root->data && root->data<right->data
//可以利用中序遍历的特点,遍历过的节点肯定小于 没遍历的节点,如果违反就不是BST树
//即拿一个tmp记录刚刚遍历的节点,在进行下一次判断时,避免根节点的前驱比根还大
//if (root == nullptr)
// return;
//if (root->left != nullptr&&root->data > root->right->data)
//{
// return false;
//}
//else if(root->right != nullptr&&root->left->data > root->data)
//{
// return false;
//}
//return isBSTree(root->left) && isBSTree(root->right);
}
//找出指定区间的元素
void findValue(vector<T> &vec,int i, int j)
{
findValue(root, vec, i, j);
}
//寻找i与j之间元素放在vec中
void findValue(BSTNode* root, vector<T>& vec, int i, int j)
{
if (root == nullptr)
return;
if (root->data > i) //>i才往下走 //L
{
findValue(root->left, vec, i, j);
}
if (root->data >= i && root->data <= j) //V
{
vec.push_back(root->data);
}
if (root->data < j) //<j才往下走 //R
{
findValue(root->right, vec, i, j);
}
}
//判断一颗BST树是不是平衡树,每一个左右子树的高度差不超过1
bool isBalancedTree()
{
int level = 0;
bool result = true;
isBalancedTree(root, level,result);
return result;
}
//判断是否是平衡二叉搜索树
int isBalancedTree(BSTNode* root, int level, bool& result)
{
//*把节点的层数返回 level 同时计算层数,不用使用high()多次递归调用*
if (root == nullptr)
{
return level;
}
int left = isBalancedTree(root->left, level + 1, result);
if (!result)
return left;
int right = isBalancedTree(root->right, level + 1, result);
if (!result)
return right;
if (abs(left - right) > 1)
{
result = false;
}
return max(left, right);
}
//找出两个元素的LCA公共祖先节点并返回
//===>不需要遍历整棵树,
//而是从根节点沿一条路从上往下走
int getLCA(int a, int b)
{
if (!query(a) || !(query(b)))
{
throw "";
}
int min = a;
int max = b;
if (a > b)
{
min = b;
max = a;
}
return getLCA(root, min, max);
}
//寻找a与b之间的数
int getLCA(BSTNode* root, int a, int b)
{
if (root == nullptr)
{
throw "no LCA";
}
//root大于a并且大于b 往左走
if (root->data > a && root->data > b)
{
return getLCA(root->left, a, b);
}
//root小于a并且小于b 往右走
else if (root->data < a && root->data < b)
{
return getLCA(root->right, a, b);
}
//root>a并且root<b 找到了!!
else
{
return root->data;
}
}
//判断参数传入的二叉树是否是当前二叉树的一颗子树
bool isChildTree(BSTree<T> tree)
{
if (!query(tree.root->data))
{
return false;
}
BSTNode *p = root;
while (p != nullptr)
{
if (p->data < tree.root->data)
{
p = p->right;
}
else if (p->data > tree.root->data)
{
p = p->left;
}
else
{
break;
}
}
if (p == nullptr)
return false;
return isChildTree(p, tree.root);
}
//判断是否是子树
bool isChildTree(BSTNode* node1, BSTNode* node2)
{
if (node1 == nullptr && node2 == nullptr)
return true;
if (node1 == nullptr)
return false;
if (node2 == nullptr)
return true;
if (node1->data != node1->data)
return false;
return isChildTree(node1->left, node2->left)
&& isChildTree(node1->right, node2->right);
}
//找第K小的元素值
//中序遍历 《=====》
//找第K大的元素值, 只需要在中序遍历时从右节点往左节点走
// (先走右root->right) 不用计算Number()-k+1 免去Number()的递归
int getTopKVlaue(int k)
{
int i = 0;
BSTNode *node = getTopKVlaue(root, k,i);
if (node == nullptr)
{
throw"no node!";
}
else
{
return node->data;
}
}
//寻找第K小节点
BSTNode* getTopKVlaue(BSTNode* root, int k, int& t)
{
if (root == nullptr)
return nullptr;
BSTNode* node1 = getTopKVlaue(root->left, k, t);
if (node1 != nullptr)
return node1;
if (++t == k)
{
return root;
}
return getTopKVlaue(root->right, k, t);
}