二叉树的基本性质(链表和顺序存储的转换,结点个数,叶子结点个数,复制,删除)

下面要实现的二叉树操作有:

(先逐个说明,最后有完整代码,都由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,就继续递归)

代码实现:

扫描二维码关注公众号,回复: 15354079 查看本文章

(这里自己写了求幂函数,因为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");
}

测试输出:

测试用二叉树:

 初学小白,有错误欢迎指正喔!~

猜你喜欢

转载自blog.csdn.net/m0_63223213/article/details/125895091