二叉树是一棵树,其每个节点的儿子数不能多于2.下图是一般的二叉树:
二叉树的平均深度要比N(数据量)小得多,其平均深度是O(根号下N)。而对于二叉查找树来说,其平均深度为O(logN).而当二叉树退化成链表时(即每个父亲节点只有一个儿子),它的深度可以大到N-1.二叉查找树是有序的,其任意节点的左子树的节点都小于它,右子树的节点都大于它。
二叉树的性质:
① 一个二叉树的第k层最多有个结点(k>=1)
② 一个高度为k的二叉树最多有个结点(k>=1)
③ 对于任意的二叉树,度为零的结点的个数等于度为1的结点的个数+1,即,证明如下,对于有n个结点的二叉树,有,其边(分支)的个数有条(除根节点外,每个结点都有一条边连接其父节点),边的条数还等于,那么结合这几个关系就可以得出结果。
④ 对于有n个结点的二叉树,有个空着的指针域,这也为线索二叉树提供了基础(使用这些空指针指向该节点的前驱或后继)。
⑤ 满二叉树:满二叉树是指,对于一个高度为k(K>=1)的二叉树,如果它结点的个数为,那么这棵二叉树就是满二叉树。
⑥ 完全二叉树:是指如果一棵高为k的树中结点的编号(从1开始编号,按照层序遍历的办法)与高度为k的满二叉树的结点编号可以对应(从1开始编号,按照层序的办法,满二叉树多出来的结点不算在内),那么这棵树就是完全二叉树。也就是说,高度为k的完全二叉树,其除第k层外是一棵满二叉树,第k层的结点从左到右依次排列,不能有空缺。一棵有n个结点的完全二叉树的高度为或。满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。如果从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;
}