二叉樹與二叉查找樹

二叉樹與二叉查找樹

樹(Tree)是n(n≥0)個結點的有限集。在任意一棵非空樹中:(1)有且僅有一個特定的被稱爲根(Root)的結點;(2)當n>1時,其餘結點可分爲m(m>0)個互不相交的有限集T1,T2,…,Tm,其中每一個集合本身又是一棵樹,並且稱爲根的子樹(SubTree)。

    結點擁有的子樹數稱爲結點的度(Degree)。度爲0的結點稱爲葉子(Leaf)或終端結點。度不爲0的結點稱爲非終端結點或分支結點。

    樹的度是樹內各結點的度的最大值。

    結點的子樹的根稱爲該結點的孩子(Child),相應地,該結點稱爲孩子的雙親(Parent)。

    結點的層次(Level)是從根結點開始計算起,根爲第一層,根的孩子爲第二層,依次類推。樹中結點的最大層次稱爲樹的深度(Depth)或高度。

    如果將樹中結點的各子樹看成從左至右是有次序的(即不能互換),則稱該樹爲有序樹,否則稱爲無序樹。

    1、二叉樹

    二叉樹(Binary Tree)的特點是每個結點至多具有兩棵子樹(即在二叉樹中不存在度大於2的結點),並且子樹之間有左右之分。

    二叉樹的性質:

    (1)、在二叉樹的第i層上至多有2i-1個結點(i≥1)。

    (2)、深度爲k的二叉樹至多有2k-1個結點(k≥1)。

    (3)、對任何一棵二叉樹,如果其終端結點數爲n0,度爲2的結點數爲n2,則n0=n2+1。

    一棵深度爲k且有2k-1個結點的二叉樹稱爲滿二叉樹。

    可以對滿二叉樹的結點進行連續編號,約定編號從根結點起,自上而下,自左至右,則由此可引出完全二叉樹的定義。深度爲k且有n個結點的二叉樹,當且僅當其每一個結點都與深度爲k的滿二叉樹中編號從1到n的結點一一對應時,稱之爲完全二叉樹。

 

    (4)、具有n個結點的完全二叉樹的深度爲不大於log2n的最大整數加1。

    (5)、如果對一棵有n個結點的完全二叉樹的結點按層序編號(從第1層到最後一層,每層從左到右),則對任一結點i(1≤i≤n),有

    a、如果i=1,則結點i是二叉樹的根,無雙親;如果i>1,則其雙親是結點x(其中x是不大於i/2的最大整數)。

    b、如果2i>n,則結點i無左孩子(結點i爲葉子結點);否則其左孩子是結點2i。

    c、如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1。

    二叉樹的鏈式存儲:

    鏈式二叉樹中的每個結點至少需要包含三個域,數據域和左、右指針域。

 

    二叉樹的遍歷:

    假如以L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹,則可有DLR、DRL、LRD、LDR、RLD、RDL這六種遍歷二叉樹的方案。若限定先左後右,則只有三種方案,分別稱之爲先(根)序遍歷、中(根)序遍歷和後(根)序遍歷,它們以訪問根結點的次序來區分。

    2、二叉查找樹

    二叉查找樹(BinarySearch Tree,也叫二叉搜索樹,或稱二叉排序樹Binary Sort Tree)或者是一棵空樹,或者是具有下列性質的二叉樹:

    (1)、若它的左子樹不爲空,則左子樹上所有結點的值均小於它的根結點的值;

    (2)、若它的右子樹不爲空,則右子樹上所有結點的值均大於它的根結點的值;

    (3)、它的左、右子樹也分別爲二叉查找樹。

    3、二叉查找樹的基本運算


樹(Tree)是n(n≥0)個結點的有限集。在任意一棵非空樹中:(1)有且僅有一個特定的被稱爲根(Root)的結點;(2)當n>1時,其餘結點可分爲m(m>0)個互不相交的有限集T1,T2,…,Tm,其中每一個集合本身又是一棵樹,並且稱爲根的子樹(SubTree)。

    結點擁有的子樹數稱爲結點的度(Degree)。度爲0的結點稱爲葉子(Leaf)或終端結點。度不爲0的結點稱爲非終端結點或分支結點。

    樹的度是樹內各結點的度的最大值。

    結點的子樹的根稱爲該結點的孩子(Child),相應地,該結點稱爲孩子的雙親(Parent)。

    結點的層次(Level)是從根結點開始計算起,根爲第一層,根的孩子爲第二層,依次類推。樹中結點的最大層次稱爲樹的深度(Depth)或高度。

    如果將樹中結點的各子樹看成從左至右是有次序的(即不能互換),則稱該樹爲有序樹,否則稱爲無序樹。

    1、二叉樹

    二叉樹(Binary Tree)的特點是每個結點至多具有兩棵子樹(即在二叉樹中不存在度大於2的結點),並且子樹之間有左右之分。

    二叉樹的性質:

    (1)、在二叉樹的第i層上至多有2i-1個結點(i≥1)。

    (2)、深度爲k的二叉樹至多有2k-1個結點(k≥1)。

    (3)、對任何一棵二叉樹,如果其終端結點數爲n0,度爲2的結點數爲n2,則n0=n2+1。

    一棵深度爲k且有2k-1個結點的二叉樹稱爲滿二叉樹。

    可以對滿二叉樹的結點進行連續編號,約定編號從根結點起,自上而下,自左至右,則由此可引出完全二叉樹的定義。深度爲k且有n個結點的二叉樹,當且僅當其每一個結點都與深度爲k的滿二叉樹中編號從1到n的結點一一對應時,稱之爲完全二叉樹。

 

    (4)、具有n個結點的完全二叉樹的深度爲不大於log2n的最大整數加1。

    (5)、如果對一棵有n個結點的完全二叉樹的結點按層序編號(從第1層到最後一層,每層從左到右),則對任一結點i(1≤i≤n),有

    a、如果i=1,則結點i是二叉樹的根,無雙親;如果i>1,則其雙親是結點x(其中x是不大於i/2的最大整數)。

    b、如果2i>n,則結點i無左孩子(結點i爲葉子結點);否則其左孩子是結點2i。

    c、如果2i+1>n,則結點i無右孩子;否則其右孩子是結點2i+1。

    二叉樹的鏈式存儲:

    鏈式二叉樹中的每個結點至少需要包含三個域,數據域和左、右指針域。

 

    二叉樹的遍歷:

    假如以L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹,則可有DLR、DRL、LRD、LDR、RLD、RDL這六種遍歷二叉樹的方案。若限定先左後右,則只有三種方案,分別稱之爲先(根)序遍歷、中(根)序遍歷和後(根)序遍歷,它們以訪問根結點的次序來區分。

    2、二叉查找樹

    二叉查找樹(BinarySearch Tree,也叫二叉搜索樹,或稱二叉排序樹Binary Sort Tree)或者是一棵空樹,或者是具有下列性質的二叉樹:

    (1)、若它的左子樹不爲空,則左子樹上所有結點的值均小於它的根結點的值;

    (2)、若它的右子樹不爲空,則右子樹上所有結點的值均大於它的根結點的值;

    (3)、它的左、右子樹也分別爲二叉查找樹。

    3、二叉查找樹的基本運算

/* bst - binary search/sort tree 
 * by Richard Tang <[email protected]> 
 */
#include <stdio.h>
#include <stdlib.h>

typedef int data_type;

typedef struct bst_node {
    data_type data;
    struct bst_node *lchild, *rchild;
}bst_t, *bst_p;

(1)、插入

    在二叉查找樹中插入新結點,要保證插入新結點後仍能滿足二叉查找樹的性質。例子中的插入過程如下:

    a、若二叉查找樹root爲空,則使新結點爲根;

    b、若二叉查找樹root不爲空,則通過search_bst_for_insert函數尋找插入點並返回它的地址(若新結點中的關鍵字已經存在,則返回空指針);

    c、若新結點的關鍵字小於插入點的關鍵字,則將新結點插入到插入點的左子樹中,大於則插入到插入點的右子樹中。 

static bst_p search_bst_for_insert(bst_p *root, data_type key)
{
    bst_p s, p = *root;

    while (p) {
	s = p;

	if (p->data == key)
	    return NULL;

	p = (key < p->data) ? p->lchild : p->rchild;
    }

    return s;
}

