PTA 7-3 二叉搜索树的删除操作

代码

#include<iostream>
using namespace std;
#include<queue>

typedef struct BSNode {
	int data;
	BSNode* left;
	BSNode* right;
}BSNode, * BSTree;


void SearchBST(BSTree& T, int key, BSTree& p, BSTree f, bool& flag)//f为p的父节点 起始为NULL
{
	// 在根指针T所指二叉排序树中递归地查找其关键字等于key的数据元素,若查找
   // 成功,则指针p指向该数据元素结点,并返回TRUE,否则指针p指向查找路径上
   // 访问的最后一个结点并返回FALSE,指针f指向T的双亲,其初始调用值为NULL
	if (!T)
	{
		p = f;  //查找不成功 p指向最后一个节点而非NULL
		flag = false;
	}
	else if (T->data == key)
	{
		flag = true;
		p = T;
	}
	else if (T->data < key)
	{
		SearchBST(T->right, key, p, T, flag);
	}
	else {
		SearchBST(T->left, key, p, T, flag);
	}
}

void InsertBST(BSTree& T, int key)
{
	BSTree p;
	bool flag;
	SearchBST(T, key, p, NULL, flag);
	if (flag)
	{
		return;

	}
	else {
		BSTree s = new BSNode();
		s->data = key;
		s->left = s->right = NULL;

		if (p == NULL)//如果p仍为空 显然为空树
		{
			T = s;
		}
		else if (p->data < key)
		{
			p->right = s;
		}
		else {
			p->left = s;
		}
	}

}

void Delete(BSTree& p, BSTree f)//f为p父节点 
{
	/*
	如果一个结点是叶子结点,则直接删除;
	如果一个结点的左子树不为空, 则将该结点的值设置为其左子树上各结点中的最大值,并继续删除其左子树上拥有最大值的结点;
	如果一个结点的左子树为空但右子树不为空,则将该结点的值设置为其右子树上各结点中的最小值,并继续删除其右子树上拥有最小值的结点。
	//此处是题目要求  这里一定要递归  
	如下注释去做会出错的数据:
	8
	86 47 55 15 1 38 21 76
	5
	55 1 76 47 86
	递归正确输出:38 21 15
	以下注释输出:38 15 21
	*/
	BSTree q=NULL, s=NULL;
	if (!p)return;

	if (p->left)
	{
		q = p;
		s = p->left;  //q指向s的父
		while (s->right)
		{
			q = s;
			s = s->right;
		}

		p->data = s->data;
		s即为左侧最大   但需注意p左子树没有右孩子的情况  即 q还为p s 还为p->left;
		//if (q == p)
		//{
		//	/*	p->data = s->data;*/
		//	p->left = s->left;
		//	delete s;
		//	s = NULL;
		//}
		//else {
		//	/*		p->data = s->data;*/
		//	q->right = s->left;
		//	delete s;
		//	s = NULL;
		//}
		Delete(s, q);
		return;

	}
	else if (p->right)
	{
		q = p;
		s = p->right;
		while (s->left)
		{
			q = s;
			s = s->left;
		}
		p->data = s->data;
		//if (q == p)
		//{
		//	q->right = s->right;   //删s需对s的父负责
		//}
		//else
		//{
		//	q->left = s->right;
		//}
		//delete s;
		//s = NULL;
		Delete(s, q);
		return;
	}
	else
	{
		//q = p;   
		//p = NULL;  //对p的父节点负责  当p的父节点存在时
		//delete q;
		//q = NULL;
		/*
		上面负责不了*/
		if (f)
		{
			if (f->left == p)
			{
				f->left = NULL;
			}
			else
				f->right = NULL;
		}
		q = p;
		p = NULL;
		delete q;
		q = NULL;
		return;
	}

}
//传一个父节点吧  f初始值为NULL
void DeleteBST(BSTree& T, BSTree f, int key)
{
	if (!T)return;
	else if (T->data == key)
	{
		Delete(T, f);
	}
	else if (T->data < key)
	{
		DeleteBST(T->right, T, key);
	}
	else
	{
		DeleteBST(T->left, T, key);
	}
}
void LayerTraverse(BSTree& T)
{
	bool flag = false;
	if (!T)return;
	BSTree temp;

	queue<BSTree>q;
	q.push(T);
	while (!q.empty())
	{
		temp = q.front();
		q.pop();
		if (flag)
		{
			printf(" ");
		}
		flag = true;
		printf("%d", temp->data);
		/*	s += to_string(temp->data);*/

		if (temp->left != NULL)
		{
			q.push(temp->left);
		}
		if (temp->right != NULL)
		{
			q.push(temp->right);
		}
	}
	cout << endl;

}

int main()
{
	BSTree T = NULL;
	
	int count, i, temp, deletenum;
	cin >> count;
	for (i = 0; i < count; i++)
	{
		cin >> temp;
		InsertBST(T, temp);

	}
	cin >> deletenum;
	for (i = 0; i < deletenum; i++)
	{
		cin >> temp;
		DeleteBST(T, NULL, temp);
	}
	LayerTraverse(T);
	return 0;
}






