c语言二叉树

树的概念

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

有一个特殊的结点,称为根结点,根结点没有前驱结点

除根结点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

因此,树是递归定义(套娃,大问题分解成多个小问题,直到不用分解)的。任何一棵树都是有[根+N棵子树(N>=0)]构成,例如:下图A的子树就由B...,C...D...三棵子树构成。B的子树有E...,F。当只有根没有子树的时候就不会再分了。

注意:树形结构中,子树之间不能有交集,否则就不是树形结构  

树的相关概念 

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6

叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点

非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等结点为分支结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点

兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点

树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6

结点的层次:从根开始定义起,根为第1层(也可以为第0层,没有特别情况以1为准),根的子结点为第2层,以此类推;

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4

堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点

结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先

子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙

森林:由m(m>0)棵互不相交的树的集合称为森林;(多棵树)

二叉树的性质

1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1)个结点.

2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是 2^h-1.

3. 对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n1 ,则有 n0=n1 +1

4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1). 

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对 于序号为i的结点有:

     1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点

     2. 若2i+1,左孩子序号:2i+1,2i+1>=n否则无左孩子

     3. 若2i+2,右孩子序号:2i+2, 2i+2>=n否则无右孩子

树结构体的创建

左孩子右兄弟法

struct treenode
{
	int val;
	struct treenode* leftchild;
	struct treenode* rightbrother;
};

 这个方法就是,A右边没有兄弟,所以他的btother没有用到,然后将A的child指向下面左边的B,然后B的brother指针指向右边的兄弟C,将B的child指向下面最左边的D,然后D的brether指向右边的E,再将E的brother指向右边的兄弟F,总体逻辑就是这样。

无论一个父亲结点有多少个结点,child始终指向从左边开始的第一个孩子。

二叉树

树的度最大被限定为2。

特殊的二叉树:

1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是2^k - 1,则它就是满二叉树。假设这棵树有N个结点,那么它的层数为log(N+1);

2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树,也就是前k-1层都是满的,最后一层不满,且最后一层从左到右都是连续的,那它就是完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

二叉树的存储

1.用数组存储二叉树

 当用数组存储完全二叉树和满二叉树时,可以用下标表示父子关系

假设父亲在数组中的位置为:i,那么左孩子在数组中的下标为:2*i+1,右孩子在数组中的下标为2*i+2。

假设孩子在数组中的下标为j,那么父亲在数组中的下标为(j-1)/2。

非完全二叉树可以用数组来存储,但是不合适,会造成很多空间浪费,数组存储带有局限性。

2.链式存储二叉树 

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程 学到高阶数据结构如红黑树等会用到三叉链。

二叉树的遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉树中的结点进行相应的操作,并且每个结点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

前序:根,左子树,右子树

中序:左子树,根,右子树

后序:左子树,右子树,根

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层 上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

二叉树的实现

二叉树的创建

typedef struct treenode
{
	elem val;
	struct treenode* left;
	struct treenode* right;
}tree;

二叉树最大的度为2,所以我们在这里只需要创建两个指针就可以保存整棵二叉树。

二叉树结点的创建

tree* BuyNode(elem x)
{
	tree* new = (tree*)malloc(sizeof(tree));
	if (new == NULL)
	{
		perror("malloc");
		return 0;
	}
	new->val = x;
	new->left = new->right = NULL;
	return new;
}

在创建结点时,我们先用malloc创建一块空间,随后赋值,再将它的左右两个指针置空就行。 

 二叉树的遍历

1.递归前序遍历

void preorder(tree* node)//递归前序遍历
{
	if (node == NULL)
	{
		printf("N ");
		return;//return在递归中不会全部结束,会回到上一层中
	}
	printf("%d", node->val);
	preorder(node->left);
	preorder(node->right);
}

前序遍历图解 

 这里我们使用递归对二叉树实现前序遍历,这里的N表示二叉树的结点为空,二叉树的遍历同时也会遍历二叉树中的NULL结点。这里我们首先访问二叉树的根节点,然后访问二叉树的左子树,由于前序遍历是先根节点,所以在访问左子树是会先访问左子树的根,注意不要和中序,后序混淆。访问左子树到NULL结点为止,在递归函数中,return并不会退出函数,而是返回调用函数中。

2.递归中序遍历

void inorder(tree* node)//中序遍历
{
	if (node == NULL)
	{
		printf("N ");
		return;
	}
	inorder(node->left);
	printf("%d", node->val);
	inorder(node->right);
}

3.递归后序遍历 

void traorder(tree* ps)//后序遍历
{
	if (ps == NULL)
	{
		printf("N ");
		return;
	}
	traorder(ps->left);
	traorder(ps->right);
	printf("%d", ps->val);
}

中序遍历及后序遍历的整体思路和前序遍历差不多,只需要改变递归函数的位置即可。

 4.层序遍历

void levelorder(tree* ps)//层序遍历
{

	que duilie;
	ini(&duilie);
	if (ps)
	{
		push(&duilie, ps);
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		printf("%d", new->val);
		if (new->left)
		{
			push(&duilie, new->left);
		}

		if (new->right)
		{
			push(&duilie, new->right);
		}
	}
	qdel(&duilie);
}

