前置: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);
}