这个题我错的最为关键的地方有两个:
其一:

//传一个父节点吧  f初始值为NULL
void DeleteBST(BSTree& T, BSTree f, int key)

在这里应该加一个引用
因为,比如我们去删除一个只有一个节点的树,如果不使用引用那么,T就是一个新的指针变量,它的值是根的地址,当我们释放掉根的存储空间,并将该T设置为NULL,但是它并未影响到原来的指向这段空间的根啊,那原来的根不就是一个野指针了吗,就导致了读取访问权限异常。
其二:

void Delete(BSTree& p, BSTree f)//f为p父节点 

Delete函数这里使用了递归删除,这是题目本身决定的。
当我们删除一个节点时,该节点若有左右子树,我们删除后可以选左子树的最小值,也可以选右子树的最大值。但删除方式并不只有这两种!!!两种的内部实现还可以不同的,在这里他是递归删除赋予其值的节点,而我们也可以找到赋予其值的节点,譬如他是左子树的最大值,我们天然的可以将该节点的左子树赋予它的父的右子树(当该节点本身不是删除节点的左孩子时),而不是去递归!!
这两种方法得到的结果是不一样的!!!
具体可见该串数据的处理:

	8
	86 47 55 15 1 38 21 76
	5
	55 1 76 47 86
	递归正确输出:38 21 15
	我所说的方式输出:38 15 21

以下是大佬代码

#include <bitsdc++.h>
using namespace std;
typedef long long ll;
#define pii pair<int,int>
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
struct BST{
	struct node {
		int val;
		node* ls;
		node* rs;
	}*rt;
	BST() { rt=NULL;}
	void insert(int x) {
		function<node*(node*,int)> dfs=[&](node* p,int x) {
			if (p==NULL) {
				node* c=(node*)malloc(sizeof(node));
				c->val=x;
				c->ls=NULL;
				c->rs=NULL;
				return c;
			}
			if (p->val>=x) p->ls=dfs(p->ls,x);
			else p->rs=dfs(p->rs,x);
			return p;
		};
		rt=dfs(rt,x);
	}
	int querymax(node *p) {
		int res=p->val;
		if (p->ls) res=max(res,querymax(p->ls));
		if (p->rs) res=max(res,querymax(p->rs));
		return res;
	}
	int querymin(node *p) {
		int res=p->val;
		if (p->ls) res=min(res,querymin(p->ls));
		if (p->rs) res=min(res,querymin(p->rs));
		return res;
	}
	void erase(int x) {
		function<node*(node*,int)> dfs=[&](node* p,int x) {
			if (!p->ls&&!p->rs) {
				free(p);
				node* tmp=NULL;
				return tmp;
			}
			if (p->val>x) p->ls=dfs(p->ls,x);
			else if (p->val<x) p->rs=dfs(p->rs,x);
			else if (p->ls) {
				p->val=querymax(p->ls);
				p->ls=dfs(p->ls,p->val);
			}
			else if (p->rs) {
				p->val=querymin(p->rs);
				p->rs=dfs(p->rs,p->val);
			}
			return p;
		};
		rt=dfs(rt,x);
	}
	void PreOrderTraverse() {
		function<void(node*)> dfs=[&](node* p) {
			if (p==NULL) return;
			cout<<p->val<<' ';
			dfs(p->ls);
			dfs(p->rs);
		};
		dfs(rt);
	}
	void LevelOrderTraverse() {
		queue<node*>q;
		q.push(rt);
		vector<int>ans;
		while (!q.empty()) {
			node *p=q.front();
			ans.push_back(p->val);
			q.pop();
			if (p->ls) q.push(p->ls);
			if (p->rs) q.push(p->rs);
		}
		for (int i=0;i<(int)ans.size();i++) cout<<ans[i]<<" "[i==(int)ans.size()-1];
	}
};
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	BST tr;
	int n;
	cin>>n;
	for (int i=1;i<=n;i++) {
		int x;
		cin>>x;
		tr.insert(x);
	}
	int k;
	cin>>k;
	for (int i=1;i<=k;i++) {
		int x;
		cin>>x;
		tr.erase(x);
	}
	tr.LevelOrderTraverse();
	return 0;
}

一个文件

emm,当出错时不知道为啥,了解了对数器什么的,也还得去学,就直接向大佬请教,得到了一个对拍文件。
在这里插入图片描述

其中,a.cpp填写测试代码,std填写标准代码,然后运行.bat文件即可,当输出不一致时,bat程序pause,这时,去查看a.in 里面内容就是不一致的东西了。

一些问题

我产生了一个疑问:
当我们删除一个左或右孩子时,难道不应该去考虑它的父节点的处理吗?
譬如 删除一个单独节点的左孩子,那现在有两个指针,当我们将该左孩子的指针delete,并设为NULL,那不也仅仅是将孩子的指针为NULL了,它原本的父节点的left还是指向原先的地址啊,并不是此时的NULL啊!!!那不就又会读取访问权限异常了???
所以下面的代码为啥能过pta呢?

