二叉树


二叉树


二叉树是一种特殊的树形结构,其每个节点恰好有两颗可以为空的子树(左子树和右子树),子树左右顺序不能颠倒。

1.二叉树的性质


  1. 一颗二叉树有\(n\)个元素,\(n>0\),它有\(n-1\)条边。
  2. 在二叉树的第\(i\)层上,至多有\(2^{i-1}\)个元素。
  3. 一颗高度为\(h\)的树,其最少有\(h\)个元素,最多有\(2^h-1\)个元素。
  4. 一颗二叉树有\(n\)个元素,则它的高度最大为n,最小为\(\lceil\log_2(n+1)\rceil\)
  5. 对于任何一颗二叉树,如果其终端节点数为\(n_0\),度为2的节点数为\(n_2\),则\(n_0=n_2+1\)

2.完全二叉树



如上图所示,当一颗高度为h的二叉树恰好有\(2^h-1\)个元素时,称其为满二叉树。我们对高度为\(h\)的满二叉树,从上到下,从左往右依次排序并编号,从\(1\)一直到\(2^h-1\)。假设从满二叉树中删除\(k\)个编号为\(2^h-i\)的元素,\((1\leq i\leq k<2^h)\),所得到的二叉树称为完全二叉树。

满二叉树是完全二叉树的一个特例
对于一个有\(n\)个元素的完全二叉树,其第\(i\)个元素\((1\leq i\leq n)\)有以下关系成立:

  1. 如果\(i=1\),则该元素为二叉树的跟。若\(i>1\),则其父节点的编号为\(\lfloor i/2\rfloor\)
  2. 如果\(2i>n\),则该元素无左子树。否则,其左子树的编号为\(2i\)
  3. 如果\(2i+1>n\),则该元素无右子树。否则,其右子树的编号为\(2i+1\)

3.二叉树的存储结构


对二叉树最常用的描述方法就是指针描述。由二叉树的性质可知,二叉树有两颗子树,用两个指针相连。

//树节点的声明
typedef struct{

}Item;						//二叉树的元素类型,由用户自定义

typedef struct tree_node {
	Item elem;                               //节点的数据域
	struct tree_node * left;            //左子树
	struct tree_node * right;         //右子树
}TreeNode;

typedef struct {
	TreeNode * root;                   //根节点
	unsigned size;                      //树的大小
}BinaryTree;

这样的二叉树节点用图可表示为:


二叉树还可以用数组描述。我们可以把二叉树当作缺少部分元素的完全二叉树,对每个元素进行编号,然后放入相应的数组位置中

//数组形式的二叉树
typedef struct {
	int a;
}Item;							//二叉树的元素类型,由用户自定义

typedef struct{
	Item elem;					//节点的数据域
}BinaryTreeNode;

typedef struct {
	BinaryTreeNode * tree;                  
	unsigned size;                      //树的大小
}BinaryTree;

但这样的二叉树留出没有使用的元素的空间,如果缺少的元素过多,则会浪费大量的空间。但我们可以再在节点中添加左右子树的信息,使得数组二叉树具备紧凑储存的能力

如果节点有左右子树,则将left和right设为对应的下标;如果没有,则设为0。

typedef struct {

}Item;							//二叉树的元素类型,由用户自定义

typedef struct{
	Item elem;					//节点的数据域
	int left;					//左子树
	int right;					//右子树
}BinaryTreeNode;

typedef struct {
	BinaryTreeNode * tree;
	int root;							//根节点的位置
	unsigned size;                      //树的大小
}BinaryTree;

4.二叉树的遍历


二叉树的遍历即按固定路径访问树中的每个节点,使每个节点被访问一次,且仅被访问一次。
二叉树的遍历有4种常用的方法:

先序遍历
中序遍历
后序遍历
层次遍历

二叉树的先序遍历使用递归函数定义的话十分简单

//先序遍历
void pre_order(BinaryTreeNode * pbtn, void(*pfun)(Item *))
{
	if (pbtn != NULL)
	{
		pfun(&pbtn->elem);					//访问前先节点
		pre_order(pbtn->left, pfun);		//递归访问左子树
		pre_order(pbtn->right, pfun);		//递归访问右子树
	}
}

中序遍历仅仅是把访问当前节点的次序放在了访问左子树之后

//中序遍历
void in_order(BinaryTreeNode * pbtn, void(*pfun)(Item *))
{
	if (pbtn != NULL)
	{
		in_order(pbtn->left, pfun);			//递归访问左子树
		pfun(&pbtn->elem);					//访问当前节点
		in_order(pbtn->right, pfun);		//递归访问右子树
	}
}

后序遍历同先序和中序

//后序遍历
void post_order(BinaryTreeNode * pbtn, void(*pfun)(Item *))
{
	if (pbtn != NULL)
	{
		post_order(pbtn->left, pfun);		//递归访问左子树
		post_order(pbtn->right, pfun);		//递归访问右子树
		pfun(&pbtn->elem);					//访问当前节点
	}
}

在层次遍历中,树被一层一层地访问,在同一层中,访问顺序从左到右。

void level_order(BinaryTreeNode * pbtn, void(*pfun)(Item *))
{
	queue q;			//创建一个队列,使用队列辅助层次遍历

	while (pbtn != NULL)
	{
		pfun(&pbtn->elem);				//访问当前节点
		if (pbtn->left != NULL)
			EnQueue(q, pbtn->left);		//如果当前节点的左子树不为空,便加入队列
		if (pbtn->right != NULL)
			EnQueue(q, pbtn->right);	//如果当前节点的右子树不为空,便加入队列
		if (QueueIsEmpty(q))
			pbtn = NULL;
		else
			pbtn = DeQueue(q);
	}
}


对于上图所示的二叉树,它的遍历结果为:

  1. 先序遍历:R A C F D B G E
  2. 中序遍历:C F A D R B E G
  3. 后序遍历:F C D A E G B R
  4. 层次遍历:R A B C D G F E

5.构造二叉树


二叉树可以用形如\(root(leftChild, rightChild)\),的字符串构造。

bool BuildBinaryTree(BinaryTree * bt, const char * scr)
{
	bool isLeft = false;
	BinaryTreeNode * root = NULL;
	stack s;

	if (*scr == '\0')
		return false;
	else
		root = make_node(scr, NULL, NULL);
	++scr;

	while (*scr != '\0')
	{
		
		switch (*scr)
		{
		case '(':
			StackPush(s, root);
			isLeft = true;
			break;
		case ')':
			root = StackPop(s);
			break;
		case ',':
			isLeft = false;
			break;
		default:
			root = make_node(scr, NULL, NULL);
			if (isLeft)
				StackTop(s)->left = root;
			else
				StackTop(s)->right = root;
			break;
		}
		++scr;
	}

	destroy_node(bt->root);
	bt->root = root;

	return true;
}

在上述程序中传入字符串"A(B(C,D(E,)),F(,G(H,I)))",可以完整构建一颗二叉树:


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM