C++數據結構_樹的理論學習筆記(2)_存儲結構,二叉樹的實現

前置:C++數據結構_樹的理論學習筆記(1)_基本概念和基本操作

1.3 存儲結構

1.3.1 數的存儲結構

基本要求:
    ①能夠存儲各結點信息;
    ②唯一的表示各結點之間的邏輯結構——父子關係

1.雙親表示法
    (1)原理:利用一維數組來表示樹,一維數組的每個元素表示樹的結點,其中包括結點的數據和該結點 的雙親在數組中的下標;數組中的第一個元 素表示根結點,該結點無雙親,因此parent域用-1表示,其他結點按照層序存儲.如結點B、 C、D 的雙親結點是下標爲0的根結點,其parent域用0表示。
        在這裏插入圖片描述

    (2)C++描述

template < class T > struct pNode //結點的C++描述
{
	T data;
	int parent;
};

#define MAXSIZE 1000
pNode< T > Tree[MAXSIZE]; //樹的C++描述
int size; //樹的總結點數

    (3)結點結構:
        在這裏插入圖片描述
    (4) 優點:結構簡單,查找結點的雙親或者祖先非常方便

2.孩子表示法
    (1)應用時需要查找當前結點的孩子,雙親表示法就會比較複雜。此時爲了適應查找孩子結點的需求,可以使用孩子表示法
        在這裏插入圖片描述
    (2)C++描述

struct CNode //孩子鏈表結點結構
{
	int child; //孩子結點在表頭數組中的下標
	CNode* next; //指向下一個孩子結點
};

template < class T > struct CBNode //表頭結點
{
	T data;
	CNode* firstchild; //指向第一個孩子結點
};

    (3)孩子表示法與雙親表示法正好相反,查找孩子結點很方便,但是查找雙親結點較爲複雜

3.多重鏈表法
    (1)原理:多重鏈表法指每個結點包括一個結點信息域和多個指針域,每個指針域指向該結點的 一個孩子結點,通過各個指針域的值反映出樹中各結點之間的邏輯關係。這種表示法中,樹中每個結點有多個指針域,從而形成了多條鏈表。由於每個結點的孩子個數沒有限制,各結點的度數又各異,可能會造成存儲空間的浪費。例如,一棵度爲k的 樹,若其結點總數爲n,則至少要浪費nk-n+1個空指針域
        在這裏插入圖片描述
    (2)缺點:不適合存儲度數較大的樹

4.孩子兄弟表示法
    (1)原理:孩子兄弟表示法又稱二叉鏈表表示法,鏈表中的每個結點包含一個數據域和兩個指針域, 其中,數據域用來存儲結點數據;第1個指針域指向該結點的第一個孩子結點;第2個指針域指向該結點的第一個右兄弟。
        在這裏插入圖片描述
    (2)C++描述

template < class T > struct TNode
{
	T data;
	TNode<T>* firstchild;
	TNode<T>* rightsib;
};

    (3) 優點:可以將任意複雜的樹結構轉換成二叉樹,這樣對樹的研究就可以轉化爲對二叉樹的研究,降低了問題的複雜程度

1.3.2 二叉樹的存儲結構

1.順序存儲結構:
        二叉樹的順序存儲結構使用一維數組存儲二叉樹的結點,利用結點的存儲位置來表示結點之間的關係。具體如下:
        (1)將二叉樹按照完全二叉樹編號;
        (2)其中無結點的位置使用NULL表示,結點則存儲在一維數組相應的位置上。如下圖所示:
        在這裏插入圖片描述
        這種存儲數據的方法邏輯簡單但會造成空間的浪費,因此,該方法最適合存儲完全二叉樹

2.二叉鏈表
        (1)基本思想如下圖所示:
        在這裏插入圖片描述
        (2)結點結構:
        在這裏插入圖片描述
        (3)C++描述:

template < class T > struct BiNode
{
	T data;
	BiNode<T>* lchild;
	BiNode<T>* rchild;
};

        二叉鏈表的存儲方式和樹的孩子兄弟表示法的存儲結構完全相同,任何一棵複雜的樹都可以容易地使用二叉鏈表的方式進行存儲

3.三叉鏈表
        在二叉鏈表的存儲方式下,從某結點出發可以直接訪問到它的孩子結點,但要找到它的雙親,則必須從根結點開始搜索,最壞情況下,可能需要遍歷整個二叉鏈表。所以, 在這種情況下,可以採用三叉鏈表來存儲二叉樹,以避免該問題的發生
        (1)結構:
        在這裏插入圖片描述
        (2)結點結構:
        在這裏插入圖片描述

        (3)C++描述:

