一、二叉树。
1、为什么要学习二叉树?
前面我们都已经学习过链表,大家都知道,如果我们需要检索出来一个数据,有可能检索一次,就可以找到数据,有可能检索到最后一个节点才能找到数据,这就是链表的特点。也就是说,寻找一条长度为N个节点的链表,搜索次数处于1~N-1(头节点无效),二叉树的出现,就是为了减少检索数据时次数。
2、什么是二叉树?
二叉树也是一种存储结构,也就是说跟链表一样,都是用来存储数据的,但是链表是线性规则结构,而二叉树是非线性的存储结构。二叉树有一个根节点作为起点,这个根节点也是用来存放数据。
二叉树结构就是把小于根节点的数据存储到根节点的左边,把大于根节点的数据存储到根节点的右边在,这样就实现了检索时,不需要更多的次数。
例如:根是节点20,根的左边是18,根的右边是22,当我们寻找节点数据为22时,就不需要考虑去跟的左边寻找,只需要去根节点的右边寻找即可,这样每次对比寻找,都可以节点一半节点不需要对比。
3、介绍二叉树概念。
1)双亲与孩子。 A就是B与C的双亲, B与C就是A的孩子。
2)兄弟:拥有共同的双亲的两个节点称之为兄弟 B与C是兄弟,D与E也是兄弟
3)度:形容一个节点孩子的个数 A的度是2 D是度是1 H的度是0
4)层: A的层是1 B/C的层是2
二、二叉树的增删改查。
1、设计节点。
经过分析,每一个节点存储数据,所以有数据域,还要有两个指针,分别指向左孩子与右孩子。
struct node{
int data;
struct node *lchild;
struct node *rchild;
};
2、初始化一个新节点。
struct node *init_new_node(int num)
{
//1. 为新节点申请空间
struct node *root = malloc(sizeof(struct node));
if(root == NULL)
printf("malloc root error!\n");
//2. 为新节点赋值
root->data = num;
root->lchild = NULL;
root->rchild = NULL;
return root;
}
3、插入节点。
struct node *insert_node(struct node *new,struct node *root)
{
//1. 判断二叉树中有没有根节点。如果没有,则new作为根节点。
if(root == NULL)
{
return new; //那么新节点就作为根节点
}
//2. 如果数根节点,则正常插入节点。
if(new->data > root->data)
{
root->rchild = insert_node(new,root->rchild);
}
else if(new->data < root->data)
{
root->lchild = insert_node(new,root->lchild);
}
else{
printf("%d node already exists!\n",new->data);
}
return root;
}
4、搜索节点。
struct node *find_node(struct node *root,int num)
{
if(root == NULL) //找到底都没有找到你啊
{
return 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{
return root;
}
}
5、遍历二叉树。
1)先序遍历。
顺序:根 --> 左 --> 右
void show_node_first(struct node *root)
{
if(root == NULL)
return;
//1. 打印根节点
printf("%d\n",root->data); //根
//2. 左
show_node_first(root->lchild);
//3. 右
show_node_first(root->rchild);
return;
}
2)中序遍历。
顺序: 左 --> 根 --> 右
void show_node_middle(struct node *root)
{
if(root == NULL)
return;
//1. 左
show_node_middle(root->lchild);
//2. 打印根节点
printf("%d\n",root->data);
//3. 右
show_node_middle(root->rchild);
return;
}
3)后序遍历。
顺序: 左 --> 右 --> 根
void show_node_last(struct node *root)
{
if(root == NULL)
return;
//1. 左
show_node_last(root->lchild);
//2. 右
show_node_last(root->rchild);
//3. 打印根节点
printf("%d\n",root->data);
return;
}
4)按层遍历。
步骤如下:
1、初始化一条空队列。
2、将根节点入队。
3、出队,如果出队失败,则结束程序,如果成功,则打印该节点。
4、在二叉树中找到出队的那个节点。
5、判断刚刚出队的那个节点有没有左孩子,如果有,则将左孩子入队。
6、判断刚刚出队的那个节点有没有右孩子,如果有,则将右孩子入队。
7、重复第3步。
struct q_node{
int data; //节点的数据域
struct q_node *next; //节点的指针域
};
struct queue{
struct q_node *head; //指向队头
struct q_node *tail; //指向队尾
int size; //统计当前队列中节点的个数
};
struct queue *init_queue()
{
//1. 为管理结构体申请空间
struct queue *q = malloc(sizeof(struct queue));
if(q == NULL)
printf("malloc q error!\n");
//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 new error!\n");
//2. 为新节点赋值
new->data = num;
new->next = NULL;
//3. 分情况讨论
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 *ret)
{
//1. 如果是空队,就不用再出队了
if(q->size == 0)
return -1;
//2. 不为空,正常出队
struct q_node *tmp = q->head; //这个tmp指向的节点就是待会要出队的节点
//3. 分情况讨论
if(q->size == 1) //如果整个队列中只有一个节点
{
q->head = NULL;
q->tail = NULL;
}
else{
q->head = q->head->next;
}
//4. 保存以下将要释放的节点的数据
*ret = tmp->data;
//5. 释放tmp
free(tmp);
//6. 节点个数-1
q->size--;
return 0;
}
int show_node_level(struct node *root)
{
//0. 判断二叉树是否为NULL
if(root == NULL)
return -1;
//1、初始化一条空队列。
struct queue *q = NULL;
q = init_queue();
//2、将根节点入队。
in_queue(q,root->data);
int a;
struct node *tmp = NULL;
while(1)
{
//3、出队,如果出队失败,则结束程序,如果成功,则打印该节点。
if(out_queue(q,&a) == -1) //a就是出队的元素
{
break; //跳出循环
}
printf("%d\n",a);
//4、在二叉树中找到出队的那个节点。
tmp = find_node(root,a);
//5、判断刚刚出队的那个节点有没有左孩子,如果有,则将左孩子入队。
if(tmp->lchild != NULL)
in_queue(q,tmp->lchild->data);
//6、判断刚刚出队的那个节点有没有右孩子,如果有,则将右孩子入队。
if(tmp->rchild != NULL)
in_queue(q,tmp->rchild->data);
//7、重复第3步。
}
free(q);
return 0;
}
6、删除节点。
1)如果需要删除的节点有左子树(不管有没有右子树),其方法是将左子树中最大值替换掉该节点。
第一步:通过递归寻找到需要删除的节点。
第二步:找到删除的那个节点的左子树的最大值。
第三步:将这个最大值替换需要删除的节点。
第四步:通过调用删除函数,递归地去删除这个最大值。(不能直接删除,需要递归删除,因为这个节点虽然肯定没有右子树(因为如果有右子树,该节点就不是最大的),但是有可能有左子树)
2)如果需要删除的节点只有右子树,其方法就是把右子树中最小值替换该节点。
第一步:通过递归寻找到需要删除的节点。
第二步:找到删除的那个节点的右子树的最小值。
第三步:将这个最小值替换需要删除的节点。
第四步:通过调用删除函数,递归地去删除这个最小值。(不能直接删除,需要递归删除,因为这个节点虽然肯定没有左子树(因为如果有左子树,该节点就不是最小的),但是有可能有右子树)
3)如果需要删除的节点的度为0,则直接删除。
第一步:通过递归寻找到需要删除的节点。
第二步:直接调用free()释放该节点的空间。
------------------------------------------------------
struct node *delete_node(struct node *root,int num)
{
//1. 找到底都没有找到该节点,则直接返回NULL。
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. 将tmp指向数据赋值给需要删除的节点的数据域。
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. 将tmp指向的数据赋值给需要删除的节点的数据域
root->data = tmp->data;
//6. 递归删除这个tmp
root->rchild = delete_node(root->rchild,tmp->data);
}
else{
free(root);
return NULL; //最后的节点释放掉之后,一定要返回一个NULL给指向这个最后的节点的左/右指针。
}
}
return root;
}
----------------------------------------------------