樹是數據結構的重點,更是算法的重點。這一章的知識還是不少的,需要多看,多思考,尤其是一些遞歸的代碼,怎樣想到遞歸,返回值是啥,參數爲什麼這樣寫,遞歸出口如何設置都需要搞清楚,弄明白。重點是第四部分,二叉樹的存儲結構及實現有很多細節。
一,樹的邏輯結構
這裏所說的樹都是有序樹,樹的其中一個重要功能便是遍歷,分爲前序(根)遍歷、後序(根)遍歷和層序(次)遍歷。
是根據訪問根節點的時機不同來區分的,注意,還有一箇中序遍歷,這是二叉樹特有的遍歷。
樹的度注意與圖的度區分。
二,樹的存儲結構
1,雙親表示法。用一維數組存儲樹的各個節點
data:存儲樹中結點的數據信息
parent:存儲該結點的雙親在數組中的下標
2,孩子表示法-多重鏈表表示法
方案一:指針域的個數等於樹的度 (浪費空間)
data:數據域,存放該結點的數據信息;
child1~childd:指針域,指向該結點的孩子。
方案二: 指針域的個數等於該結點的度(節點結構不一致)
data:數據域,存放該結點的數據信息;
degree:度域,存放該結點的度;
child1~childd:指針域,指向該結點的孩子。
3,孩子表示法-孩子鏈表表示法(每個節點創建一個單鏈表)
把每個結點的孩子排列起來,看成是一個線性表,且以單鏈表存儲,則n個結點共有 n 個孩子鏈表。
這 n 個單鏈表共有 n 個頭指針,這 n 個頭指針又組成了一個線性表。
爲了便於進行查找採用順序存儲存儲每個鏈表的頭指針。
最後,將存放 n 個頭指針的數組和存放n個結點的數組結合起來,構成孩子鏈表的表頭數組。
4,孩子兄弟表示法
設置兩個分別指向該結點的第一個孩子和右兄弟的指針
data:數據域,存儲該結點的數據信息;
firstchild:指針域,指向該結點第一個孩子;
rightsib:指針域,指向該結點的右兄弟結點
三,二叉樹的邏輯結構
1,斜樹
1. 在斜樹中,每一層只有一個結點;
2.斜樹的結點個數與其深度相同
2,滿二叉樹
滿二叉樹在同樣深度的二叉樹中結點個數最多
滿二叉樹在同樣深度的二叉樹中葉子結點個數最多
3,完全二叉樹
1. 葉子結點只能出現在最下兩層,且最下層的葉子結點都集中在二叉樹的左部;
2. 完全二叉樹中如果有度爲1的結點,只可能有一個,且該結點只有左孩子。
3. 深度爲k的完全二叉樹在k-1層上一定是滿二叉樹。
4,二叉樹的基本性質
二叉樹的第i層上最多有2的i-1次方個結點(i≥1)。
一棵深度爲k的二叉樹中,最多有2的k次方-1個結點,最少有k個結點。
深度爲k且具有2k-1個結點的二叉樹一定是滿二叉樹,
深度爲k且具有k個結點的二叉樹不一定是斜樹。
在一棵二叉樹中,如果葉子結點數爲n0,度爲2的結點數爲n2,則有: n0=n2+1。
5,完全二叉樹的基本性質
具有n個結點的完全二叉樹的深度爲 log2n向下取整 +1。
對一棵具有n個結點的完全二叉樹中從1開始按層序編號,則對於任意的序號爲i(1≤i≤n)的結點(簡稱爲結點i),有:
(1)如果i>1,
則結點i的雙親結點的序號爲 i/2;如果i=1,
則結點i是根結點,無雙親結點。
(2)如果2i≤n,
則結點i的左孩子的序號爲2i;
如果2i>n,則結點i無左孩子。
(3)如果2i+1≤n,
則結點i的右孩子的序號爲2i+1;如果2i+1>n,則結點 i無右孩子。
四,二叉樹的存儲結構及實現
1,二叉樹的順序存儲結構就是用一維數組存儲二叉樹中的結點,並且結點的存儲位置(下標)應能體現結點之間的邏輯關係——父子關係。完全二叉樹和滿二叉樹中結點的序號可以唯一地反映出結點之間的邏輯關係 。
中序+前(後)序遍歷可確定一棵二叉樹。下面是中序和前序的例子:
#include<bits/stdc++.h>
using namespace std;
typedef struct nnode{
char data;
struct nnode *lchild,*rchild;
}nnode,*btree; //此處用到typedef將nnode* 重命名爲btree。可進行等價替換
//struct nnode{
// char data;
//struct nnode *lchild,*rchild;
//};
//nnode *btree;
string porder,iorder;
btree Build(string porder,string iorder,int s1,int e1,int s2,int e2) //nnode* Build(
{
btree t =new nnode(); //nnode *t =new nnode();
t->data=porder[s1];
int root;
for(int i=s2;i<=e2;i++)
{
if(porder[s1]==iorder[i])
{
root=i;break;
}
}
int llen = root - s2;
int rlen = e2 - root;
if(llen != 0) {
t -> lchild = new nnode();
t -> lchild = Build(porder,iorder,s1 + 1,s1 + llen,s2,s2 + llen - 1);
}
else
t -> lchild = NULL; if(rlen != 0) {
t -> rchild = new nnode();
t-> rchild = Build(porder,iorder,e1 - rlen + 1,e1,e2 - rlen + 1,e2);
}
else {
t -> rchild = NULL;
}
return t;
}
void horder(btree t){ //void horder(nnode *t){
if(t!=NULL)
{
horder(t->lchild);
horder(t->rchild);
cout<<t->data;
}
}
int main(){
btree t=NULL; //nnode *t=NULL;
cin>>porder>>iorder;
t=Build(porder,iorder,0,porder.length()-1,0,iorder.length()-1);
horder(t);
}
2,二叉鏈表
標準二叉鏈表(拓展二叉樹構建的樹)結構表示如下:
#include<iostream>
#include<string.h>
using namespace std;
struct bnode{
char data;
bnode *lchild,*rchild;
};
class btree{
private:
bnode *creat();
bnode *root;
void inorder(bnode *bt);
void porder(bnode *bt);
void horder(bnode *bt);
public:
btree(){ root=creat(); }
void inorder(){inorder(root);}
void porder(){porder(root);}
void horder(){horder(root);} //在函數有返回值時 要加return
};
bnode *btree::creat(){
bnode *bt;
char a;
cin>>a;
if(a=='#') bt=NULL;
else{
bt= new bnode;
bt->data=a;
bt->lchild=creat();
bt->rchild=creat();
}
return bt;
}
void btree::inorder(bnode *bt){
if(bt==NULL) return;
else{
inorder(bt->lchild);
cout<<bt->data;
inorder(bt->rchild);
}
////後序遍歷的方法析構,將輸出改爲delete root
遍歷還可以不使用遞歸算法,前,中,後,可用棧,中序可用隊列實現。
前序遍歷:每個節點只進棧一次,在進棧前訪問節點
中序遍歷:每個節點進棧一次,在出棧時訪問節點
後序遍歷:每個節點進棧兩次,在第二次出棧時訪問節點(要做標記,比較麻煩)
/ /前序遍歷基本思想/ /
while (root!=NULL | | !s.empty()) {
while (root!= NULL) {
cout<<root->data;
s.push(root);
root=root->lchild;
}
if (!s.empty()) {
root=s.top();
s.pop();
root=root->rchild;
}
}
層序遍歷,非遞歸
2. 如果二叉樹非空,將根指針入隊;
3. 循環直到隊列Q爲空 (重複隊首出隊左右兒子入隊)
3.1 q=隊列Q的隊頭元素出隊;
3.2 訪問結點q的數據域;
3.3 若結點q存在左孩子,則將左孩子指針入隊;
3.4 若結點q存在右孩子,則將右孩子指針入隊;