再談資料結構(二)數和二叉樹
1 - 引言
關於樹和二叉樹,我們需要達到的能力有:
- 熟悉樹和二叉樹的有關概念
- 熟悉二叉樹的性質
- 熟練掌握遍歷二叉樹的遞迴演算法,並靈活運用
- 遞迴遍歷二叉樹及其應用
本文著重在樹和二叉樹實際應用與程式碼實現基本操作,對概念就不再贅述
2 - 二叉樹的儲存結構
2.1 - 順序儲存結構
二叉樹可以用陣列儲存,編號i的節點存放在[i-1]處,適合於儲存完全二叉樹
讓我們看一道例題來感受一下用陣列怎麼使用二叉樹。
例題2-1 小球下落(Dropping Balls, UVa 679)
有一棵二叉樹,最大深度為D,且所有葉子的深度都相同。所有結點從上到下從左到右
編號為1, 2, 3,…, 2D-1。在結點1處放一個小球,它會往下落。每個內結點上都有一個開關,
初始全部關閉,當每次有小球落到一個開關上時,狀態都會改變。當小球到達一個內結點
時,如果該結點上的開關關閉,則往左走,否則往右走,直到走到葉子結點,如圖所
示。
一些小球從結點1處依次開始下落,最後一個小球將會落到哪裡呢?輸入葉子深度D和
小球個數I,輸出第I個小球最後所在的葉子編號。假設I不超過整棵樹的葉子個數。D≤20。
輸入最多包含1000組資料。
樣例輸入:
4 2
3 4
10 1
2 2
8 128
16 12345
樣例輸出:
12
7
512
3
255
36358
【分析】
這道題目是一道簡單的構造二叉樹的題目,然後只需加入開關判斷是進入左子樹還是右子樹即可。
在使用陣列構建二叉樹的時候,我們要清楚的知道二叉樹的基本性質
- 二叉樹的第i層上至多有 個結點。
- 深度為k的二叉樹至多有 個結點
- 葉子結點 ,度為2的結點為 ,則
- n個結點的完全二叉樹深度為
- n個結點的完全二叉樹,節點按層次編號
有:i的雙親是 ,如果i=1時為根(無雙親);
i的左孩子是2i,如果2i>n,則無左孩子
i的右孩子是2i+1,如果2i+1>n則無右孩子。
在知道這些性質之後,我們不難寫出一個數組二叉樹來模擬題目操作
#include<cstdio>
#include<string>
const int maxd = 20;
int s[1 << maxd]; //最大節點個數為2^maxd-1
int main()
{
int D, I;
while(scanf_s("%d%d",&D,&I)==2){
memset(s, 0, sizeof(s)); //初始化開關
int k, n = (1 << D) - 1; //n是最大節點編號
for(int i = 0;i<I; i++){ //連續讓I個小球下落
k = 1;
for(;;){
s[k] = !s[k];
k = s[k] ? k * 2 : k * 2 + 1; //根據開關狀態選擇下落方向
if (k > n) break; //已經落“出界了”
}
}
printf("%d\n", k / 2); //“出界”之前的葉子編號
}
return 0;
}
2.2 - 二叉樹的鏈式結構
但是在實際操作中,我們用的更多的是鏈式結構來生成二叉樹
首先讓我們來看一下二叉樹結點的連結串列結構定義:
typedef struct BTNode{
int data;
struct BTNode *lchild;
struct BTNode *rchild;
}BiTNode,*BiTree;
在定義了結點後,我們就可以使用這個結構來構建二叉樹,並且對二叉樹進行基本的操作和訪問。
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
typedef struct BTNode{
int data;
struct BTNode *lchild;
struct BTNode *rchild;
}BiTNode,*BiTree;
int CreateBiTree(BiTree *T){
int ch;
cin >> ch;
if(ch == -1)
{
*T = NULL;
return 0;
}
else
{
*T = (BiTree)malloc(sizeof(BiTNode));
if(T == NULL)
{
printf("failed\n");
return 0;
}
else
{
(*T)->data = ch;
printf("輸入%d的左子節點:",ch);
CreateBiTree(&((*T)->lchild));
printf("輸入%d的右子節點:", ch);
CreateBiTree(&((*T)->rchild));
}
}
return true;
}
void PreOrderBiTree(BiTree T)
{
if (T == NULL)
{
return;
}
else
{
printf("%d ", T->data);
PreOrderBiTree(T->lchild);
PreOrderBiTree(T->rchild);
}
}
void MiddleOrderBiTree(BiTree T)
{
if (T == NULL)
{
return;
}
else
{
MiddleOrderBiTree(T->lchild);
printf("%d ", T->data);
MiddleOrderBiTree(T->rchild);
}
}
void PostOrderBiTree(BiTree T)
{
if (T == NULL)
{
return;
}
else
{
PostOrderBiTree(T->lchild);
PostOrderBiTree(T->rchild);
printf("%d ", T->data);
}
}
int main()
{
BiTree T;
cout << "請輸入根節點:\n";
CreateBiTree(&T);
printf("先序遍歷二叉樹:");
PreOrderBiTree(T);
printf("\n");
printf("中序遍歷二叉樹:");
MiddleOrderBiTree(T);
printf("\n");
printf("後序遍歷二叉樹:");
PostOrderBiTree(T);
printf("\n");
return 0;
}
讓我們將上圖的二叉樹輸入這個程式觀察輸出結果(輸入時,當結點沒有子節點則輸入-1)
下面讓我們看一道例題如何使用層遍歷
樹的層次遍歷(Trees on the level, Duke 1993, UVa 122)
輸入一棵二叉樹,你的任務是按從上到下、從左到右的順序輸出各個結點的值。每個結
點都按照從根結點到它的移動序列給出(L表示左,R表示右)。在輸入中,每個結點的左
括號和右括號之間沒有空格,相鄰結點之間用一個空格隔開。每棵樹的輸入用一對空括
號“()”結束(這對括號本身不代表一個結點),如圖所示。
注意,如果從根到某個葉結點的路徑上有的結點沒有在輸入中給出,或者給出超過一
次,應當輸出-1。結點個數不超過256。
樣例輸入:
(11,LL) (7,LLL) (8,R) (5,) (4,L) (13,RL) (2,LLR) (1,RRR) (4,RR) ()
(3,L) (4,R) ()
樣例輸出:
5 4 8 11 13 4 7 2 1
-1
【分析】
-
1.用結構連結串列來建樹
-
2.用佇列來實現層次遍歷,當遍歷到根節點時,將其子節點壓入佇列
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <cstdio>
using namespace std;
//樹結點
struct Node{
int v ;
Node* left,*right ;
int have_value ;
Node():have_value(false),left(NULL),right(NULL){} ;
} ;
Node* root ;//根節點
Node* newnode(){
return new Node() ; //返回一個新結點
}
bool failed ;
void addnode(int v,char* s){//新增新結點
int n = strlen(s);
Node* u = root ;
for(int i = 0;i < n;i++)//找到要加入的位置
{
if(s[i] == 'L'){
if(u->left == NULL) u->left = newnode();
u = u->left;
}
else if(s[i] == 'R'){
if(u->right == NULL) u->right= newnode();
u = u->right ;
}
}
if(u->have_value) failed = true ;//是否已經被訪問過;
u->v = v;
u->have_value = true;
}
void freetree(Node* u){ //釋放記憶體
if(u == NULL) return ;
freetree(u->left);
freetree(u->right);
delete u;
}
char s[1005];
bool read_input(){
failed = false ;
freetree(root) ;
root = newnode();
while(true){
if(scanf("%s", s) != 1) return false;
if(!strcmp(s,"()")) break;
int v ;
sscanf(&s[1],"%d",&v);
addnode(v,strchr(s,',')+1);
}
return true ;
}
bool bfs(vector<int>& ans){//搜尋
queue<Node*> q;
ans.clear();
q.push(root);
while(!q.empty()){
Node *u = q.front();q.pop();
if(!u->have_value) return false;
ans.push_back(u->v);
if(u->left != NULL) q.push(u->left);
if(u->right != NULL) q.push(u->right);
}
return true ;
}
int main(int argc, char *argv[])
{
vector<int> ans;
while(read_input()){
if(!bfs(ans)) failed = 1;
if(failed) printf("not complete\n");
else{
for(int i = 0;i < ans.size();i++)
{
if(i != 0) cout << " " ;
cout << ans[i];
}
cout << endl ;
}
}
return 0;
}