一、二叉树概念。
1、线性结构与非线性结构。 线性结构:数组、链表、栈、队列。 非线性结构: 二叉树。
2、什么是二叉树? 二叉树是一种特殊的储存结构,是一种非线性的储存结构。所谓一棵树,都是有一个根节点,这个根节点也是可以用来存储数据的,二叉树结构就是把小于根节点的数据储存到根节点的左边,把大于根节点的数据储存到根节点的右边,这样就实现了检索数据时,例如,根节点是20,根左边是18,根右边是23,当我们需要寻找一个数据为23时,就不需要去根节点的左边寻找了,只需要去根节点的右边寻找即可,所谓二叉树搜索,就是不需要比较每一个节点,都可以快速找到目标,其效率要比链表高。
4、介绍二叉树基本概念。 1)双亲与孩子:A就是B/C/D的双亲、B/C/D就是A的孩子。 2)兄弟:拥有共同的双亲的两个节点称之为兄弟 B和C是兄弟 G和H是兄弟。 3)度: 形容一个节点孩子的个数 A的度是3,C的度是2 F的度是1。 4)层:A的层是1 B/C/D的层是2
5、二叉树种类。 1)只有根节点的二叉树。 根是一棵树的基本,哪怕只有一个根节点,也可以形成一棵树的。
2)普通二叉树。 树上面任意的一个节点的度都是小于/等于2(也就是说任意一个节点的孩子不得超过2个,可以0个,可以1个,可以2个,但是不可以3个或者更多)
3)满二叉树。 树上面的任意一个节点的度都是等于2,也就是说,假设有一颗树,有N层,其树总节点个数达到2的N次方-1个时候,那么这棵树就是满二叉树。
4)非二叉树。 只要树上有任意一个节点,其度大于2,那么这棵树就是非二叉树。
5、左节点与右节点。 一颗没有任何储存规则的二叉树是没有任何意义的,像上述所说规则一样,把小于根节点的数据存储到左边,把大于根节点的数据放在右边,那么如果整棵树都遵循以上原则,那么这棵树就是有存储规律的数,那么放在某一个节点的左边节点称之为左节点,也叫左孩子,右边同理。
二、二叉树模型。 每一个节点都需要储存数据,所以有数据域。 每一个节点都需要指向左边孩子,所以需要一个指向左孩子的指针。 每一个节点都需要指向右边孩子,所以需要一个指向右孩子的指针。
struct node{
int data; //数据域
struct node *lchild; //指向左孩子的指针
struct node *rchild; //指向右孩子的指针
};
三、二叉树增删改查
1、初始化根节点。
struct node *init_new_node(struct node *root, int num)
{
//1. 为新节点申请空间。
root = malloc(sizeof(struct node));
if (root == NULL)
printf("malloc new node error!\n");
//2. 为新节点赋值。
root->data = num;
root->lchild = NULL;
root->rchild = NULL;
return root;
}
2、 插入数据。
struct node *insert_node(struct node *new, struct node *root)
{
//1. 先判断树是不是为空。
if (root == NULL)
{
return new; //如果这棵树连根都没有,那么这个新节点就作为根。
}
//2. 树不为空,则寻找合适的位置正常插入数据。
if (new->data < root->data) //如果新节点比根节点要小
{
root->lchild = insert_node(new, root->lchild); //那么让新节点与我的左孩子进行比较
}
else if (new->data > root->data) //如果新节点比根节点要大
{
root->rchild = insert_node(new, root->rchild); //那么让新节点与我的右孩子进行比较
}
else
{
printf("%d node already exists!\n", new->data);
}
return root;
}
3、在二叉树中搜索节点。
struct node *find_node(struct node *root, int num)
{
if (root == NULL) //找到底都没有找到你
{
return NULL; //那么就返回NULL
}
if (num < root->data) //如果这个数字比root小,则去这个root的左边寻找。
{
return find_node(root->lchild, num);
}
else if (num > root->data) //如果这个数字比root大,则去这个root的右边寻找。
{
return find_node(root->rchild, num);
}
else
{ //如果这个数字等于root,则这个root就是我们想找的节点。
return root;
}
}
4、删除节点。 1)如果需要删除的节点有左子树(不管有没有右子树),其方法是把左子树中最大值替换掉该节点。
第一步:通过递归寻找需要删除的节点。 第二步:找到这个删除的节点的左子树的最大值。 第三步:将这个最大值替换掉需要删除的节点。 第四步:通过调用删除函数,递归删除这个最大值。(不能直接删,需要递归删,因为这个点虽然肯定没有右子树,但是有可能有左子树,如果有右,该点就不是最大的)
2)如果需要删除的节点只有右子树,其方法是把右子树中最小值替换掉该节点。
第一步:通过递归寻找需要删除的节点。 第二步:找到这个删除的节点的右子树的最小值。 第三步:将这个最小值替换掉需要删除的节点。 第四步:通过调用删除函数,递归删除这个最小值。(不能直接删,需要递归删,因为这个点虽然肯定没有左子树,但是有可能有右子树,如果有左,该点就不是最小的)
3)如果需要删除的节点是一个度为0的节点,则直接删除即可。
第一步:通过递归寻找需要删除的节点。 第二步:直接调用free()释放该节点的空间。
struct node *delete_node(struct node *root, int num)
{
//1. 如果找到底,都没有找到你
if (root == NULL)
{
return NULL;
}
//2. 寻找需要删除的节点。
if (num < root->data)
{
root->lchild = delete_node(root->lchild, num);
}
else if (num > root->data)
{
root->rchild = delete_node(root->rchild, num);
}
else
{ //找到需要删除的节点了,这个点就是root
//3. 判断需要删除的节点有没有左子树。
struct node *tmp = NULL;
if (root->lchild != NULL) //不为NULL,代表有左子树
{
//4. 寻找该节点左子树中最大值
for (tmp = root->lchild; tmp->rchild != NULL; tmp = tmp->rchild)
;
//从循环中出来时,tmp就是左子树中最大值
//5. 将这个最大值替换掉root。
root->data = tmp->data;
//6. 递归删除tmp
root->lchild = delete_node(root->lchild, tmp->data);
}
else if (root->rchild != NULL) //不为NULL,代表有右子树
{
//4. 寻找该节点右子树中最小值
for (tmp = root->rchild; tmp->lchild != NULL; tmp = tmp->lchild)
;
//从循环中出来时,tmp就是右子树中最小值
//5. 将这个最小值替换掉root。
root->data = tmp->data;
//6. 递归删除tmp
root->rchild = delete_node(root->rchild, tmp->data);
}
else
{ //这个节点又没有左,又没有右。
free(root);
return NULL;
}
}
return root;
}
5、遍历二叉树。 1)先序遍历。 顺序: 根 -> 左 -> 右
void show_node_first(struct node *root)
{
if (root == NULL)
return;
printf("%d\n", root->data); //根节点
show_node_first(root->lchild); //左
show_node_first(root->rchild); //右
}
2)中序遍历。 顺序: 左 -> 根 -> 右
void show_node_middle(struct node *root)
{
if (root == NULL)
return;
show_node_middle(root->lchild);
printf("%d\n", root->data);
show_node_middle(root->rchild);
}
3)后序遍历。 顺序: 左 -> 右 -> 根
void show_node_last(struct node *root)
{
if (root == NULL)
return;
show_node_last(root->lchild);
show_node_last(root->rchild);
printf("%d\n", root->data);
}
4)按层遍历。
设计步骤如下:
1>. 初始化一条空队。
2>. 将根节点入队。
3>. 出队,如果出队失败,则结束程序,如果出队成功,则打印该节点。
4>. 找到刚刚出队的那个节点。
5>. 判断刚刚出队的那个节点有没有左孩子,如果有,则将左孩子入队。
6>. 判断刚刚出队的那个节点有没有右孩子,如果有,则将右孩子入队。
7>. 重复第3步。
四、代码所有功能总汇
#include <stdio.h>
#include <stdlib.h>
//设计节点
struct node{ //二叉树节点
int data;
struct node *lchild;
struct node *rchild;
};
//设计队列的节点结构体
struct q_node{
int data;
struct q_node *next;
};
//设计队列的管理结构体
struct queue{
struct q_node *head;
struct q_node *tail;
int size;
};
struct node *init_new_node(struct node *root,int num)
{
//1. 为新节点申请空间。
root = malloc(sizeof(struct node));
if(root == NULL)
printf("malloc new node error!\n");
//2. 为新节点赋值。
root->data = num;
root->lchild = NULL;
root->rchild = NULL;
return root;
}
struct node *insert_node(struct node *new,struct node *root)
{
//1. 先判断树是不是为空。
if(root == NULL)
{
return new; //如果这棵树连根都没有,那么这个新节点就作为根。
}
//2. 树不为空,则寻找合适的位置正常插入数据。
if(new->data < root->data) //如果新节点比根节点要小
{
root->lchild = insert_node(new,root->lchild); //那么让新节点与我的左孩子进行比较
}
else if(new->data > root->data) //如果新节点比根节点要大
{
root->rchild = insert_node(new,root->rchild); //那么让新节点与我的右孩子进行比较
}
else{
printf("%d node already exists!\n",new->data);
}
return root;
}
struct node *find_node(struct node *root,int num)
{
if(root == NULL) //找到底都没有找到你
{
return NULL; //那么就返回NULL
}
if(num < root->data) //如果这个数字比root小,则去这个root的左边寻找。
{
return find_node(root->lchild,num);
}
else if(num > root->data) //如果这个数字比root大,则去这个root的右边寻找。
{
return find_node(root->rchild,num);
}
else{ //如果这个数字等于root,则这个root就是我们想找的节点。
return root;
}
}
struct node *delete_node(struct node *root,int num)
{
//1. 如果找到底,都没有找到你
if(root == NULL)
{
return NULL;
}
//2. 寻找需要删除的节点。
if(num < root->data)
{
root->lchild = delete_node(root->lchild,num);
}
else if(num > root->data)
{
root->rchild = delete_node(root->rchild,num);
}
else{ //找到需要删除的节点了,这个点就是root
//3. 判断需要删除的节点有没有左子树。
struct node *tmp = NULL;
if(root->lchild != NULL) //不为NULL,代表有左子树
{
//4. 寻找该节点左子树中最大值
for(tmp=root->lchild;tmp->rchild!=NULL;tmp=tmp->rchild);
//从循环中出来时,tmp就是左子树中最大值
//5. 将这个最大值替换掉root。
root->data = tmp->data;
//6. 递归删除tmp
root->lchild = delete_node(root->lchild,tmp->data);
}
else if(root->rchild != NULL) //不为NULL,代表有右子树
{
//4. 寻找该节点右子树中最小值
for(tmp=root->rchild;tmp->lchild!=NULL;tmp=tmp->lchild);
//从循环中出来时,tmp就是右子树中最小值
//5. 将这个最小值替换掉root。
root->data = tmp->data;
//6. 递归删除tmp
root->rchild = delete_node(root->rchild,tmp->data);
}
else{ //这个节点又没有左,又没有右。
free(root);
return NULL;
}
}
return root;
}
void show_node_first(struct node *root)
{
if(root == NULL)
return;
printf("%d\n",root->data); //根节点
show_node_first(root->lchild); //左
show_node_first(root->rchild); //右
}
void show_node_middle(struct node *root)
{
if(root == NULL)
return;
show_node_middle(root->lchild);
printf("%d\n",root->data);
show_node_middle(root->rchild);
}
void show_node_last(struct node *root)
{
if(root == NULL)
return;
show_node_last(root->lchild);
show_node_last(root->rchild);
printf("%d\n",root->data);
}
struct queue *init_queue()
{
//1. 为队列管理结构体申请空间。
struct queue *q = malloc(sizeof(struct queue));
//2. 为管理结构体赋值。
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
int in_queue(struct queue *q,int num)
{
//1. 为新节点申请空间。
struct q_node *new = malloc(sizeof(struct q_node));
if(new == NULL)
printf("malloc q_node error!\n");
//2. 赋值
new->data = num;
new->next = NULL; //新排队的用户后面100%是没有人的。
if(q->size == 0) //如果插入的节点是第一个节点,则需要特殊处理。
{
q->head = new; //如果队列中只有你一个人,那么队头队尾都是你
q->tail = new;
}
else{
q->tail->next = new;
q->tail = new;
}
q->size++;
return 0;
}
int out_queue(struct queue *q,int *a)
{
if(q->size == 0)
return -1;
struct q_node *tmp = q->head; //tmp就是待会出队的那个人。
if(q->size == 1) //整个队列只有一个节点。
{
q->head = NULL;
q->tail = NULL;
}
else{
q->head = q->head->next;
}
*a = tmp->data;
free(tmp);
q->size--;
return 0;
}
int show_node_level(struct node *root)
{
if(root == NULL)
return -1;
//1. 初始化一条空队。
struct queue *q = NULL;
q = init_queue();
//2. 将根节点入队。
in_queue(q,root->data);
//3. 不断出队.
int a;
struct node *tmp = NULL;
while(1)
{
//3.1 如果出队失败,则结束程序
if(out_queue(q,&a) == -1) //出队失败
break;
//3.2 如果出队成功,则打印该节点。
printf("%d\n",a);
//4. 在二叉树中找到刚刚出队的那个节点。
tmp = find_node(root,a); //现在tmp就是指向刚刚出队的那个节点。
//5. 判断tmp有没有左孩子,如果有,则将左孩子入队。
if(tmp->lchild != NULL) //代表有左孩子。
in_queue(q,tmp->lchild->data);
//6. 判断tmp有没有右孩子,如果有,则将右孩子入队。
if(tmp->rchild != NULL) //代表有右孩子。
in_queue(q,tmp->rchild->data);
}
free(q);
return 0;
}
int main(int argc,char *argv[])
{
//1. 初始化根节点。
struct node *root = NULL;
root = init_new_node(root,20);
//2. 插入节点到二叉树中。
struct node *new = NULL;
new = init_new_node(new,10);
insert_node(new,root);
new = init_new_node(new,30);
insert_node(new,root);
new = init_new_node(new,5);
insert_node(new,root);
new = init_new_node(new,11);
insert_node(new,root);
new = init_new_node(new,40);
insert_node(new,root);
new = init_new_node(new,3);
insert_node(new,root);
new = init_new_node(new,7);
insert_node(new,root);
new = init_new_node(new,35);
insert_node(new,root);
new = init_new_node(new,45);
insert_node(new,root);
new = init_new_node(new,6);
insert_node(new,root);
new = init_new_node(new,33);
insert_node(new,root);
new = init_new_node(new,34);
insert_node(new,root);
//3. 在二叉树中搜索节点。
struct node *tmp = find_node(root,11);
if(tmp != NULL)
{
printf("已经寻找到目标节点:%d\n",tmp->data);
}
else{
printf("二叉树中没有这个节点!\n");
}
4. 删除二叉树的节点。
printf("root->lchild->data:%d\n",root->lchild->data);
printf("root->lchild->lchild->rchild->data:%d\n",root->lchild->lchild->rchild->data);
delete_node(root,10);
printf("root->lchild->data:%d\n",root->lchild->data);
printf("root->lchild->lchild->rchild->data:%d\n",root->lchild->lchild->rchild->data);
5. 先序遍历。 根 -> 左 -> 右
show_node_first(root);
6. 中序遍历。 左 -> 根 -> 右
show_node_middle(root);
7. 后序遍历。
show_node_last(root);
8. 按层遍历。
show_node_level(root);
return 0;
}