二叉树

一、二叉树概念。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值