【PTA刷题】甲级 二叉搜索树与平衡树

搜索树基本增改查删


核心是查,增就是在最后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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值