数据结构-树和二叉树(C语言)

树和二叉树

树(tree)是一个有限集合,在任意一颗非空的树里面
1 有且仅有一个被称为根的根节点(root)
2 当节点的个数n > 1的时候,也就是这个树里面除了这个
根节点以外还有别的节点存在,其余的节点都可以看成是一个小的
树,除了这个根节点以外,其他的这些节点实际上也是一颗树的根
节点,而这些树我们称为子树
也就是每一个节点实际上都是一颗子树的根节点

在这里插入图片描述
树的节点包含一个数据元素(树存在的意义实际上就是为了保持数据的)
然后还有指向其他子树的关系(指针去实现的),也就是指向其他子树的指针
一个树节点:1 数据域 2指针域

为什么有链表之后还会出现树这种结构
1 链表是不利于查找
因为我们只能保存这个链表的头或者尾
你如果要找到中间只能遍历

2 但是我们的现实生活中很多时候都是查找优先的
这个时候我们就可以将链表变成一个利于查找的东西不就可以了
这个时候我们就需要对链表进行改革!!!
让它好像二分法,树的这种结构就是为了实现
链表的二分法而出来的

在树里面每一个节点都会有一些分支
这些节点的分之数量称为这个节点的度(degree)
节点的度也可能是为0的,为0的节点我们称为终端节点/叶子节点(leaf)

树里面的有一个概念叫层次,根为第一层,层的孩子为第二层…,树的最大层次为这颗树的高度

二叉树:树的一种形态

节点的度不会超过2,节点下面的每一个节点
我们都称为孩子节点
二叉树至多有2个孩子
被称为左孩子与右孩子
左右孩子的顺序是不能颠倒的
一般:左孩子比右孩子要小
他们的大小区别为
左孩子 < 根 < 右孩子

二叉树的五种形态
1 空树
2 只有一个节点
3 只有一个左孩子
4 只有一个右孩子
5 儿女双全

二叉树的基本性质:

	1 二叉树的第i层上面最多有 2 ^ ( i - 1)个节点

	2 深度(高度)为k的二叉树最多有 2 ^ k - 1个节点

	3 对任何一颗二叉树来说,度为0的节点个数为n0
		度为2的节点个数n2
		n0 = n2 + 1
	
	满二叉树:深度为k的二叉树有 2 ^ k - 1个节点
		这颗树就被称为满二叉树
		
	完全二叉树:
		1 去除最后一层是为满二叉树
		2 最后一层所有的节点全部都尽左排
			左边不能添加任何一个节点了
	4 如果对n个节点的完全二叉树从上到下,从左到右
		从1开始进行编号,如果有一个节点的编号为i
		则:
			它的父节点的编号为 i / 2;
			它的左子节点(如果有)的编号就是  2 * i;
			它的右子节点(如果有)的编号就是  2 * i + 1;

	5 具有n个节点的完全二叉树的深度 k
		log2n向下取整 + 1
			2 ^ (k - 1) - 1 < n	<= 2 ^ k  - 1
		由于都是整数
			2 ^ (k - 1) <= n < 2 ^ k
			-> k = log2n向下取整 + 1

链式存储
链式结构有指针域,我只需要弄出两个指针就可以实现左右孩子的区分,因此更多的时候我们是采用链式结构去存储二叉树的。

代码实现:

扫描二维码关注公众号,回复: 14673615 查看本文章
typedef char MyTreeDataType;	
typedef struct TreeNode
{
    
    
	int n;
	MyTreeDataType data;
	struct TreeNode * lchild;
	struct TreeNode * rchild;

}TreeNode;	

创建树

//用data生产一个新的节点  单独的孤立的节点
TreeNode * CreateTreeNode(const MyTreeDataType data)
{
    
    
	TreeNode * pnew = malloc(sizeof(*pnew));
	//memset(pnew,0,sizeof(*pnew));
	
	pnew ->lchild = pnew ->rchild = NULL;
	pnew ->data = data;
	return pnew;
}

//将数据做成节点添加到我们的树里面去
TreeNode *AddTreeNodeForData(TreeNode *root,const MyTreeDataType data)
{
    
    
	
	root = AddTreeNodeForNode(root,CreateTreeNode(data));
	return root;
}


