1. 程式人生 > >樹的學習——(遞迴構建二叉樹、遞迴非遞迴前序中序後序遍歷二叉樹、根據前序序列、中序序列構建二叉樹)

樹的學習——(遞迴構建二叉樹、遞迴非遞迴前序中序後序遍歷二叉樹、根據前序序列、中序序列構建二叉樹)

前言

最近兩個星期一直都在斷斷續續的學習二叉樹的資料結構,昨晚突然有點融匯貫通的感覺,這裡記錄一下吧

題目要求

給定前序序列,abc##de#g##f###,構建二叉樹,並且用遞迴和非遞迴兩種方法去做前序,中序和後序遍歷

二叉樹的資料結構

#define STACKSIZE 10005

/**
 * 二叉樹的資料結構
 */
typedef struct btree {
	struct btree *lchild;
	struct btree *rchild;
	char item;
} btree;

typedef btree *bt;

/**
 * 定義順序棧資料結構(非遞迴遍歷)
 */
typedef struct stack {
	btree *db[STACKSIZE];
	int top;
} stack;


遞迴構建二叉樹

構建二叉樹有固定的幾種考察型別:
  1. 根據完整的先序序列構建二叉樹
  2. 根據前序和中序序列構建二叉樹

根據先序序列構建二叉樹(c語言實現)

char str[101] = "abc##de#g##f###";
int count = 0;

/**
 * 根據先序序列構建二叉樹(因為涉及到對根節點指標修改,因此傳遞根節點指標的指標)
 */
void createBtree(btree **t)
{
	if (str[count ++] == '#') {
		*t = NULL;
	} else {
		*t = (btree *)malloc(sizeof(btree));
		(*t)->item = str[count - 1];
		createBtree(&(*t)->lchild);
		createBtree(&(*t)->rchild);
	}
}

遞迴的前序、中序、後序演算法(c語言實現)

/**
 * 遞迴先序遍歷二叉樹
 */
void recPreorder(btree *t)
{
	if (t) {
		printf("%c", t->item);
		recPreorder(t->lchild);
		recPreorder(t->rchild);
	}
}

/**
 * 遞迴中序遍歷二叉樹
 */
void recInorder(btree *t)
{
	if (t) {
		recInorder(t->lchild);
		printf("%c", t->item);
		recInorder(t->rchild);
	}
}

/**
 * 遞迴後序遍歷二叉樹
 */
void recPostorder(btree *t)
{
	if (t) {
		recPostorder(t->lchild);
		recPostorder(t->rchild);
		printf("%c", t->item);
	}
}


非遞迴前序、中序遍歷演算法(c語言實現)

/**
 * 非遞迴前序遍歷
 */
void preorderTraverse(btree *t)
{
	btree *p = t;

	// 初始化棧
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			printf("%c", p->item);
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			p = p->rchild;
		}
	}
}

/**
 * 非遞迴中序遍歷
 */
void inorderTraverse(btree *t)
{
	btree *p = t;

	// 初始化棧
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			printf("%c", p->item);
			p = p->rchild;
		}
	}
}

非遞迴後序遍歷演算法(c語言實現)

演算法思想:

  1. 首先,也是找到最左邊的葉子結點並把路上遇到的節點依次入棧
  2. 然後,彈出棧頂元素(該元素為最左邊的葉子),判斷(1)它是否有右節點(2)如果有右節點,是否被訪問過。如果滿足(1)有右節點並且(2)右節點沒有訪問過,說明這是後序遍歷的相對根節點,因此需要將這個節點再次入棧,並且它的右節點入棧,然後重新執行第一步。否則,就訪問該節點,並且設定pre為此節點,同時把將遍歷節點附空值,訪問進入無限迴圈

演算法程式碼:

/**
 * 非遞迴後序遍歷
 */
void postTraverse(btree *t)
{
	btree *p, *pre;
	p = t;
	pre = NULL;

	// 初始化棧
	stack *s = (stack *)malloc(sizeof(stack));
	s->top = 0;

	while (p || s->top > 0) {
		if (p) {
			s->db[s->top ++] = p;
			p = p->lchild;
		} else {
			p = s->db[-- s->top];
			if (p->rchild != NULL && p->rchild != pre) { // p為相對根節點
				s->db[s->top ++] = p;
				p = p->rchild;
			} else {
				printf("%c", p->item);
				pre = p;
				p = NULL;
			}
		}
	}
}

注意:

嚴蔚敏的<<資料結構>>上有一段話很經典,摘錄如下:”從二叉樹遍歷的定義可知,三種遍歷演算法之不同處僅在於訪問根節點和遍歷左、右子樹的先後關係。如果在演算法中暫且抹去和遞迴無關的visit語句,則三個遍歷演算法完全相同。因此,從遞迴執行過程的角度來看,前序、中序、後序遍歷也完全相同。“ 這段話給我們的提示就是,前序、中序、後序遍歷的演算法相同,只是printf()語句位置而已。

