代码
#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中都使用了引用传递,那么本身就不需要考虑在找个指针指向待删除节点的父节点了。 因为我们本身删除的这个节点是 它父指向该节点的指针的引用!!!