数据结构—二叉树

二叉树是一棵树,其每个节点的儿子数不能多于2.下图是一般的二叉树:

 二叉树的平均深度要比N(数据量)小得多,其平均深度是O(根号下N)。而对于二叉查找树来说,其平均深度为O(logN).而当二叉树退化成链表时(即每个父亲节点只有一个儿子),它的深度可以大到N-1.二叉查找树是有序的,其任意节点的左子树的节点都小于它,右子树的节点都大于它。

二叉树的性质:

① 一个二叉树的第k层最多有2^{k-1}个结点(k>=1)

② 一个高度为k的二叉树最多有2^{k}-1个结点(k>=1)

③ 对于任意的二叉树,度为零的结点的个数等于度为1的结点的个数+1,即n_{0}=n_{2}+1,证明如下,对于有n个结点的二叉树,有n=n_{0}+n_{1}+n_{2},其边(分支)的个数有n-1条(除根节点外,每个结点都有一条边连接其父节点),边的条数还等于2*n_{2}+n_{1},那么结合这几个关系就可以得出结果。

④ 对于有n个结点的二叉树,有n+1个空着的指针域,这也为线索二叉树提供了基础(使用这些空指针指向该节点的前驱或后继)。

⑤ 满二叉树:满二叉树是指,对于一个高度为k(K>=1)的二叉树,如果它结点的个数为2^{k}-1,那么这棵二叉树就是满二叉树。

⑥ 完全二叉树:是指如果一棵高为k的树中结点的编号(从1开始编号,按照层序遍历的办法)与高度为k的满二叉树的结点编号可以对应(从1开始编号,按照层序的办法,满二叉树多出来的结点不算在内),那么这棵树就是完全二叉树。也就是说,高度为k的完全二叉树,其除第k层外是一棵满二叉树,第k层的结点从左到右依次排列,不能有空缺。一棵有n个结点的完全二叉树的高度为\left \lfloor log_{2} n \right \rfloor+1\left \lceil log_{2}(n+1) \right \rceil。满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。如果从1开始编号,那么完全二叉树中的结点与其孩子结点有倍数上的关系,所以可以使用顺序表表示,例如,1结点的孩子是2和3,2结点的孩子是4和5等。

二叉树的遍历:

① 先序遍历:先处理根节点,再处理左子树,最后处理右子树

② 中序遍历:先处理左子树,再处理根节点,最后处理右子树

③ 后序遍历:先处理左子树,再处理右子树,最后处理根节点

二叉查找树的递归实现:

#include <stdio.h>
#include <stdlib.h>

typedef struct tree {
	int ele;//节点值
	struct tree* left;//左子树
	struct tree* right;//右子树
	int count;//记录重复元素的个数
}tree;

tree* FindMin(tree* t) {//找最小值,一直找到最左边的树叶即可
	if (t == NULL) {//平均时间复杂度和空间复杂度都为O(logN)
		printf("树为空树\n");
		return NULL;
	}
	if (t->left != NULL)
		return FindMin(t->left);
	else
		return t;
}

tree* FindMax(tree* t) {//和找最小值原理相同
	if (t == NULL) {
		printf("树为空树\n");
		return NULL;
	}
	if (t->right != NULL)
		return FindMax(t->right);
	else
		return t;
}

tree* insert(tree* t, int x) {//插入
	if (t == NULL) {//如果t为空,就建立一个节点
		t = (tree*)malloc(sizeof(tree));//时间空间复杂度为O(logN)
		t->ele = x;
		t->left = NULL;
		t->right = NULL;
		t->count = 1;
	}
	else {//t不为空,首先找到x的位置
		if (x > t->ele) {//x大于节点值,就插入在节点的右边
			t->right = insert(t->right, x);
		}
		else if (x < t->ele) {//x小于该节点值,就插入到节点的左边
			t->left = insert(t->left, x);
		}
		else {//等于节点值,就让count+1
			t->count++;
		}
	}
	return t;
}

tree* delete(tree* t, int x) {//删除要分为有两个孩子的删除和一个或零个孩子的删除
	if (t == NULL) {//删除的时间空间复杂度也为O(logN)
		printf("树为空树\n");
		return NULL;
	}
	if (x < t->ele) {//x小于节点值就向左找
		t->left = delete(t->left, x);
	}
	else if (x > t->ele) {//x大于节点值就向右找
		t->right = delete(t->right, x);
	}
	else {//找到x的位置
		if (t->left != NULL && t->right != NULL) {//x有两个孩子节点
			tree* tmp = FindMin(t->right);//首先找到右子树的最小值
			t->ele = tmp->ele;//将最小值节点移到x的位置,实际上只需要赋值即可
			t->right = delete(t->right, tmp->ele);//然后删除该最小值节点
		}
		else {//当只有一个或没有孩子时
			tree* tmp = t;
			if (t->left == NULL) {//当它的左子树为空,就将右子树的指针返回
				t = t->right;
			}
			else if (t->right == NULL) {//右子树为空,就返回左子树的指针
				t = t->left;
			}
			free(tmp);
		}
	}
	return t;
}