//直接将节点添加到我们的树里面去                    非递归形式添加
TreeNode *AddTreeNodeForNode(TreeNode *root,TreeNode * pnew)
{
    
    
	//这棵树是否存在是优先考虑的
	if(!root)
		return pnew;

	//找添加位置
	TreeNode * p = root;
	while(p)
	{
    
    
		if(pnew ->data < p ->data)//小的在左边
		{
    
    
			if(p ->lchild == NULL)
			{
    
    
				p ->lchild = pnew;
				return root;
			}
			p = p ->lchild;
		}
		else if(pnew ->data > p ->data)//大的在右边
		{
    
    
			if(p ->rchild == NULL)
			{
    
    
				p ->rchild = pnew;
				return root;
			}
			p = p ->rchild;
		}
		else //相等不能添加        我们可以在节点里面添加一个成员去表示这个节点出现了几次
		{
    
    
			free(pnew);
			return root;
		}
	}
	return root;
}

//数据来源  解析str字符串
//返回树的根节点
TreeNode * CreateTree(const char * str)
{
    
    
	TreeNode * root = NULL;
	while(*str)
	{
    
    
		root = AddTreeNodeForData(root,*str);
		str++;
	}
	return root;
}

销毁树
这个树里面的节点都是malloc出来,请不用的时候将其释放

//实现销毁树的函数
static void DestoryTreeTest(TreeNode *root)
{
    
    
	if(!root)
		return;
	//实际上就是一个后序遍历
	//左右子树全部释放完毕之后才能释放根节点
	//左右子树的情况跟我现在释放这棵树是一模一样的
	DestoryTreeShixian(root ->lchild);
	DestoryTreeShixian(root ->rchild);
	
	root ->lchild = root ->rchild = NULL;//孤立
	free(root);
}

//销毁这棵树
//传二级指针  调用完成会将调用它的那个根置空
void DestoryTree(TreeNode **root)
{
    
    
	printf("开始释放这棵树.....");
	if(!root)
	{
    
    
		printf("二级指针都传了一个空,释放了个鸡毛\n");
		return;
	}
	DestoryTreeTest(*root);

	*root = NULL;
	printf("释放结束!!!\n");
}

前序、中序、后序递归遍历树
添加节点是用一个遍历指针,根据大的找右边小的找左边的原则去添加节点的
而这个做法跟递归不谋而合
因此将我的循环改递归试一试
1 如果是小的就以相同的规则添加到左边去
2 如果是大的就以相同的规则添加到右边去
3 如果相同就停止添加
4 发现NULL的位置就是你添加的位置
依照小的在左边大的在右边这种规则去建立的二叉树我们叫 — 排序二叉树

static void PreOrderShixian(TreeNode *root)
{
    
    
	//如果你的root不存在         不用访问
	if(!root)
		return;
	//1 打印根节点
	printf("%c ",root ->data);

	//2 以相同的规则访问左子树
	PreOrderShixian(root ->lchild);

	//3 以相同的规则访问右子树
	PreOrderShixian(root ->rchild);
}

//递归先序
void PreOrder(TreeNode *root)
{
    
    
	printf("递归先序访问:");
	
	PreOrderShixian(root);
	
	printf("\n");
}

static void MidOrderShixian(TreeNode *root)
{
    
    
	//如果你的root不存在         不用访问
	if(!root)
		return;
	

	//1 以相同的规则访问左子树
	MidOrderShixian(root ->lchild);
	
	//2 打印根节点
	printf("%c ",root ->data);

	//3 以相同的规则访问右子树
	MidOrderShixian(root ->rchild);
}

//递归中序
void MidOrder(TreeNode *root)
{
    
    
	printf("递归中序访问:");
	
	MidOrderShixian(root);
	
	printf("\n");
}

static void PostOrderShixian(TreeNode *root)
{
    
    
	//如果你的root不存在         不用访问
	if(!root)
		return;

	//1 以相同的规则访问左子树
	PreOrderShixian(root ->lchild);

	//2 以相同的规则访问右子树
	PreOrderShixian(root ->rchild);
	
	//3 打印根节点
	printf("%c ",root ->data);
}

//递归后序
void PreOrder(TreeNode *root)
{
    
    
	printf("递归后序访问:");
	
	PostOrderShixian(root);
	
	printf("\n");
}

猜你喜欢

转载自blog.csdn.net/weixin_46836491/article/details/125945306