根據前序序列、中序序列構建二叉樹

函式定義

bt rebuildTree(char *pre, char *in, int len);

引數: * pre:前序遍歷結果的字串陣列 * in:中序遍歷結果的字串陣列 len : 樹的長度 例如: 前序遍歷結果: a b c d e f g h 中序遍歷結果: c b e d f a g h

演算法思想

  1. 遞迴思想,遞迴的終止條件是樹的長度len == 0
  2. 在中序遍歷的陣列中找到前序陣列的第一個字元,記錄在中序陣列中的位置index.如果找不到,說明前序遍歷陣列和中序遍歷陣列有問題,提示錯誤資訊,退出程式即可;找到index後,新建一個二叉樹節點t,t->item = *pre,然後遞迴的求t的左孩子和有孩子
  3. 遞迴的左孩子:void rebuildTree(pre + 1, in, index)
  4. 遞迴的右孩子:void rebuildTree(pre + (index + 1), in + (index + 1), len - (index + 1))

實現程式碼(c語言版)

/**
 * Description:根據前序和中序構建二叉樹
 */
bt rebuildTree(char *pre, char *in, int len)
{
	bt t;
	if(len <= 0)
	{ 
		//遞迴終止
		t = NULL;
	}else
	{ 
		//遞迴主體
		int index = 0;
		
		while(index < len && *(pre) != *(in + index))
		{
			index ++;
		}
		
		if(index >= len)
		{
			printf("前序遍歷或者中序遍歷陣列有問題!\n");
			exit(-1);
		}
		
		t = (struct bintree *)malloc(sizeof(struct bintree));
		t->item = *pre;
		t->lchild = rebuildTree(pre + 1, in, index);
		t->rchild = rebuildTree(pre + (index + 1), in + (index + 1), len - (index + 1));
	}
	return t;
}

根據中序序列、後序序列構建二叉樹

函式定義

/**
 * 中序、後序序列構建二叉樹
 */
btree* rebuildTree(char *order, char *post, int len);

演算法思想

中序序列:C、B、E、D、F、A、H、G、J、I 後序序列:C、E、F、D、B、H、J、I、G、A 遞迴思路:
  1. 根據後序遍歷的特點,知道後序遍歷最後一個節點為根節點,即為A
  2. 觀察中序遍歷,A左側CBEDF為A左子樹節點,A後側HGJI為A右子樹節點
  3. 然後遞迴的構建A的左子樹和後子樹

實現程式碼(c程式碼)

/**
 * 根據中序和後序序列構建二叉樹
 * (ps:昨晚參加阿里筆試,等到最後說可以免筆試直接面試,今天估計還是要根據學校篩選,哈哈,為了這點
 * 也得參加阿里筆試,不能讓自己的學校受到鄙視)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int n;

typedef struct btree {
	struct btree *lchild;
	struct btree *rchild;
	char data;
} btree;

/**
 * 中序、後序序列構建二叉樹
 */
btree* rebuildTree(char *order, char *post, int len)
{
	btree *t;

	if (len <= 0) {
		return NULL;
	} else {
		int index = 0;

		while (index < len && *(post + len - 1) != *(order + index)) {
			index ++;
		}	

		t = (btree *)malloc(sizeof(btree));
		t->data = *(order + index);
		t->lchild = rebuildTree(order, post, index);
		t->rchild = rebuildTree(order + index + 1, post + index, len - (index + 1));
	}

	return t;
}

/**
 * 前序遍歷二叉樹
 */
void preTraverse(btree *t)
{
	if (t) {
		printf("%c ", t->data);
		preTraverse(t->lchild);
		preTraverse(t->rchild);
	}
}

int main(void)
{
	int i;
	char *post, *order;
	btree *t;

	while (scanf("%d", &n) != EOF) {
		post = (char *)malloc(n);
		order = (char *)malloc(n);
		
		getchar();
		for (i = 0; i < n; i ++)
			scanf("%c", order + i);

		getchar();
		for (i = 0; i < n; i ++)
			scanf("%c", post + i);

		t = rebuildTree(order, post, n);

		preTraverse(t);
		printf("\n");

		free(post);
		free(order);

	}

	return 0;
}


遞迴清理二叉樹

/**
 * 清理二叉樹
 */
void cleanBtree(btree *t)
{
	if (t) {
		cleanBtree(t->lchild);
		cleanBtree(t->rchild);
		free(t);
	}
}


後記

2012年今天是最後一天了,哈哈,終於把二叉樹該掌握的部分都掌握了,還是不錯的,期待新的一年2013年有更多的收穫,2013年可能又是我人生髮生抉擇和變化的一年,我依然會堅持自己的價值觀,踏踏實實的走下去,現在我學會最多的就是堅持,堅忍,光說不做是沒有的,寫程式如此,做人亦如此! 今天是2013年7月23日,重寫了部分二叉樹操作的程式碼,對指標的掌握和對資料結構的掌握更加熟練,希望校招一切順利,自己加油!