tree* Find(tree* t,int x) {//查找时间空间复杂度为O(logN)
	if (t == NULL) {
		printf("树为空树\n");
		return;
	}
	if (x > t->ele) {//x大于节点值,就向右找
		return Find(t->right, x);
	}
	else if (x < t->ele) {//x小于节点值,就向左找
		return Find(t->left, x);
	}
	else
		return t;
}

//中序遍历打印
void Print(tree* t) {//时间空间复杂度为O(N)
	if (t == NULL) {
		printf("树为空树\n");
		return;
	}
	if (t->left != NULL) {
		Print(t->left);
	}
	printf("%d ", t->ele);
	if (t->right != NULL) {
		Print(t->right);
	}
}

void MakeEmpty(tree* t) {//时间空间复杂度为O(N)
	if (t != NULL) {
		MakeEmpty(t->left);
		MakeEmpty(t->right);
		free(t);
	}
}

int main() {
	tree* root = insert(NULL, 5);
	for (int i = 0; i <= 10; i++) {
		insert(root, i);
	}
	Print(root);
	printf("\n-------------------------\n");
	insert(root, 18);
	Print(root);
	printf("\n-------------------------\n");

	printf("%d\n-------------------------\n", (FindMin(root))->ele);
	printf("%d\n-------------------------\n", (FindMax(root))->ele);
	printf("%d\n-------------------------\n", (Find(root,5))->ele);

	for (int i = 0; i <= 4; i++) {
		delete(root, i);
		Print(root);
		printf("\n-------------------------\n");
	}
	//当二叉树只有左子树或是右子树时,对其根的删除就必须重新赋值
	root = delete(root, 5);
	//只有这样,才能正常打印出二叉树的元素,这是因为,当只有一边的子树时
	//删除时要将删除节点的孩子节点连接在父亲节点上,但是由于根没有父亲节点
	//所以当删除结束后,root会丢失二叉树的地址,就必须重新进行赋值才能正常使用

	Print(root);
	printf("\n-------------------------\n");

	return 0;
}

二叉查找树的循环实现: 

#include <stdio.h>
#include <stdlib.h>

typedef struct tree {
	int ele;
	struct tree* left;
	struct tree* right;
	int count;
}tree;

tree* FindMin(tree* t) {//时间复杂度为O(logN)
	if (t == NULL) {
		printf("树为空树\n");
		return NULL;
	}
	while (t->left != NULL) {//找到最左边的节点
		t = t->left;
	}
	return t;
}

tree* FindMax(tree* t) {//找最大值同理
	if (t == NULL) {
		printf("树为空树\n");
		return NULL;
	}
	while (t->right != NULL)
		t = t->right;
	return t;
}

tree* insert(tree* t, int x) {//插入的时间复杂度为O(logN)
	tree* before = t;
	if (t == NULL) {
		t = (tree*)malloc(sizeof(tree));
		t->ele = x;
		t->left = NULL;
		t->right = NULL;
		t->count = 1;
	}
	else {
		while (1) {
			if (x < t->ele) {
				before = t;
				t = t->left;
				if (t == NULL) {
					t = (tree*)malloc(sizeof(tree));
					t->ele = x;
					t->left = NULL;
					t->right = NULL;
					t->count = 1;
					before->left = t;
					break;
				}
			}
			else if (x > t->ele) {
				before = t;
				t = t->right;
				if (t == NULL) {
					t = (tree*)malloc(sizeof(tree));
					t->ele = x;
					t->left = NULL;
					t->right = NULL;
					t->count = 1;
					before->right = t;
					break;
				}
			}
			else {
				t->count++;
				break;
			}
		}
	}
	return t;
}