层序遍历较为复杂,在实现层序遍历的过程中,我们需要使用队列的函数:

1.que 队列结构体,2.front 取头结点,3.push 插入结点,4.pop 删除,5.qdel 销毁 ,6.ini 初始化 

首先我们创建一个队列结构体,并将它初始化,如果树不为空,那就将根节点放进队列中,

随后我们使用while循环,循环结束的条件时队列为空,在循环中我们创建一个树来接收队头结点,然后将队头结点给删除,然后打印队头结点值,这里可能有同学会产生疑问,Pop的时候new为什么不会变为野指针,那是因为当你调用pop(&duilie)时,你只是从队列中移除了对这个节点的引用,但new指针仍然指向那个节点的内存地址。

随后将根节点的左右子树入队列,当有结点出队列时,它的左右子树同时也会入队列,保证了层序遍历能够打印二叉树全部内容。

最后将队列进行销毁避免内存泄漏。

二叉树结点个数

int size(tree* ps)//结点个数
{
	if (ps == NULL)
	{
		return 0;
	}
	return size(ps->left) + size(ps->right)+1;
}

二叉树叶子结点个数

int leafsize(tree* ps)//叶子结点个数
{
	if (ps==NULL)
	{
		return 0;
	}
	if (ps->left == NULL && ps->right == NULL)
	{
		return 1;
	}
	return leafsize(ps->left) + leafsize(ps->right);
}

叶子结点就是度为0的结点,所以存在左右子树为NULL的结点返回1即可

树的高度

int hight(tree* ps)//树的高度
{
	if (ps == NULL)
	{
		return 0;
	}
	int left = hight(ps->left);
	int right = hight(ps->right);
	return left > right ? left + 1 : right + 1;
}

这里我们使用left和right将左右两颗子树的高度存起来,然后使用三元操作符返回最大的高度加上根节点的高度。

第k层数据个数

