代码中有详细注释
//
// Created by Cauchyshy on 2023/5/25.
//
#include <iostream>
#include <cstdio>
#include <malloc.h>
using namespace std;
#define TREE_DEEP 10
struct treeNode {
int a; // 数据成员
struct treeNode *pFather; // 父节点
struct treeNode *pLeft; // 左孩子
struct treeNode *pRight; // 右孩子
};
// 由于不需要前后指针 直接用树叶指针类型即可 装树几点地址即可
struct treeNode *stack[TREE_DEEP] = {0};
// 用下标最栈顶指示符即可 栈顶
int stacktop = -1; // -1表示空栈 因为下标从0开始 0元素就是一个栈内元素了
// 入栈
void push(struct treeNode *node) {
if (NULL == node)
return ;
stacktop++; // 栈顶标记先自加1
stack[stacktop] = node; // 然后对栈顶赋值
}
//出栈
struct treeNode * pop(void) {
if (stacktop == -1)
return NULL;
int pre = stacktop;
stacktop--;
return stack[pre];
}
// 插入 算法优化 记录左右指针的地址 出去之后直接加即可
void insert(struct treeNode **root, int data) {
// 创建节点 并且赋初值
struct treeNode *tp = (struct treeNode *) malloc(sizeof(struct treeNode));
if (NULL == tp) {
return ; // 创建失败 直接结束
}
tp->a = data;
tp->pLeft = NULL;
tp->pRight = NULL;
tp->pFather = NULL;
// 节点连接到树上
// 如果树根是空的 则该节点作为根节点
if (NULL == *root) {
*root = tp;
} else {
struct treeNode *temp = *root; // 定义中间变量遍历树
// struct treeNode *ji = temp;
// 循环找到接入点 因为找到后temp为NULL 没法操作 所以还得要个变量记录一下temp 等下做插入操作才行
// 找到是父节点 因为没有记录左还是右 所以出来后还要判断一下
struct treeNode **jilu = NULL;
struct treeNode *tt = temp;
while (temp != NULL) {
// jilu = temp;
tt = temp;
if (data > temp->a) {
jilu = &(temp->pRight); // 记录右指针地址
// tt = temp;
// 放右子树
//jilu = temp;
temp = temp->pRight;
} else {
// 记录左指针地址
jilu = &(temp->pLeft);
// tt = temp;
//jilu = temp;
temp = temp->pLeft; // 否则放在左子树
}
}
// 出来后就不需要判断 直接连接即可
*jilu = tp;
tp->pFather = tt;
}
}
// 中序遍历
void look(struct treeNode *root) {
if (NULL != root) {
look(root->pLeft);
printf("%d ", root->a);
look(root->pRight);
}
}
void createTree(struct treeNode **root, int *arr, int len) {
for (int i = 0; i < len; ++i) {
insert(root, arr[i]);
}
}
// 释放二叉树 从叶子节点一个一个释放 栈/队列 pop一个释放一个 注意释放的时机 四种遍历均可以
void freeTree(struct treeNode **root) {
struct treeNode *t = *root;
while(1) {
while (t != NULL) { // 左子树入栈
push(t);
t = t->pLeft;
}
if (-1 == stacktop)
break;
t = pop();
struct treeNode * tp = t; // 先记录
t = t->pRight;
free(tp);
}
*root = NULL; // 树根清空
}
// 二叉搜索树的查找复杂度 O(logn)
struct treeNode* find(struct treeNode *root, int val) {
struct treeNode *tp = root;
while (tp != NULL) {
if (tp->a == val) {
return tp;
}
if (tp->a > val) {
tp = tp->pLeft;
} else {
tp = tp->pRight;
}
}
// 没找到返回空
return NULL;
}
// 统计指定数据的个数
int Count(struct treeNode *root, int val) {
int ans = 0;
while (root != NULL) {
if (val == root->a)
ans++;
if (val > root->a)
root = root->pRight;
else
root = root->pLeft;
}
return ans;
}
void DeleteMid(struct treeNode *node) {
if (NULL == node)
return ;
// 左子树不为空 右子树为空
if (NULL != node->pLeft && NULL == node->pRight) {
// 判断是左子树还是右子树
if (node == node->pFather->pLeft)
node->pFather->pLeft = node->pLeft;
else
node->pFather->pRight = node->pLeft;
node->pLeft->pFather = node->pFather;
free(node);
}
// 左子树为空 右子树不为空
else if (NULL == node->pLeft && NULL != node->pRight) {
if (node == node->pFather->pRight)
node->pFather->pRight = node->pRight;
else
node->pFather->pLeft = node->pRight;
node->pRight->pFather = node->pFather;
free(node);
} else {
// 两边都有 既有左又有右
/*
* 也是两种处理办法
* 一种是左子树连在右子树的最左端
* 另外一种是将右子树连接在左子树的最右端
* */
// 这里采用右子树接在左子树的最右端
// 找叶子 找到左子树的最右端
struct treeNode *tp = node->pLeft;
while (tp->pRight != NULL) {
tp = tp->pRight;
}
// 链接
tp->pRight = node->pRight;
node->pRight->pFather = tp;
// 替换删除节点
node->pLeft->pFather = node->pFather;
if (node == node->pFather->pLeft)
node->pFather->pLeft = node->pLeft;
else
node->pFather->pRight = node->pLeft;
// 释放
free(node);
}
}
// 删除节点
// 1.删除根节点 2.删除叶子 3.删除中间节点
// 只有一个树根
// 根有左子树火种右子树
// 删除根 如果左右子树都有 有两种处理办法 选择其中一种即可
// 如果左子树为根 那么将右子树连接在左子树的最右端
// 如果右子树为根 那么将左子树连接在右子树的最左端
bool Delete(struct treeNode **root, int data) {
if (*root == NULL) {
return false;
}
// 查找节点
struct treeNode *node = find(*root, data);
if (NULL == node) {
return false;
}
// 找到 删除
// 删除节点是树根
if (node == *root) {
// 只有根一个节点
if (NULL == node->pLeft && NULL == node->pRight) {
free(node); // free(*root)
*root = NULL;
}
// 树只有左子树 右子树为NULL
else if ((node->pRight) == NULL && (node->pLeft) != NULL) {
// 根变成新根
*root = node->pLeft;
(*root)->pFather = NULL; // 变成根了 没有父节点了
free(node);
}
// 树只有右子树 左子树为NULL
else if ((node->pLeft) == NULL && (node->pRight) != NULL) {
// 根变成新根
*root = node->pRight;
(*root)->pFather = NULL; // 变成根了 没有父节点了
free(node);
}
// 树有左右子树
/* 这里采用左子树最右端连接右子树
* 1.先找到左子树的最右端
* 2.将根的右子树与该节点的右指针相连
* 3.将根变成新的根
* */
else {
// 找到最右边的叶子
struct treeNode *tp = (*root)->pLeft;
while (tp->pRight != NULL) {
tp = tp->pRight;
}
// 连接
tp->pRight = (*root)->pRight;
(*root)->pRight->pFather = tp;
// 释放旧根 换根节点
*root = (*root)->pLeft; // 左子树变为根
free((*root)->pFather); // free(node)
(*root)->pFather = NULL;
}
}
// 删除叶子节点
else if (NULL == node->pLeft && NULL == node->pRight) {
if (node == node->pFather->pLeft)
node->pFather->pLeft = NULL; // 左叶子
else if (node == node->pFather->pRight)
node->pFather->pRight = NULL; // 右叶子
free(node);
}
// 删除中间节点
// 3中情况 有左无右 有右无左 都有
else {
DeleteMid(node);
}
return true;
}
// 去重
// x循环将指定数据全部删除 然后添加一个新的该数据 因为增删是logn 所以问题不是很大
void remove(struct treeNode** root, int data) {
int cnt = Count(*root, data);
if (cnt <= 1)
return ; // 超过2个才去重 否则没必要
bool ok = true;
while (ok) {
ok = Delete(root, data);
}
insert(root, data);
}
// 修改指定数据
/*
* 由于是排序树 不能直接找到节点后就修改 这样会造成树结构的变化
* 可以先找到修改的节点 删除该节点 将修改后的数据添加到树种
* 一般来说排序二叉树是很少修改节点数据的
* */
void Change(struct treeNode** root, int data, int newVal) {
bool ok = true;
while (1) {
ok = Delete(root, data);
if (false == ok) {
break; // 删完了
}
insert(root, newVal);
}
}
int main() {
struct treeNode *root = NULL; // 树根
// insert(&root, 12);
// insert(&root, 13);
// insert(&root, 10);
// insert(&root, 12);
// 小技巧 如果想创建出来的排序树跟纸上画出来的一样(也就是结构一模一样) 只需要按照层序遍历的顺序插入即可
// int arr[] = {7, 4, 8, 2, 5, 12, 1, 3, 9, 10};
// int arr[] = {7, 4, 8, 2, 2, 2, 1, 3, 9, 10};
int arr[] = {10, 5, 2, 2, 7, 1, 2, 6, 2};
createTree(&root, arr, 9);
look(root);
printf("\n");
Change(&root, 2, 20);
look(root);
printf("\n");
freeTree(&root);
return 0;
}