tree* delete(tree* t, int x) {//删除的时间复杂度为O(logN)
	tree* root = t;
	tree* before = t;
	if (t == NULL) {
		printf("树为空树\n");
		return NULL;
	}
	while (1) {
		if (x > t->ele) {
			before = t;
			t = t->right;
		}
		else if (x < t->left) {
			before = t;
			t = t->left;
		}
		else {
			if (t->left != NULL && t->right != NULL) {//有两个子树的情况
				tree* tmp = FindMin(t->right);//找到右子树的最小值
				t->ele = tmp->ele;//将最小值赋给待删除节点,然后删除最小值节点
				tree* minbefore = t->right;
				if (minbefore == tmp) {//如果最小值就是删除节点的右子树
					t->right = tmp->right;
					free(tmp);
				}
				else {//如果最小值不为右子树
					while (minbefore->left->ele != tmp->ele) {//找到最小值的父亲节点
						minbefore = minbefore->left;
					}
					minbefore->left = tmp->right;//删除最小值
					free(tmp);
				}
			}
			else {//有小于两个子树的情况
				if (t == root) {//如果要删除节点为根节点
					if (t->left == NULL) {//左子树为空,就将根节点变为右子树的第二个节点
						tree* p = t->right;
						free(t);
						return p;
					}
					else {//右子树为空同理
						tree* p = t->left;
						free(t);
						return p;
					}
				}
				if (t->left == NULL) {//节点的左子树为空,则要将节点的右子树连接到父亲节点上
					if (before->left == t) {
						before->left = t->right;
					}
					else
						before->right = t->right;
				}
				else if (t->right == NULL) {//节点的右子树为空同理
					if (before->left == t) {
						before->left = t->left;
					}
					else
						before->right = t->left;
				}
			}
		}
	}
	return t;
}

tree* Find(tree* t,int x) {
	tree* tmp = t;
	if (t == NULL) {
		printf("树为空树\n");
		return NULL;
	}
	while (tmp->ele != x) {
		if (x < tmp->ele) {
			tmp = tmp->left;
		}
		else
			tmp = tmp->right;
	}
	return tmp;
}

//中序遍历打印
void Print(tree* t) {//中序遍历的打印使用循环也过于麻烦
	//由于必须先打印左子树,所以需要先找到最左边的叶节点,
	//从这个叶节点开始向上打印,所以打印显然是一个递归
	if (t == NULL) {
		printf("树为空树\n");
		return;
	}
	if (t->left != NULL) {
		Print(t->left);
	}
	printf("%d ", t->ele);
	if (t->right != NULL) {
		Print(t->right);
	}
}

void MakeEmpty(tree* t) {//清除内存函数使用循环过于低效,因为必须先找到每个叶节点
	//当叶节点释放完毕后,再释放上一级的叶节点,但由于没有办法从叶节点直接找到它们的
	//父亲节点,所以总共需要树的深度-1次循环才能实现
	if (t != NULL) {
		MakeEmpty(t->left);
		MakeEmpty(t->right);
		free(t);
	}
}

int main() {
	tree* root = insert(NULL, 5);
	for (int i = 0; i <= 10; i++) {
		insert(root, i);
	}
	Print(root);
	printf("\n-------------------------\n");
	insert(root, 18);
	Print(root);
	printf("\n-------------------------\n");

	printf("%d\n-------------------------\n", (FindMin(root))->ele);
	printf("%d\n-------------------------\n", (FindMax(root))->ele);
	printf("%d\n-------------------------\n", (Find(root,5))->ele);

	for (int i = 0; i <= 4; i++) {
		delete(root, i);
		Print(root);
		printf("\n-------------------------\n");
	}
	//当二叉树只有左子树或是右子树时,对其根的删除就必须重新赋值
	root = delete(root, 5);
	//只有这样,才能正常打印出二叉树的元素,这是因为,当只有一边的子树时
	//删除时要将删除节点的孩子节点连接在父亲节点上,但是由于根没有父亲节点
	//所以当删除结束后,root会丢失二叉树的地址,就必须重新进行赋值才能正常使用

	Print(root);
	printf("\n-------------------------\n");

	return 0;
}

表达式树:

要将一个表达式转化成表达式树,首先要将其改写成后缀表达式(使用栈),接下来从左到右遍历后缀表达式,遇见数字就建立一个节点保存数字,并将节点的地址推入栈中,当遇见运算符时,建立一个节点保存符号,并从栈中弹出两个地址,分别放在符号节点的左子树和右子树。最后栈中留下的地址即是根节点的地址。

int main() {
	char str[] = "ab+cde+**";
	char* p = str+1;
	s* ps = CreatStack();//创建一个栈
	tree* tmp = insert(NULL, 'a');//建立一个节点
	push(ps, tmp);//将节点的地址推入栈中
	while (*p) {
		if (isalpha(*p)) {//当遇到数字时
			tmp = insert(NULL, *p);//建立一个节点并推入栈中
			push(ps, tmp);
		}
		else {
			tree* root = insert(NULL, *p);//为运算符时
			root->left = topandpop(ps);//建立一个节点,并且和栈中弹出的两个元素连接起来
			root->right = topandpop(ps);
			push(ps, root);
		}
		p++;
	}//循环结束后栈顶的元素就是根的地址
	tree* root = top(ps);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值