template < class T > struct BiNode
{
	T data;
	BiNode<T>* parent;
	BiNode<T>* lchild;
	BiNode<T>* rchild;
};

1.4 二叉樹的實現

1.4.1 二叉樹的聲明

        採用二叉鏈表作爲存儲結構的二叉樹其簡單的 C++ 描述如下:

template<class T> class BiTree
{
private:
	void Create(BiNode<T>* &R, T data[], int i, int n); //創建二叉樹
	void Release(BiNode<T>* R); //釋放二叉樹
public:
	BiNode<T>* root; //根結點
	BiTree(T data[], int n); //構造函數
	void PreOrder(BiNode<T>* R); //前序遍歷
	void InOrder(BiNode<T>* R); //中序遍歷
	void PostOrder(BiNode<T>* R); //後序遍歷
	void LevelOrder(BiNode<T>* R); //層序遍歷
	~BiTree(); //析構函數
};

1.4.2 二叉樹的關鍵算法

1.二叉樹的創建
        建立二叉樹有很多種方法,其中較爲簡單的就是使用順序存儲結構來建立二叉鏈表。以順序存儲結構爲輸入創建二叉樹時,採用先建立根結點,再建立左右孩子的方法遞歸地建立用二叉鏈表表示的二叉樹,其 C++描述如下:

template<class T>
void BiTree<T>::Create(BiNode <T>*& R, T data[], int i, int n) //i表示位置,從1開始
{
	if (i <= n && data[i - 1] != 0)
	{
		R = new BiNode<T>; //創建根結點
		R->data = data[i - 1];
		R->lchild = R->rchild = NULL;
		Create(R->lchild, data, 2 * i); //創建左子樹
		Create(R->rchild, data, 2 * i + 1); //創建右子樹
	}
}

template<class T> BiTree<T>::BiTree(T data[], int n)
{
	Create(root, data, 1, n);
}

        上述遞歸程序分解步驟如下,假設輸入爲下圖所示的順序存儲結構表示的二叉樹,則遞歸地建立用二叉鏈表表示的二叉樹示意圖如下圖所示
        在這裏插入圖片描述
2.二叉樹前序、中序、後序遍歷的實現
        由二叉樹的前序遍歷定義,結合遞歸,前序遍歷的結果如下圖所示。
        在這裏插入圖片描述

         C++算法描述如下:

template<class T>
void BiTree<T>::PreOrder(BiNode<T>* R)
{
	if (R != NULL)
	{
		cout << R->data; //訪問結點
		PreOrder(R->lchild); //遍歷左子樹
		PreOrder(R->rchild); //遍歷右子樹
	}
}

         對於中序遍歷,只需要把語句 cout << R->data; 移到語句 PreOrder(R->lchild); 之後即可;
         對於後續遍歷,只需要把語句 cout << R->data; 移到語句 PreOrder(R->rchild); 之後即可。

3.層序遍歷的實現
         二叉鏈表的層序遍歷基本思想如下:
         在這裏插入圖片描述
         具體描述如下:
         【1】若根結點非空,入隊。
         【2】如果隊列不空:
                  【2.1】隊頭元素出隊;
                  【2.2】訪問該元素;
                  【2.3】若該結點的左孩子非空,則左孩子入隊;
                  【2.4】若該結點的右孩子非空,則右孩子入隊。
         C++描述如下:

template<class T>
void BiTree<T>::LevelOrder(BiNode<T>* R)
{
	BiNode<T>* queue[MAXSIZE];
	int f = 0; r = 0; //初始化空隊列
	if (R != NULL) queue[++r] = R; //根結點入隊
	while (f != r)
	{
		BiNode<T>* p = queue[++f]; //表頭元素出隊
		cout << p->data; //出隊打印
		if (p->lchild != NULL) queue[++r] = p->lchild; //左孩子入隊
		if (p->rchild != NULL) queue[++r] = p->rchild; //右孩子入隊
	}
}

4.析構函數的實現
         二叉鏈表屬於動態存儲分配,因此,需要在析構函數中釋放二叉鏈表的所有結點.爲了防止內存泄漏,釋放結點時應先釋放該結點的左右子樹,左右子樹全部釋放完畢後再釋放該結點。採用後序遍歷的方法,具體實現如下:

template<class T>
void BiTree<T>::Release(BiNode<T>* R) //釋放二叉樹
{
	if (R != NULL)
	{
		Release(R->lchild); //釋放左子樹
		Release(R->rchild); //釋放右子樹
		delete R; //釋放根結點
	}
}

template<class T> BiTree<T>::~BiTree() //釋放二叉樹
{
	Release(root);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章