二叉樹


二叉樹


二叉樹是一種特殊的樹形結構,其每個節點恰好有兩顆可以為空的子樹(左子樹和右子樹),子樹左右順序不能顛倒。

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