树的概念与功能实现

目录

树的概念与结构:

树的概念:

树的结构示例图:

二叉树的性质:

二叉树的基本功能实现:

手动构建一棵二叉树:

二叉树的遍历:

求叶节点的个数:

求树的高度:

返回第k层节点的个数:

查找值为n的节点:

判断该二叉树是否为完全二叉树:


树的概念与结构:

树的概念:

  • 树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
  • 根节点较为特殊,没有前驱节点。
  • 除根节点外,其余结点被分成X(X>0)个互不相交的集合T1、T2、……、Tn,其中每一个集合Ti(1<= i <= n)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱节点,可以有0个或多个后继节点。

  • 树是递归定义的、子树之间不能有交集。

树的结构示例图:

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的度为4。

  • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:E、F、G、I、G、K节点为叶节点。

  • 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点。

  • 子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的子节点。

  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为4。

  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推。

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

二叉树的性质:

二叉树是一个特殊的树,树的概念都能用于二叉树。

一棵二叉树是结点的一个有限集合,该集合:

1. 或者为空

2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

  1. 二叉树不存在度大于2的结点

  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

特殊的二叉树:

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

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

假设树有N个节点,高度为h:

第h层节点数为:2^(h-1)

最多有(满二叉树):2^h-1 = N

h = log(N+1)    (log以2为底)

完全二叉树高度为h,节点数范围为:(2^(h-1) , 2^h-1)

二叉树的基本功能实现:

手动构建一棵二叉树:

typedef int HTDateType;

typedef struct BinarTreeNode
{
	HTDateType x;
	struct BinarTreeNode* left;//记录左孩子
	struct BinarTreeNode* right;//记录右孩子
}TreeNode;

TreeNode* BuyTreeNode(int n)
{
	TreeNode* Node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(Node);
	Node->x = n;
	Node->left = NULL;
	Node->right = NULL;

	return Node;
}

TreeNode* CreateTree()
{
	TreeNode* Node1 = BuyTreeNode(1);
	TreeNode* Node2 = BuyTreeNode(2);
	TreeNode* Node3 = BuyTreeNode(3);
	TreeNode* Node4 = BuyTreeNode(4);
	TreeNode* Node5 = BuyTreeNode(5);
	TreeNode* Node6 = BuyTreeNode(6);
	TreeNode* Node7 = BuyTreeNode(7);

	Node1->left = Node2;
	Node1->right = Node4;
	Node2->left = Node3;
	Node4->left = Node5;
	Node4->right = Node6;
	Node5->left = Node7;

	return Node1;
}

其概念图如下:

二叉树的遍历:

  1. 前序、中序以及后序遍历(递归式):

前序遍历(Preorder Traversal):访问根结点的操作发生在遍历其左右子树之前。 即按照:根->左子树->右子树的顺序去遍历树,如上述的树,根为1、左为2、右为4。然后继续拆分为根为2、左为3、右为N(空NULL),按照根->左子树->右子树的顺序以此类推。

最后得出顺序为:1 2 3 N N N 4 5 7 N N N 6 N N

void PrevOrder(TreeNode* node)
{
	if (node == NULL)
	{
		printf("N ");//当节点为空时最好也打印出来,方便判别
		return;//当节点为空就不用继续往下了,没有左右子树可以直接返回了
	}

	printf("%d ", node->x);// 打印根节点的值
	PrevOrder(node->left);// 继续往下遍历左子树与右子树
	PrevOrder(node->right);
}

中序遍历(Inorder Traversal):访问根结点的操作发生在遍历其左右子树之中(间)。即按照:左子树 -> 根 -> 右子树的顺序进行遍历。

最后得出顺序为:N 3 N 2 N 1 N 7 N 5 N 4 N 6 N

void InOrder(TreeNode* node)
{
	if (node == NULL)
	{
		printf("N ");
		return;
	}

	// 按照左子树 根 右子树的顺序遍历
	InOrder(node->left);
	printf("%d ", node->x);
	InOrder(node->right);
}

后序遍历(Postorder Traversal):访问根结点的操作发生在遍历其左右子树之后。即按照:左子树 -> 右子树 -> 根的顺序进行遍历。

最后得出顺序为:N N 3 N 2 N N 7 N 5 N N 6 4 1

void PostOrder(TreeNode* node)
{
	if (node == NULL)
	{
		printf("N ");
		return;
	}

	// 按照左子树 右子树 根的顺序遍历
	PostOrder(node->left);
	PostOrder(node->right);
	printf("%d ", node->x);
}

2.层序遍历:

层序遍历就是一层一层的去遍历每个节点,在这里需要运用到一些前面学过的队列的一些知识点,可以直接搬队列的代码过来用,代码是一样的。

  • QueueInit :初始化队列
  • QueuePush :插入数据
  • QueueFront :返回队头的数据
  • QueuePop :删除数据
  • QueueDestroy :销毁队列
  • QueueEmpty :判空

        本质上是把每一层的节点都分为节点本身和节点的左右孩子,可以设计一个队列,将根节点插入队列中,然后根节点的左右孩子也依次入队,然后根节点的左孩子的左右孩子也依次入队,右孩子的左右孩子也同理,以此类推就能得到层序遍历。

void LevelOrder(TreeNode* node)
{
	Queue q;
	QueueInit(&q);

	// 如果节点不为空,则将节点插入到队列中
	if (node)
		QueuePush(&q, node);

	while (!QueueEmpty(&q))
	{
		// 取出队头数据
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		// 打印该节点
		printf("%d ", front->x);

		// 如果该节点的左右孩子不为空,则将该节点的左右孩子也插入队列中
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	QueueDestroy(&q);
}

求叶节点的个数:

int TreeLeafSize(TreeNode* node)
{
	// 节点本身为空直接返回0
	if (node == NULL)
	{
		return 0;
	}

	// 节点本身不为空且左右子节点为空则为叶节点
	if (node->left == NULL && node->right == NULL)
	{
		return 1;
	}

	// 既不为空也不是叶节点则为枝干,则向下查找
	return TreeLeafSize(node->left) 
		+ TreeLeafSize(node->right);
}

求树的高度:

int TreeHeight(TreeNode* node)
{
	if (node == NULL)
	{
		return 0;
	}
	// 取左右两树的最大值加一(要算上根节点)
	return fmax(TreeHeight(node->left), TreeHeight(node->right)) + 1;
}

返回第k层节点的个数:

int TreeLevelK(TreeNode* node, int k)
{
	assert(k > 0);
	// 当节点为空时返回0
	if (node == NULL)
	{
		return 0;
	}

	// 当前节点属于第k层的节点时返回1
	if (k == 1)
	{
		return 1;
	}

	// 不属于上述情况时往下查找
	return TreeLevelK(node->left, k - 1) + TreeLevelK(node->right, k - 1);
}

查找值为n的节点:

TreeNode* TreeFind(TreeNode* node, HTDateType n)
{
	// 如果节点为空,返回空
	if (node == NULL)
		return NULL;

	// 找到该值,返回该节点
	if (node->x == n)
		return node;

	// 创建ret1与ret2来接收递归调用的返回值
    // 如果找到了就返回ret1/ret2
	TreeNode* ret1 = TreeFind(node->left, n);
	if (ret1)
		return ret1;
	TreeNode* ret2 = TreeFind(node->right, n);
	if (ret2)
		return ret2;
    
    // 找不到就返回NULL
	return NULL;
}

判断该二叉树是否为完全二叉树:

        这里也需要运用到队列的一些知识,和上面层序遍历的思路的差不多。同样是先按照层的思路插入节点到队列中,然后依次往外取,如果遇到NULL后,后面还有数据的话那就不是完全二叉树,因为完全二叉树的前h-1层是满的,最后一层是连续的。

bool BinaryTreeComplete(TreeNode* node)
{
	Queue q;
	QueueInit(&q);
	if (node)
		QueuePush(&q, node);

	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		// 遇到空就跳出去
		if (front == NULL)
			break;

		// 未遇到就继续插入左右孩子
		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	// 前面遇到空了,看后面还会不会有非空
	while (!QueueEmpty(&q));
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		// 有非空,不是完全二叉树
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	// 没有非空,是完全二叉树
	QueueDestroy(&q);
	return true;
}

二叉树的特点:

1.当根节点为第一层时,一棵非空二叉树的第x层上最多有2^(x-1)个节点

2.当根节点为第一层时,深度为h的二叉树的最大节点数为:(2^h) - 1

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

4.当根节点为第一层时,具有n个节点的满二叉树的高度为:h = log(n+1) (log以2为底)

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

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

        2. 若2i+1=n否则无左孩子

        3. 若2i+2=n否则无右孩子

6.一棵满二叉树的上下限为;[2^(h-1),2^h-1]

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值