int ksize(tree* ps,int k)//第k层数据个数
{
	if (ps == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return ksize(ps->left,k-1) + ksize(ps->right,k-1);
}

 查找数据

tree* find(tree* ps, elem k)//查找数据
{
	if (ps == NULL)
	{
		return NULL;
	}
	if (ps->val == k)
	{
		return ps;
	}
	tree* p1=find(ps->left, k);
	if (p1)
	{
		return p1;
	}
	tree* p2=find(ps->right, k);
	if (p2)
	{
		return p2;
	}
	return NULL;

}

这里创建p1和p2的目的是为了在找到数据时避免函数还在不断递归,return在递归中的作用是将数据返回上一次的调用之中。并不是结束运行 ,两个if用来判断有没有找到数据,在找到数据是就会不断的返回上一次的调用之中。

判断一个树是否为完全二叉树

bool BinaryTreeComplete(tree* ps)//判断是否为完全二叉树
{
	que duilie;
	ini(&duilie);
	if (ps)
	{
		push(&duilie, ps);
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		if (new == NULL)
		{
			break;
		}
		push(&duilie, new->left);
		push(&duilie, new->right);//不需要判断new的left和right是否为空,都录进去
	
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		if (new != NULL)
		{
			qdel(&duilie);
			return false;
		}
	}
	qdel(&duilie);
	return true;
}

判断完全二叉树时也要调用队列函数。可参考层序遍历的介绍。 

判断完全二叉树使用层序遍历即可,不过它遇到NULL时也入队列,当遇到空时,如果后面遇到非空就不是完全二叉树。

当第一个while循环结束时,有两种情况:

  1. 如果这是一个完全二叉树,那么所有的节点都已经按照层序遍历的顺序入队了。当遇到第一个NULL节点时,它表示的是最后一层的末端,即之后不应该有任何子节点。因此,队列中剩余的都应该是NULL节点。

  2. 如果这不是一个完全二叉树,那么在遇到第一个NULL节点之后,如果队列中还有非NULL的节点,这意味着树的结构在之前的某个点已经不连续了,即在某个非叶子节点缺少了子节点。

二叉树的销毁 

void treedel(tree* ps)//销毁
{
	if (ps == NULL)
	{
		return;
	}
	treedel(ps->left);
	treedel(ps->right);
	free(ps);
}

 只需递归销毁左右子树,最后再销毁根节点即可。

源码

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



typedef int elem;
typedef struct treenode
{
	elem val;
	struct treenode* left;
	struct treenode* right;
}tree;

tree* BuyNode(elem x)
{
	tree* new = (tree*)malloc(sizeof(tree));
	if (new == NULL)
	{
		perror("malloc");
		return 0;
	}
	new->val = x;
	new->left = new->right = NULL;
	return new;
}

tree* CreatBinaryTree()
{
	tree* node1 = BuyNode(1);
	tree* node2 = BuyNode(2);
	tree* node3 = BuyNode(3);
	tree* node4 = BuyNode(4);
	tree* node5 = BuyNode(5);
	tree* node6 = BuyNode(6);


	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
    

	return node1;
}

void preorder(tree* node)//递归前序遍历
{
	if (node == NULL)
	{
		printf("N ");
		return;//return在递归中不会全部结束,会回到上一层中
	}
	printf("%d", node->val);
	preorder(node->left);
	preorder(node->right);
}

void inorder(tree* node)//中序遍历
{
	if (node == NULL)
	{
		printf("N ");
		return;
	}
	inorder(node->left);
	printf("%d", node->val);
	inorder(node->right);
}

void traorder(tree* ps)//后序遍历
{
	if (ps == NULL)
	{
		printf("N ");
		return;
	}
	traorder(ps->left);
	traorder(ps->right);
	printf("%d", ps->val);
}

int size(tree* ps)//结点个数
{
	if (ps == NULL)
	{
		return 0;
	}
	return size(ps->left) + size(ps->right)+1;
}

int leafsize(tree* ps)//叶子结点个数
{
	if (ps==NULL)
	{
		return 0;
	}
	if (ps->left == NULL && ps->right == NULL)
	{
		return 1;
	}
	return leafsize(ps->left) + leafsize(ps->right);
}

int hight(tree* ps)//树的高度
{
	if (ps == NULL)
	{
		return 0;
	}
	int left = hight(ps->left);
	int right = hight(ps->right);
	return left > right ? left + 1 : right + 1;
}

int ksize(tree* ps,int k)//第k层数据个数
{
	if (ps == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return ksize(ps->left,k-1) + ksize(ps->right,k-1);
}

tree* find(tree* ps, elem k)//查找数据
{
	if (ps == NULL)
	{
		return NULL;
	}
	if (ps->val == k)
	{
		return ps;
	}
	tree* p1=find(ps->left, k);
	if (p1)
	{
		return p1;
	}
	tree* p2=find(ps->right, k);
	if (p2)
	{
		return p2;
	}
	return NULL;

}
//0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
typedef tree* qelem;

typedef struct node//使用单链表实现刚刚好,不需要尾删
{
	struct node* next;
	qelem val;
}nn;

typedef struct que
{
	nn* ph;//头节点
	nn* pt;//尾节点
	int size;
}que;


void ini(que* ps)//初始化
{
	assert(ps);
	ps->ph = ps->pt = NULL;
	ps->size = 0;
}


void qdel(que* ps)//删除
{
	assert(ps);
	nn* new = ps->ph;
	while (new)
	{

		nn* del = new->next;
		free(new);
		new = del;

	}
	ps->ph = ps->pt = NULL;
	ps->size = 0;
}

void push(que* ps, qelem x)//队尾插入
{
	assert(ps);
	nn* new = (nn*)malloc(sizeof(nn));
	if (new == NULL)
	{
		perror("push new");
		return;
	}
	new->next = NULL;
	new->val = x;
	if (ps->pt == NULL)//如果队列没有结点
	{
		ps->ph = ps->pt = new;
	}
	else//尾插
	{
		ps->pt->next = new;
		ps->pt = new;
	}
	ps->size++;

}

void pop(que* ps)//队头删除
{
	assert(ps);

	if (ps->ph->next == NULL)//只有一个链表
	{
		free(ps->ph);
		ps->ph = ps->pt = NULL;
	}
	else//多个结点
	{
		nn* new = ps->ph->next;
		free(ps->ph);
		ps->ph = new;
	}
	ps->size--;
}

qelem front(que* ps)//取头结点
{
	assert(ps);
	assert(ps->ph);
	return ps->ph->val;
}

qelem back(que* ps)//取尾结点
{
	assert(ps);
	assert(ps->pt);
	return ps->pt->val;
}

//int size(que* ps)//求数据个数
//{
//	assert(ps);
//	return ps->size;
//}

bool emp(que* ps)//判空
{
	assert(ps);
	return ps->size == 0;
}
//000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

void levelorder(tree* ps)//层序遍历
{

	que duilie;
	ini(&duilie);
	if (ps)
	{
		push(&duilie, ps);
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		printf("%d", new->val);
		if (new->left)
		{
			push(&duilie, new->left);
		}

		if (new->right)
		{
			push(&duilie, new->right);
		}
	}
	qdel(&duilie);
}

bool BinaryTreeComplete(tree* ps)//判断是否为完全二叉树
{
	que duilie;
	ini(&duilie);
	if (ps)
	{
		push(&duilie, ps);
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		if (new == NULL)
		{
			break;
		}
		push(&duilie, new->left);
		push(&duilie, new->right);//不需要判断new的left和right是否为空,都录进去
	
	}
	while (!emp(&duilie))
	{
		tree* new = front(&duilie);
		pop(&duilie);
		if (new != NULL)
		{
			qdel(&duilie);
			return false;
		}//此时就将
	}
	qdel(&duilie);
	return true;
}
void treedel(tree* ps)//销毁
{
	if (ps == NULL)
	{
		return;
	}
	treedel(ps->left);
	treedel(ps->right);
	free(ps);
}

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值