#include<iostream>
#include<cstdlib>
#include<queue>
using namespace std;
typedef struct BinaryTree_Node{
    int key;
    BinaryTree_Node* lChild;
    BinaryTree_Node* rChild;
}*BT_Node;
void DeleteNode(BT_Node &node){//删除结点
    BT_Node temp=node;
    if(node->rChild==NULL&&node->lChild==NULL){//都为空
        node=node->lChild;
        free(temp);
    }else if(node->lChild!=NULL){//左
        BT_Node left=node->lChild;
        while (left->rChild!=NULL){
            temp=left;
            left=left->rChild;
        }
        node->key=left->key;//把值赋给那个结点然后删除这个结点
        if(temp!=node)DeleteNode(temp->rChild);
        else{
            DeleteNode(temp->lChild);
        }
    }else if(node->lChild==NULL&&node->rChild!=NULL){//右
        BT_Node right=node->rChild;
        while (right->lChild!=NULL){
            temp=right;
            right=right->lChild;
        }
        node->key=right->key;
        if(temp!=node)DeleteNode(temp->lChild);
        else{
            DeleteNode(temp->rChild);
        }
    }
}
void printf(BT_Node node){//打印
    bool flag=false;
    queue<BT_Node> q;
    q.push(node);
    while (!q.empty()){
        BT_Node temp=q.front();
        if(flag)cout<<" ";
        flag=true;
        cout<<temp->key;
        q.pop();
        if(temp->lChild!=NULL)q.push(temp->lChild);
        if(temp->rChild!=NULL)q.push(temp->rChild);
    }
    cout<<endl;
}
void Delete(BT_Node &node,int key){//找到删除
    if(node==NULL){
        return;
    }
    if(key==node->key){
        DeleteNode(node);
        return;
    }
    if(key<node->key){
        Delete(node->lChild,key);
    }else{
        Delete(node->rChild,key);
    }
}

void insert(BT_Node &node,int key){//插入
    if(node==NULL){
        node=(BT_Node)malloc(sizeof(BinaryTree_Node));
        node->lChild=NULL;
        node->rChild=NULL;
        node->key=key;
        return;
    }
    if(key<node->key){
        insert(node->lChild,key);
    }else{
        insert(node->rChild,key);
    }
}

int main(){
	BT_Node root=NULL;
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int key;
        cin>>key;
        insert(root,key);
    }
    int m;
    cin>>m;
    for (int i = 0; i < m; ++i) {
        int temp;
        cin>>temp;
        Delete(root,temp);
    }
    printf(root);
}

为什么呢,是因为传递了指针的引用, Delete(node->lChild,key); void Delete(BT_Node &node,int key),我们在删除时Delete的本身就是左子树指针的引用去不断传递,那么当我们free(node)时,我们free掉内存空间,紧接着使node=NULL;本身就是使得引用的原对象左右子树指针变为NULL了。
所以,在删除节点时若在DeleteNode和Delete中都使用了引用传递,那么本身就不需要考虑在找个指针指向待删除节点的父节点了。 因为我们本身删除的这个节点是 它父指向该节点的指针的引用!!!

题目描述: 给定一棵二叉排序树和一个值,求该值在树中出现的次数。 输入格式: 第一行包含整数 n,表示树中节点个数。 接下来 n 行,每行包含一个整数,表示当前节点的值。 最后一行包含一个整数x,表示需要统计出现次数的值。 输出格式: 共一行,包含一个整数,表示该值在树中出现的次数。 输入样例: ``` 5 2 1 2 3 4 2 ``` 输出样例: ``` 2 ``` 算法1: 递归遍历,统计值出现的次数。 时间复杂度 平均时间复杂度为O(logn),最坏时间复杂度为O(n)。 参考文献 Python 代码 ```python class TreeNode(object): def __init__(self, x): self.val = x self.left = None self.right = None class Solution(object): def searchBST(self, root, val): """ :type root: TreeNode :type val: int :rtype: bool """ if not root: return 0 if val == root.val: return 1 + self.searchBST(root.left, val) + self.searchBST(root.right, val) elif val < root.val: return self.searchBST(root.left, val) else: return self.searchBST(root.right, val) ``` 算法2: 迭代遍历,统计值出现的次数。 时间复杂度 平均时间复杂度为O(logn),最坏时间复杂度为O(n)。 参考文献 Python 代码 ```python class TreeNode(object): def __init__(self, x): self.val = x self.left = None self.right = None class Solution(object): def searchBST(self, root, val): """ :type root: TreeNode :type val: int :rtype: bool """ if not root: return 0 count = 0 stack = [root] while stack: node = stack.pop() if node.val == val: count += 1 if node.left and node.val >= val: stack.append(node.left) if node.right and node.val <= val: stack.append(node.right) return count ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值