下面要实现的二叉树操作有:
(先逐个说明,最后有完整代码,都由C语言实现)
1.二叉树的深度
思路:先递归求根节点左子树最大深度,再递归求根节点右子树的最大深度,最后进行比较,加上根结点的一层,返回深度
代码实现:
int Bitdepth(BiNode *bt) { //求二叉树的深度
int m, n;
if (bt == NULL)
return 0;
else {
m = Bitdepth(bt->Lchild); //递归计算左子树的深度
n = Bitdepth(bt->rchild); //递归计算右子树的深度
if (m > n)
return m + 1;
else
return n + 1;
}
}
2.二叉树的结点(叶子)个数
思路:判断条件,如果是叶子结点,就要判断是否左右孩子均为NULL,如果是叶子结点,就返回1,代表的就是计数+1(这里不能在函数里像以前一样设置一个count++的原因是,因为这时递归函数,每次调用都会重新初始化创建count,达不到计数的目的);如果不是叶子结点,就递归调用寻找左右子树的叶子节点,最后返回两者的和
代码实现:
int countleave(BiNode *bt) { //求叶子结点的个数
if (bt == NULL) //递归调用的出口
return 0;
else {
if (bt->Lchild == NULL && bt->rchild == NULL)
return 1;
else
return countleave(bt->Lchild) + countleave(bt->rchild);
}
}
int countnode(BiNode *bt) {//求结点的个数
if (bt == NULL) //递归调用的出口
return 0;
else
return countnode(bt->Lchild) + countnode(bt->rchild) + 1;
}
3.二叉链表存储和顺序存储(一维数组)相互转换
思路:这里使用了二叉树结点间编号的性质,具体如下图:
还是应用的递归的思想,这里要给函数传入数组应该有的长度,应用了完全二叉树的性质,深度为K的二叉树最多有2^k-1个结点,即对应的分配数组空间;只要该结点存在左右孩子(左右孩子编号小于2^k-1,就继续递归)
代码实现:
(这里自己写了求幂函数,因为c语言自带的pow(a,b)函数设定参数和返回值都是double类型,有时计算整数会出错)
void TreetoArr(BiNode *bt, ElemType *arr, int len, int pos) { //链表转换成数组
if (bt == NULL || arr == NULL || len < 1) {
return;
}
arr[pos] = bt->data;
trans.index[trans.k++] = pos; //index数组里保存了所有赋过值的结点下标,用于更改null值
if (pos * 2 + 1 < len) {
TreetoArr(bt->Lchild, arr, len, pos * 2 + 1);
}
if (pos * 2 + 2 < len) {
TreetoArr(bt->rchild, arr, len, pos * 2 + 2);
}
}
BiNode *ArrtoTree(ElemType *arr, int n, int pos) { //数组转换成链表
if (arr != NULL && pos < n) {
struct BiNode *bt;
bt = (struct BiNode *)malloc(sizeof(BiNode));
bt->data = arr[pos];
bt->Lchild = ArrtoTree(arr, n, pos * 2 + 1);
bt->rchild = ArrtoTree(arr, n, pos * 2 + 2);
return bt;
}
return NULL;
}
int pow_1(int a, int b) {//主函数中调用,计算len值
int sum = 1;
for (int i = 0; i < b; i++) {
sum = sum * a;
}
return sum;
}
4.删除二叉树子树
思路:这里的二叉树不是二叉有序树,删除条件是:
1,如果删除的结点是叶子结点,则删除该结点
2,如果删除的结点是非叶子结点,则递归删除该子树
这里分为两个删除函数,一个进行删除结果的输出,一个进行删除算法
代码实现:
int deleteNode1(BiNode *bt, ElemType x) {//这里的二叉树不是二叉有序树,1删除成功,0删除失败
/*删除条件是:
1.如果删除的结点是叶子结点,则删除该结点
2.如果删除的结点是非叶子结点,则删除该子树
*/
/*如果当前结点的左子结点不为空并且是要删除的结点就删除左结点*/
if (bt->Lchild != NULL && bt->Lchild->data == x) {
bt->Lchild = NULL;
return 1;
}
/*如果当前结点的右子结点不为空并且是要删除的结点就删除右结点*/
if (bt->rchild != NULL && bt->rchild->data == x) {
// clearstruct(bt, 2);
bt->rchild = NULL;
return 1;
}
/*如果当前结点的右子结点和右子结点都不是要删除的结点*/
if (bt->Lchild != NULL) {
//向左子树递归删除
int flag = deleteNode1(bt->Lchild, x);
if (flag == 1)
return 1;
}
if (bt->rchild != NULL) {
//上面没找的话,就向左子树递归删除
int flag = deleteNode1(bt->rchild, x);
if (flag == 1)
return 1;
}
return 0;
}
int deleteNode(BiNode *root, ElemType x) { //删除的判断,从而调用真正的删除函数
if (root != NULL) {
if (root->data == x) { //删除的结点是根结点
free(root);
return 1;//根结点比较特殊,在返回1时就不能调用输出二叉树的函数了,因为二叉树已经销毁了
printf("根结点删除成功!\n");
} else {
int isDelete = deleteNode1(root, x);
if (isDelete == 1)
printf("结点删除成功!\n");
else
printf("删除结点失败没有找到结点!\n");
return 2;//其他情况返回2
}
} else {
printf("二叉树为空!\n");
}
}
5.复制二叉树
思路:和创建二叉树相似,只不过每次要将原二叉树的data域赋给新的结点,容易出错的地方是要将递归调用后的值返回给指针域,这样才能正确的建立起连接关系
代码实现:
BiNode *copyTree(BiNode *bt) { //复制一个二叉树
struct BiNode *bt_1 = NULL;
if (bt == NULL) {
bt_1 = NULL;
} else {
bt_1 = (struct BiNode *)malloc(sizeof(BiNode));//为复制的二叉树创建结点
bt_1->data = bt->data;
bt_1->Lchild = copyTree(bt->Lchild); //递归复制左子树
bt_1->rchild = copyTree(bt->rchild); //递归复制右子树
}
return bt_1;
}
5.判断是否为完全二叉树
思路:调用Bitdepth(BiNode *bt)函数求二叉树深度,然后根据完全二叉树性质,调用求结点个数函数countnode(BiNode *bt),看结点数和2^k-1是否相等即可
代码实现:
int isCompleteTree(BiNode *bt) { //判断二叉树是不是完全二叉树,是返回1,不是返回2
int depth = Bitdepth(bt);
int sum = countnode(bt);
if (pow_1(2, depth) - 1 == sum)
return 1;
else
return 0;
}
完整代码:
总共分为四个包,三个头文件包,一个主函数包(哈哈我习惯分的比较多),实现上面1~6功能的函数都在第三个头文件包中,几个包互相调用就能实现代码功能
1.链队列.h
(包含二叉树链表的结点定义,队列的创建和基本操作,这个队列是二叉树的层序遍历要调用的,里面会有一些函数用不到,但我还是放一起了)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char Datatype;
typedef struct BiNode {
Datatype data;//数据内容
struct BiNode *Lchild;//指向左孩子结点
struct BiNode *rchild;//指向右孩子结点
} BiNode ;
typedef BiNode QElemType;
//定义队列结点类型
typedef struct QNode {
QElemType data;
struct QNode *next;
} QNode, *QueuePtr;
typedef struct {
QueuePtr front;//头指针
QueuePtr rear;//尾指针
} LinkQueue;
int InitQueue(LinkQueue *Q) {
QueuePtr node;
node = (QueuePtr)malloc(sizeof(QNode));
if (node != NULL) {
node->next = NULL; //创建头指针
Q->front = Q->rear = node;
return 1;
} else
return 0;
}
void DestroyQueue(LinkQueue *Q) {
QueuePtr temp;
while (Q->front != Q->rear) {
temp = Q->front->next;
free(Q->front);
Q->front = temp;
}
free(Q->rear);
}
void ClearQueue(LinkQueue *Q) { //保留队列头指针和队列尾指针,其他结点销毁
QueuePtr p, q;
Q->rear = Q->front; //将尾指针归位到头指针
Q->front->next = NULL;//天哪一定要记得把头节点next域设为NULL,不然还是被释放了的q,就乱指了
q = Q->front->next; //队列头
while (q != NULL) {
p = q->next;
free(q);
q = p;
}
}
int QueueEmpty(LinkQueue Q) { //是否为空,为空返回,非空返回0
if (Q.front->next == NULL)
return 1;
else
return 0;
}
int QueueLength(LinkQueue Q) { //返回队列长度
QueuePtr p = Q.front->next;
if (p == NULL) {//刚初始化完的队列长度为0
return 0;
}
int count = 1;
while (p != Q.rear) {
count++;
p = p->next;
}
return count;
}
QElemType GetHead(LinkQueue Q) { //若队列非空,返回队列首的值
if (QueueEmpty(Q) != 1)
return Q.front->next->data;
else
printf("队列为空!\n");
}
void EnQueue(LinkQueue *Q, QElemType e) { //将e元素插入队列
QueuePtr node;
node = (QueuePtr)malloc(sizeof(QNode));
if (node != NULL) {
node->next = NULL;
node->data = e;
Q->rear->next = node;
Q->rear = node;
} else
printf("EnQueue error!\n");
}
QElemType DeQueue(LinkQueue *Q) { //删除队头元素,并返回其值
QElemType e;
QueuePtr q;
if (QueueEmpty(*Q) != 1) {
q = Q->front->next;
e = Q->front->next->data;
Q->front->next = q->next;
if (Q->rear == q)//队头等于队尾,防止尾指针丢失
Q->rear = Q->front;
free(q);
return e;
} else
printf("队列为空!\n");
}
void QueueTraverse(LinkQueue Q) { //从队列头到尾遍历队列元素并输出
QueuePtr p = Q.front->next;
// printf("从队列头到尾遍历队列元素并输出:\n");
if (QueueEmpty(Q) == 1) {
printf("队列为空!\n");
return;
}
while (p != NULL) {
if (p != Q.rear)
printf("%c ", p->data);
else
printf("%c\n", p->data);
p = p->next;
}
}
2.二叉树.h
(二叉树的创建和遍历函数)
#include "链队列.h"
BiNode *Creat(char *str, int *i, int len) { //树的创建
struct BiNode *bt = NULL;
char ch = str[(*i)++];
if (ch == '#' || *i >= len) {
bt = NULL;
} else {
bt = (struct BiNode *)malloc(sizeof(BiNode));
if (bt != NULL) {
bt->data = ch;
bt->Lchild = Creat(str, i, len); //这里的递归要赋值,这样才能建立不同域中的连接关系
bt->rchild = Creat(str, i, len);
}
}
return bt;//返回的一直是根结点
}
void visit(Datatype e) {
printf("%c ", e);
}
void PreOrder(BiNode *bt) { //树的前序遍历
if (bt == NULL) //递归调用的出口
return;
else {
visit(bt->data);//访问根节点bt的数据域
PreOrder(bt->Lchild);//前序递归遍历bt的左子树
PreOrder(bt->rchild);//前序递归遍历bt的右子树
}
}
void MidOrder(BiNode *bt) { //树的中序遍历
if (bt == NULL) //递归调用的出口
return;
else {
MidOrder(bt->Lchild);//前序递归遍历bt的左子树
visit(bt->data);//访问根节点bt的数据域
MidOrder(bt->rchild);//前序递归遍历bt的右子树
}
}
void PostOrder(BiNode *bt) { //树的后序遍历
if (bt == NULL) //递归调用的出口
return;
else {
PostOrder(bt->Lchild);//前序递归遍历bt的左子树
PostOrder(bt->rchild);//前序递归遍历bt的右子树
visit(bt->data);//访问根节点bt的数据域
}
}
void LeverOrder(BiNode *root, LinkQueue *Q) { //树的层序遍历
QElemType q;//q的数据结构类型是最上面二叉树链表的结构体
if (root == NULL)
return;
EnQueue(Q, *root); //根指针入队
while (QueueEmpty(*Q) != 1) { //当队列非空时
q = DeQueue(Q); //出队
visit(q.data);
if (q.Lchild != NULL)
EnQueue(Q, *q.Lchild); //左孩子入队
if (q.rchild != NULL)
EnQueue(Q, *q.rchild); //右孩子入队
}
}
void Bitreedel(BiNode *bt) { //删除结点
if (bt == NULL)
return;
else {
Bitreedel(bt->Lchild);//前序递归遍历bt的左子树
Bitreedel(bt->rchild);//前序递归遍历bt的右子树
printf("删除结点:%c ", bt->data); //访问根节点bt的数据域
}
}
3. 二叉树性质.h
(上面1~6所有功能的实现函数)
#include "二叉树.h"
#define LEN 50
typedef int ElemType;
typedef struct global {//在链表转换成数组时使用,用于优化输出
int index[LEN];
int k;
} global;
global trans;
void clearstruct(BiNode *bt, int n);
int Bitdepth(BiNode *bt) { //求二叉树的深度
int m, n;
if (bt == NULL)
return 0;
else {
m = Bitdepth(bt->Lchild); //递归计算左子树的深度
n = Bitdepth(bt->rchild); //递归计算右子树的深度
if (m > n)
return m + 1;
else
return n + 1;
}
}
int countleave(BiNode *bt) { //求叶子结点的个数
if (bt == NULL) //递归调用的出口
return 0;
else {
if (bt->Lchild == NULL && bt->rchild == NULL)
return 1;
else
return countleave(bt->Lchild) + countleave(bt->rchild);
}
}
int countnode(BiNode *bt) {//求结点的个数
if (bt == NULL) //递归调用的出口
return 0;
else
return countnode(bt->Lchild) + countnode(bt->rchild) + 1;
}
void TreetoArr(BiNode *bt, ElemType *arr, int len, int pos) { //链表转换成数组
if (bt == NULL || arr == NULL || len < 1) {
return;
}
arr[pos] = bt->data;
trans.index[trans.k++] = pos; //index数组里保存了所有赋过值的结点下标,用于更改null值
if (pos * 2 + 1 < len) {
TreetoArr(bt->Lchild, arr, len, pos * 2 + 1);
}
if (pos * 2 + 2 < len) {
TreetoArr(bt->rchild, arr, len, pos * 2 + 2);
}
}
BiNode *ArrtoTree(ElemType *arr, int n, int pos) { //数组转换成链表
if (arr != NULL && pos < n) {
struct BiNode *bt;
bt = (struct BiNode *)malloc(sizeof(BiNode));
bt->data = arr[pos];
bt->Lchild = ArrtoTree(arr, n, pos * 2 + 1);
bt->rchild = ArrtoTree(arr, n, pos * 2 + 2);
return bt;
}
return NULL;
}
int pow_1(int a, int b) {
int sum = 1;
for (int i = 0; i < b; i++) {
sum = sum * a;
}
return sum;
}
void displayarr(ElemType *arr, int len) {//输出链表转换后的数组
for (int i = 0; i < len; i++)
printf("%c ", arr[i]);
}
void fillarr(ElemType *arr, int len) {//优化输出,将数组中没有赋值的对应下标值赋成‘*’
int temp[len];
for (int i = 0; i < len; i++)
temp[i] = -1;
for (int i = 0; i < trans.k; i++) {
temp[trans.index[i]] = 0;
}
for (int i = 0; i < len; i++) {
if (temp[i] == -1)
arr[i] = '*';
}
}
int deleteNode1(BiNode *bt, ElemType x) {//这里的二叉树不是二叉有序树,1删除成功,0删除失败
/*删除条件是:
1.如果删除的结点是叶子结点,则删除该结点
2.如果删除的结点是非叶子结点,则删除该子树
*/
/*如果当前结点的左子结点不为空并且是要删除的结点就删除左结点*/
if (bt->Lchild != NULL && bt->Lchild->data == x) {
bt->Lchild = NULL;
return 1;
}
/*如果当前结点的右子结点不为空并且是要删除的结点就删除右结点*/
if (bt->rchild != NULL && bt->rchild->data == x) {
// clearstruct(bt, 2);
bt->rchild = NULL;
return 1;
}
/*如果当前结点的右子结点和右子结点都不是要删除的结点*/
if (bt->Lchild != NULL) {
//向左子树递归删除
int flag = deleteNode1(bt->Lchild, x);
if (flag == 1)
return 1;
}
if (bt->rchild != NULL) {
//上面没找的话,就向左子树递归删除
int flag = deleteNode1(bt->rchild, x);
if (flag == 1)
return 1;
}
return 0;
}
int deleteNode(BiNode *root, ElemType x) { //删除的判断,从而调用真正的删除函数
if (root != NULL) {
if (root->data == x) { //删除的结点是根结点
free(root);
return 1;//根结点比较特殊,在返回1时就不能调用输出二叉树的函数了,因为二叉树已经销毁了
printf("根结点删除成功!\n");
} else {
int isDelete = deleteNode1(root, x);
if (isDelete == 1)
printf("结点删除成功!\n");
else
printf("删除结点失败没有找到结点!\n");
return 2;//其他情况返回2
}
} else {
printf("二叉树为空!\n");
}
}
int isCompleteTree(BiNode *bt) { //判断二叉树是不是完全二叉树,是返回1,不是返回2
int depth = Bitdepth(bt);
int sum = countnode(bt);
if (pow_1(2, depth) - 1 == sum)
return 1;
else
return 0;
}
BiNode *copyTree(BiNode *bt) { //复制一个二叉树
struct BiNode *bt_1 = NULL;
if (bt == NULL) {
bt_1 = NULL;
} else {
bt_1 = (struct BiNode *)malloc(sizeof(BiNode));//为复制的二叉树创建结点
bt_1->data = bt->data;
bt_1->Lchild = copyTree(bt->Lchild); //递归复制左子树
bt_1->rchild = copyTree(bt->rchild); //递归复制右子树
}
return bt_1;
}
4.二叉树性质测试.c
(用于测试上1~6功能的实现)
#include "二叉树性质.h"
void traverseTree(BiNode *bt);
int main() {
printf("建立一个二叉树\n");
BiNode *bt;
int i = 0, len;
char str[50];
printf("输入一个字符串用于建立二叉树:");
scanf("%s", str);
len = strlen(str);
bt = Creat(str, &i, len);
printf("-----------------测试遍历操作-----------------\n");
printf("\n");
traverseTree(bt);
printf("\n");
printf("----------------测试二叉树的性质---------------\n");
printf("\n");
int depth = Bitdepth(bt);
printf("二叉树的深度:%d\n", Bitdepth(bt));
printf("二叉树的叶子结点个数:%d\n", countleave(bt));
printf("二叉树的结点个数:%d\n", countnode(bt));
printf("判断二叉树是不是完全二叉树:");
if (isCompleteTree(bt) == 1)
printf("是!\n");
else
printf("否!\n");
printf("\n");
printf("--------------测试复制一个二叉树---------------\n");
printf("\n");
BiNode *bt_1;
bt_1 = copyTree(bt);
printf("复制后的二叉树:\n");
traverseTree(bt_1);
printf("\n");
printf("--------二叉树链表储存和顺序储存的转换---------\n");
printf("\n");
printf("将链表二叉树转换成顺序储存并输出:");
int len1 = pow_1(2, depth) - 1; //所有结点的个数
ElemType arr[len1];
TreetoArr(bt, arr, len1, 0);
fillarr(arr, len1);
displayarr(arr, len1);
printf("\n");
printf("将顺序储存二叉树转换成链表并(前序)输出:");
BiNode *root;
root = ArrtoTree(arr, len1, 0);
PreOrder(root);//前序输出链表内容
printf("\n");
getchar();
printf("\n");
printf("-----------------测试删除结点------------------\n");
printf("\n");
printf("请输入想要删除的根节点的值:");
char del;
scanf("%c", &del);
int flag = deleteNode(bt, del);
if (flag != 1) { //如果删除的不是头结点的话
printf("删除后的二叉树(前序)输出:");
PreOrder(bt);
} else
printf("二叉树为空!");
}
void traverseTree(BiNode *bt) {//调用函数进行树的遍历
printf("测试树的前序遍历:");
PreOrder(bt);
printf("\n");
printf("测试树的中序遍历:");
MidOrder(bt);
printf("\n");
printf("测试树的后序遍历:");
PostOrder(bt);
printf("\n");
printf("测试树的层序遍历:");
LinkQueue Q;
InitQueue(&Q);
LeverOrder(bt, &Q);
printf("\n");
}
测试输出:
测试用二叉树:
初学小白,有错误欢迎指正喔!~