二叉樹
二叉樹是一種特殊的樹形結構,其每個節點恰好有兩顆可以為空的子樹(左子樹和右子樹),子樹左右順序不能顛倒。
1.二叉樹的性質
- 一顆二叉樹有\(n\)個元素,\(n>0\),它有\(n-1\)條邊。
- 在二叉樹的第\(i\)層上,至多有\(2^{i-1}\)個元素。
- 一顆高度為\(h\)的樹,其最少有\(h\)個元素,最多有\(2^h-1\)個元素。
- 一顆二叉樹有\(n\)個元素,則它的高度最大為n,最小為\(\lceil\log_2(n+1)\rceil\)。
- 對於任何一顆二叉樹,如果其終端節點數為\(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)\)有以下關系成立:
- 如果\(i=1\),則該元素為二叉樹的跟。若\(i>1\),則其父節點的編號為\(\lfloor i/2\rfloor\)。
- 如果\(2i>n\),則該元素無左子樹。否則,其左子樹的編號為\(2i\)。
- 如果\(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);
}
}
對於上圖所示的二叉樹,它的遍歷結果為:
- 先序遍歷:R A C F D B G E
- 中序遍歷:C F A D R B E G
- 后序遍歷:F C D A E G B R
- 層次遍歷: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)))",可以完整構建一顆二叉樹: