1. 程式人生 > >再談資料結構(二)數和二叉樹

再談資料結構(二)數和二叉樹

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

【分析】
這道題目是一道簡單的構造二叉樹的題目,然後只需加入開關判斷是進入左子樹還是右子樹即可。
在使用陣列構建二叉樹的時候,我們要清楚的知道二叉樹的基本性質

  1. 二叉樹的第i層上至多有 2
    i 1 2^{i-1}
    個結點。
  2. 深度為k的二叉樹至多有 2 k
    1 2^k-1
    個結點
  3. 葉子結點 n 0 n_0 ,度為2的結點為 n 2 n_2 ,則 n 0 = n 2 + 1 n_0=n_2+1
  4. n個結點的完全二叉樹深度為 l o g n + 1 \left \lfloor logn \right \rfloor+1
  5. n個結點的完全二叉樹,節點按層次編號
    有:i的雙親是 n / 2 + 1 \left \lfloor n/2\right \rfloor+1 ,如果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;
}

在這裡插入圖片描述