void insert_bst_node(bst_p *root, data_type data)
{
    bst_p s, p;

    s = malloc(sizeof(struct bst_node));
    if (!s)
	perror("Allocate dynamic memory");

    s -> data = data;
    s -> lchild = s -> rchild = NULL;

    if (*root == NULL)
	*root = s;
    else {
	p = search_bst_for_insert(root, data);
	if (p == NULL) {
	    fprintf(stderr, "The %d already exists.\n", data);
	    free(s);
	    return;
	}

	if (data < p->data)
	    p->lchild = s;
	else
	    p->rchild = s;
    }
}

    (2)、遍歷

static int print(data_type data)
{
    printf("%d ", data);

    return 1;
}

int pre_order_traverse(bst_p root, int (*visit)(data_type data)) 
{
    if (root) {
	if (visit(root->data))
		if (pre_order_traverse(root->lchild, visit))
		    if (pre_order_traverse(root->rchild, visit))
			    return 1;
	return 0;
    }
    else
	return 1;
}

int post_order_traverse(bst_p root, int (*visit)(data_type data))
{
    if (root) {
	if (post_order_traverse(root->lchild, visit))
	    if (visit(root->data))
		if (post_order_traverse(root->rchild, visit))
		    return 1;
	return 0;
    }
    else
	return 1;
}

中序遍歷二叉查找樹可得到一個關鍵字的有序序列。

    (3)、刪除

    刪除某個結點後依然要保持二叉查找樹的特性。例子中的刪除過程如下:

    a、若刪除點是葉子結點,則設置其雙親結點的指針爲空。

    b、若刪除點只有左子樹,或只有右子樹,則設置其雙親結點的指針指向左子樹或右子樹。

    c、若刪除點的左右子樹均不爲空,則:

    1)、查詢刪除點的右子樹的左子樹是否爲空,若爲空,則把刪除點的左子樹設爲刪除點的右子樹的左子樹。

 

    2)、若不爲空,則繼續查詢左子樹,直到找到最底層的左子樹爲止。

 

void delete_bst_node(bst_p *root, data_type data)
{
    bst_p p = *root, parent, s;

    if (!p) {
	fprintf(stderr, "Not found %d.\n", data);
	return;
    }

    if (p->data == data) {
	/* It's a leaf node */
	if (!p->rchild && !p->lchild) {
	    *root = NULL;
	    free(p);
	}
	/* the right child is NULL */
	else if (!p->rchild) {
	    *root = p->lchild;
	    free(p);
	}
	/* the left child is NULL */
	else if (!p->lchild) {
	    *root = p->rchild;
	    free(p);
	}
	/* the node has both children */
	else {
	    s = p->rchild;
	    /* the s without left child */
	    if (!s->lchild)
		s->lchild = p->lchild;
	    /* the s have left child */
	    else {
		/* find the smallest node in the left subtree of s */
		while (s->lchild) {
		    /* record the parent node of s */
		    parent = s;
		    s = s->lchild;
		}
		parent->lchild = s->rchild;
		s->lchild = p->lchild;
		s->rchild = p->rchild;
	    }
	    *root = s;
	    free(p);
	}
    } 
    else if (data > p->data) {
	delete_bst_node(&(p->rchild), data);
    } 
    else if (data < p->data) {
	delete_bst_node(&(p->lchild), data);
    }
}

    4、二叉查找樹的查找分析

    同樣的關鍵字,以不同的插入順序,會產生不同形態的二叉查找樹。

int main(int argc, char *argv[])
{
    int i, num;
    bst_p root = NULL;

    if (argc < 2) {
	fprintf(stderr, "Usage: %s num\n", argv[0]);
	exit(-1);
    }

    num = atoi(argv[1]);
    data_type arr[num];
    printf("Please enter %d integers:\n", num);
    for (i = 0; i < num; i++) {
	scanf("%d", &arr[i]);
	insert_bst_node(&root, arr[i]);
    }

    printf("\npre order traverse: ");
    pre_order_traverse(root, print);
    printf("\npost order traverse: ");
    post_order_traverse(root, print);
    printf("\n");

    delete_bst_node(&root, 45);

    printf("\npre order traverse: ");
    pre_order_traverse(root, print);
    printf("\npost order traverse: ");
    post_order_traverse(root, print);
    printf("\n");
    
    return 0;
}

運行兩次,以不同的順序輸入相同的六個關鍵字:

     

    根據前序遍歷的結果可得到兩次運行所產生的二叉查找樹的形態並不相同,如下圖:

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章