搜索树基本增改查删
核心是查,增就是在最后root ==NULL的地方加入新的节点。删相对复杂一点点,涉及到前驱后续的问题。前驱(左子树的最大值替代当前root)即,所有小的里面找个最大的。后序(右子树的最小值替代当前root),即所有大的里面找一个最小的。
如Leetcode 450. 删除二叉搜索树中的节点
class Solution {
public:
TreeNode* findMax(TreeNode* root) //找前驱,以root为根的最大节点
{
while (root->right != NULL)
root = root->right;
return root;
}
TreeNode* findMin(TreeNode* root)
{
while (root->left != NULL)
root = root->left;
return root;
}
void myDelete(TreeNode* &root,int key)
{
if (root == NULL) return; //不存在key大小的节点
if (root->val == key)
{
if (root->left == NULL && root->right == NULL) //找到的是叶节点,直接删除
root = NULL;
else if (root->left) //前驱优先还是后序优先取决于题目,本题没有限制,所以有两种结果
{
TreeNode* pre = findMax(root->left); //找到前驱节点,root左儿子子树的最大值
root->val = pre->val; //前驱节点的数值覆盖root
myDelete(root->left, pre->val);
}
else if (root->right)
{
TreeNode* behind = findMin(root->right);
root->val = behind->val;
myDelete(root->right, behind->val);
}
}
else if (root->val > key)
myDelete(root->left, key);
else
myDelete(root->right, key);
}
TreeNode* deleteNode(TreeNode* root, int key) {
if (root == NULL) return root;
myDelete(root, key);
return root;
}
};
【PTA】1043 Is It a Binary Search Tree
大意是告诉你一个数列,让你判断这数列是搜索树的前序数列还是镜像前序数列(左右相反)。
所以,只需要先建二叉搜索树,然后用两种不同的左右顺序分别前序遍历这个树,和原始的ORI数列做对比。如果一样则是yes,按照镜像或者常规的方式后序遍历,输出即可。
需要注意的几点:
1.前序遍历的数列,就是二叉搜索树的建树插入顺序。
2.***Insert里面的root必须带上传址符号&。***这是因为一开始root是NULL的,新的插入进去,就把root这个位置的数据全部改掉了。如果不带,则只是创建了一个root的临时副本,root传出函数的时候还是null的。
/* A 1043*/
#include <stdio.h>
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
struct TreeNode
{
TreeNode* left;
TreeNode* right;
int val;
};
TreeNode* NewNode(int num)
{
TreeNode* root = new TreeNode;
root->val = num;
root->left = NULL;
root->right = NULL;
return root;
}
void Insert(TreeNode* &root, int key) // 传地址符的重要性!!!
{
if (root == NULL)//如果没找到,当前位置就是插入位置
{
root = NewNode(key);//新建节点
return;
}
if (key < root->val) //若节点值大于键值,应寻找左子树插入
Insert(root->left, key);
else
Insert(root->right, key);
}
TreeNode* CreateBST(vector<int> pre,int n)
{
TreeNode* root = NULL; //初始的时候root为空,第一个元素对着空插入
for (int i = 0; i < n; i++)
Insert(root, pre[i]);
return root;
}
vector<int> pre;
vector<int> Mirropre;
void preorder(TreeNode* root) //常规前序
{
if (root == NULL)
return;
pre.push_back(root->val);
preorder(root->left);
preorder(root->right);
}
void Mirropreorder(TreeNode* root) //镜像前序
{
if (root == NULL)
return;
Mirropre.push_back(root->val);
Mirropreorder(root->right);
Mirropreorder(root->left);
}
vector<int> postres;
void postorder(TreeNode* root) //常规后序
{
if (root == NULL)
return;
postorder(root->left);
postorder(root->right);
postres.push_back(root->val);
}
void Mirpostorder(TreeNode* root) //镜像后序
{
if (root == NULL) return;
Mirpostorder(root->right);
Mirpostorder(root->left);
postres.push_back(root->val);
}
int main()
{
int N;
vector<int> ori;
cin >> N;
for (int i = 0; i < N; i++)
{
int tmp; cin >> tmp;
ori.push_back(tmp);
}
TreeNode* root = CreateBST(ori, N);
preorder(root);
Mirropreorder(root);
if (pre == ori) //前序遍历和原数组一致
{
cout << "YES" << "\n";
postorder(root);
for (int i = 0; i < N; i++)
{
if (0 == i) cout << postres[i];
else
cout << " " << postres[i];
}
}
else if (Mirropre == ori)
{
cout << "YES" << "\n";
Mirpostorder(root);
for (int i = 0; i < N; i++)
{
if (0 == i) cout << postres[i];
else
cout << " " << postres[i];
}
}
else
cout << "NO";
return 0;
}
二叉搜索树的左右关系
leetcode 108
找到根节点,奇数的时候根就是中序遍历的中间位置的数字,偶数取中间位置-1。然后根的左边全是左子树,右边全是右子树。把左右的角标作为参数传入递归函数就可以了。
class Solution {
public:
TreeNode* buildBST(vector<int>& nums,int left,int right)
{
if(left > right)
return nullptr;
int mid = (left +right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = buildBST(nums,left,mid - 1);
root->right = buildBST(nums,mid +1,right);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return buildBST(nums,0,nums.size() - 1);
}
};
[PTA]1064 完全二叉搜索树
用时:fail
利用完全二叉树的性质:
1.数组表示时,根与左右子树有脚标关系。
2.层序遍历就是数组表示法的数组存放数据顺序(因为完全嘛)。
利用二叉搜索树的性质:
1.中序遍历是有序的,在本体中和排序后的Ori数组相一致。
所以,利用脚标中序遍历空的完全二叉搜索树数组。依次填入排序后的Ori数组。最后直接输出这个数组,就是层序遍历结果啦。
注意:
1.填入数据的那一个index要设为全局。理由在标注中说了。
2.root要设为1!!!因为如果是0的话,左边就是2root +1 ,右边是2root。这样同一层左边还比右边大,就不是完全树了!(以后都最好root设为1)
#include <stdio.h>
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
const int maxN = 1005;
int CBT[maxN];
vector<int> ori;
int N,index = 0; //index对应ori的脚标,因为不随递归规律变化,所以设为全局变量
void inorder(int root) //中序遍历是有序的,和排序后的ori对应上了
{
if (root > N) //超出树的数组范围,直接返回
return;
inorder(root * 2);
CBT[root] = ori[index++]; //root实际上是CBT的脚标,代表当前位置。++表示在Ori中是依次的
inorder(root * 2 + 1);
}
int main()
{
cin >> N;
for (int i = 0; i < N; i++)
{
int num; cin >> num;
ori.push_back(num);
}
sort(ori.begin(), ori.end());
inorder(1);
for (int i = 1; i <= N; i++)
{
if (1 == i)
cout << CBT[1];
else
cout <<" " <<CBT[i];
}
return 0;
}
相似题:1099 Build A Binary Search Tree (30分)
用时:20min
与上题几乎一模一样,只不过遍历的方式略有不同(一个是普通搜索二叉树,一个是完全搜索二叉树。故不能用index和2*index的规律了)。所以用个结构体数组来装树。
#include <stdio.h>
#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
int const maxnum = 105;
struct Node
{
int left, right,val;
};
Node a[maxnum];
vector<int> vec;
int index = 0;
vector<int> result;
void inorder(int root) //负责填入每个结点的val
{
if (root == -1)
return;
inorder(a[root].left);
a[root].val = vec[index++];
inorder(a[root].right);
}
void levelorder(int root)
{
queue<int> Q;
Q.push(root);
int p;
while (!Q.empty())
{
p = Q.front(); Q.pop();
result.push_back(a[p].val);
if (a[p].left != -1) Q.push(a[p].left);
if (a[p].right != -1) Q.push(a[p].right);
}
}
int main()
{
int N;
cin >> N;
for (int i = 0; i < N; i++)
cin >> a[i].left >> a[i].right;
for (int i = 0; i < N; i++)
{
int num;
cin >> num;
vec.push_back(num);
}
sort(vec.begin(), vec.end());
inorder(0);//fill val to every Node
levelorder(0);
for (int i = 0; i < N; i++)
{
if (0 == i) cout << result[i];
else
cout << " " << result[i];
}
return 0;
}
二叉平衡树高度
leetcode 110
题意大概就是让你判断一个树是不是二叉平衡树。
由于自顶向下逐个判断高度有大量同步计算,基本效率狗带。所以采取自下而上的方法来整,思想上有点像后续遍历。
先判断左右儿子的高度。再判断当前根的高度是不是需要返回-1。非常巧妙的是用leftheight和rightheight来接住返回的值,经过max(a,b) + 1的计算,就可以将每个节点的子树高度带入上一层递归了。
注意:判断条件中,lefth == -1 ,righth == -1这个不能省略。因为算到-1之后并没有跳出,如果当前层级的根的另一边儿子也是-1,就让根返回了错误的判断。
class Solution {
public:
int height(TreeNode* root)
{
if (root == NULL)
return 0;
int leftheight = height(root->left);
int rightheight = height(root->right);
if ( leftheight == -1 || rightheight == -1 || abs(leftheight - rightheight) > 1)
return -1;
else
return max(leftheight, rightheight) + 1; //巧妙!!!思想要记得
}
bool isBalanced(TreeNode* root) {
return height(root) >= 0 ? true : false; //最后判断不是-1就说明是二叉平衡树
}
};
有序数组转二叉搜索树
LeetCode.108
和下面的PTA1066是不一样的,因为PTA那道是给的插入顺序,而这道题是给的有序数组来转AVL.
class Solution {
public:
TreeNode* buildBST(vector<int>& nums,int left,int right)
{
if(left > right)
return nullptr;
int mid = (left +right) / 2;
TreeNode* root = new TreeNode(nums[mid]);//find&build root
root->left = buildBST(nums,left,mid - 1);
root->right = buildBST(nums,mid +1,right);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return buildBST(nums,0,nums.size() - 1);
}
};
[PTA]1066 平衡树的建树
用时:70Min
模板题,需要注意的几点:
1.NewNode那边,节点的初始高度是1.(这样就可以和空节点区分了)
2.计算平衡指数那边,用左边减去右边(右边减去左边也行,但是后面符号全要反过来)
3.更新高度:用左边、右边取一个最大的,加一。
4.LL型用左旋(用右旋会报错)
5.LR型的第一步是对root的左子树做左旋。一开始把左子树那边视为整体,先调整整体内部,再做一次右旋就完成了处理。
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
struct TreeNode
{
int val, height;
TreeNode* left, *right;
};
int getHeight(TreeNode* root)
{
if (root == NULL) return 0;
return root->height;
}
int getBalance(TreeNode* root)
{
return getHeight(root->left) - getHeight(root->right);//所以左儿子高的话,平衡因子为正数
}
void updateH(TreeNode* root)
{
root->height = max(getHeight(root->left), getHeight(root->right)) + 1;//算高度
}
TreeNode* NewNode(int v)
{
TreeNode* root = new TreeNode;
root->val = v;
root->height = 1; //初始高度1
root->left = NULL;
root->right = NULL;
return root;
}
void L(TreeNode* &root) //左旋调整
{
TreeNode* temp = root->right;
root->right = temp->left;
temp->left = root;
updateH(root);
updateH(temp);
root = temp;
}
void R(TreeNode* &root) //右旋
{
TreeNode* temp = root->left;
root->left = temp->right;
temp->right = root;
updateH(root);
updateH(temp);
root = temp;
}
void insert(TreeNode* &root , int v)
{
if (root == NULL) //找到空位置,填入
{
root = NewNode(v);
return;
}
if (v < root->val)
{
insert(root->left, v); //待插入值比当前节点小,往左子树插入
updateH(root);//更新当前节点高度
if (getBalance(root) == 2) //当前节点平衡指数等于2则需要调整
{
if (getBalance(root->left) == 1) //LL型用右旋!
R(root);
if (getBalance(root->left) == -1)
{
L(root->left); //对左子树做一次左旋
R(root);
}
}
}
if (v > root->val)
{
insert(root->right, v);
updateH(root);
if (getBalance(root) == -2)
{
if (getBalance(root->right) == -1)
L(root);
if (getBalance(root->right) == 1)
{
R(root->right);
L(root);
}
}
}
}
int main()
{
int N;
cin >> N;
TreeNode* root = NULL;
for (int i = 0; i < N; i++)
{
int tmp;
cin >> tmp;
insert(root, tmp);
}
cout << root->val;
return 0;
}
[PTA]1123 平衡树和判断是否是完全二叉树
大意:给一个插入序列,让你建一个平衡树。然后层序遍历这个平衡树,顺便判断是否是完全二叉树。
用时:120min debug太久 最后还挂了两个2分的点
解法:
平衡树建树是一套末班来的,需要注意几个细节,不然调试爆炸。
判断完全二叉树是根据以下两点:
1.如果某节点有右儿子却没有左儿子,那么这个树必然不是完全二叉树了。
2.如果一棵树在层序遍历时已经触及了叶子节点,那么这个树就不能再在后期的层序遍历中出现叶子节点了。(用一个常数代表“叶子模式”的开启)
需要注意的点:
1.左右旋,Insert得root都必须带&,不然直接出错,而且极难Debug。!!! 目前的理解是只要改变了树的结构(而不是像遍历那样只是取值),就需要加上&直接去地址里面操作,而不是操作副本。
2.LL型是是一个右旋即可。(往左偏的太多,所以要向右扭一下)。反之亦然。 而LR型是对root的左子树进行一次左旋,再对root整体进行一次右旋。(注意是root的左子树先,不确定就画图!)
RL同理。这两兄弟的处理方法和字母顺序是一样的,只要搞清楚操作对象即可。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <ctype.h>
#include <queue>
using namespace std;
/*用类常规建立AVL树,但为什么中序遍历的时候丢失了节点?*/
struct TreeNode
{
int val, height;
TreeNode* left, *right;
};
class AVL
{
public:
void Insert(TreeNode* &root, int v)
{
if (root == NULL) //如果找到空位置,即是插入位置
{
root = NewNode(v);
return;
}
if (v < root->val) //值比较小,往左子树上插
{
Insert(root->left, v);
UpdataH(root); //插入完成,更新当前节点高度
if (BalanceIndex(root) == 2) //判断当前结点高度有无超标
{
if (BalanceIndex(root->left) == 1)//LL型树
R(root);
else if (BalanceIndex(root->left) == -1)//LR !!!!一定要有else,如果更新后再搞就乱了都
{
L(root->left);
R(root);
}
}
}
else
{
Insert(root->right, v);
UpdataH(root);
if (BalanceIndex(root) == -2)
{
if (BalanceIndex(root->right) == -1)//RR型
L(root);
else if (BalanceIndex(root->right) == 1)//RL
{
R(root->right);
L(root);
}
}
}
}
private:
TreeNode* NewNode(int v) //建立新节点
{
TreeNode* root = new TreeNode;
root->val = v;
root->height = 1;
root->left = NULL;
root->right = NULL;
return root;
}
int getH(TreeNode* root) //得到节点高度
{
if (root == NULL) return 0;
return root->height;
}
void UpdataH(TreeNode* root) //更新当前高度
{
root->height = max(getH(root->left), getH(root->right)) + 1;
}
int BalanceIndex(TreeNode* root) //计算节点平衡因子
{
return getH(root->left) - getH(root->right);
}
void L(TreeNode* &root) //左旋 对树的结构操作用&
{
TreeNode* temp = root->right;
root->right = temp->left;
temp->left = root;
UpdataH(root);
UpdataH(temp);
root = temp;
}
void R(TreeNode* &root)//右旋
{
TreeNode* temp = root->left;
root->left = temp->right;
temp->right = root;
UpdataH(root);
UpdataH(temp);
root = temp;
}
};
int IsCBT = 1;
int leaf = 0;
vector<int> res;
void levelorder(TreeNode* root) //一旦出现了叶子,由于是层序遍历,后面就必须全是叶子了
{
queue<TreeNode* > Q;
Q.push(root);
TreeNode* p;
while (!Q.empty())
{
p = Q.front(); Q.pop();
res.push_back(p->val);
if (p->left == NULL && p->right) //有右儿子没有做儿子肯定不是完全二叉树
IsCBT = 0;
if (leaf && (p->left || p->right))//开启叶子模式后,不能有儿子了
IsCBT = 0;
if (p->left) Q.push(p->left);
if (p->right) Q.push(p->right);
if (p->left == NULL && p->right == NULL)
leaf = 1; //开启叶子模式
}
}
int main()
{
int N;
cin >> N;
AVL avl;
TreeNode* root = NULL;
for (int i = 0; i < N; i++)
{
int v;
cin >> v;
avl.Insert(root, v);
}
levelorder(root);
for (int i = 0; i < res.size(); i++)
{
if (i == 0) cout << res[i];
else cout << " " << res[i];
}
if (1 == IsCBT) cout << "\n" << "YES";
else cout << "\n" << "NO";
return 0;
}
[PTA]1135 Is It A Red-Black Tree 红黑树
题意:给一个前序遍历,让你判断这个树是否是红黑树。
题解:由于我们知道仅仅靠一个前序遍历是没有办法建立出一个唯一的树,然而三个判断条件并不介意左右儿子的顺序。所以我们就可以按照搜索树那样把小的放在左边。 前序遍历又正好是自上而下的建立,所以每次传入一个数字,就连接一次节点即可。
TreeNode* BuildTree(TreeNode* root,int v)
{
if (root == NULL) //第一个数字进来,啥都没有,从空的开始建立
{
root = new TreeNode;
root->val = v;
root->left = root->right = NULL;
}
else if (abs(v) <= abs(root->val))
root->left = BuildTree(root->left, v);
else
root->right = BuildTree(root->right, v);
return root;
}
现在树有了,需要有三个判断条件:
1.根结点是否为⿊⾊
2.如果⼀个结点是红⾊,它的孩⼦节点是否都为⿊⾊
3.从任意结点到叶⼦结点的路径中,⿊⾊结点的个数是否相同
1非常简单,我们主要看2和3;
2其实就是遍历一遍,然后在每层判断当前节点是红的情况下左右儿子是不是都是黑的,如果左右儿子存在却是红的,就返回一个false上去。
bool JudgeSon(TreeNode* root)
{
if (root == NULL) //只剩叶子,必然满足条件
return true;
if (root->val < 0) //当前的节点是红 //当前“我”做的事情
{
if (root->left && root->left->val < 0) //左儿子存在且为红
return false;
if (root->right && root->right->val < 0)
return false;
}
return JudgeSon(root->left) && JudgeSon(root->right);
}
主要难点:
3 的话比较复杂,先用一个getblacknum函数来获取从当前root出发,每一层到叶子的黑色节点数量。
然后用JudgeBlack函数判断对当前节点来说,左右儿子返回的黑色节点数量。
int GetBlackNum(TreeNode* cur)
{
if (cur == NULL)
return 0;
int l = GetBlackNum(cur->left);
int r = GetBlackNum(cur->right);
return cur->val > 0 ? max(l, r) + 1 : max(l, r); //本节点为黑的话,黑的数量要加一
}
bool JudgeBlack(TreeNode* root)
{
if (root == NULL)
return true;
int l = GetBlackNum(root->left);
int r = GetBlackNum(root->right);
if (l != r) return false;
return JudgeBlack(root->left) && JudgeBlack(root->right);
}
所以整体的解法,就是这三个判断条件的组合:
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode
{
int val;
TreeNode* left, *right;
};
TreeNode* BuildTree(TreeNode* root,int v)
{
if (root == NULL) //第一个数字进来,啥都没有,从空的开始建立
{
root = new TreeNode;
root->val = v;
root->left = root->right = NULL;
}
else if (abs(v) <= abs(root->val))
root->left = BuildTree(root->left, v);
else
root->right = BuildTree(root->right, v);
return root;
}
bool JudgeSon(TreeNode* root)
{
if (root == NULL) //只剩叶子,必然满足条件
return true;
if (root->val < 0) //当前的节点是红 //当前“我”做的事情
{
if (root->left && root->left->val < 0) //左儿子存在且为红
return false;
if (root->right && root->right->val < 0)
return false;
}
return JudgeSon(root->left) && JudgeSon(root->right);
}
int GetBlackNum(TreeNode* cur)
{
if (cur == NULL)
return 0;
int l = GetBlackNum(cur->left);
int r = GetBlackNum(cur->right);
return cur->val > 0 ? max(l, r) + 1 : max(l, r); //本节点为黑的话,黑的数量要加一
}
bool JudgeBlack(TreeNode* root)
{
if (root == NULL)
return true;
int l = GetBlackNum(root->left);
int r = GetBlackNum(root->right);
if (l != r) return false;
return JudgeBlack(root->left) && JudgeBlack(root->right);
}
int main()
{
int N;
cin >> N;
for (int n = 0; n < N; n++)
{
int nodenum;
cin >> nodenum;
TreeNode* root = NULL;
int rootval;
for (int i = 0; i < nodenum; i++)
{
int val;
cin >> val;
if (i == 0) rootval = val; //保留根的值,做判断条件
root = BuildTree(root, val);
}
if (rootval > 0 && JudgeSon(root) && JudgeBlack(root))
cout << "Yes\n";
else
cout << "No\n";
}
return 0;
}