『數據結構』二叉樹的構建和遍歷

二叉樹是我們比較熟悉的一個數據結構,遍歷更是二叉樹的最基本的一種操作,所謂遍歷二叉樹,就是按照一定的規則和順序走遍二叉樹的所有結點,使每一個結點都被訪問一次,而且只被訪問一次。由於二叉樹是非線性結構,因此,樹的遍歷實質上是將二叉樹的各個結點轉換成爲一個線性序列來表示。

二叉樹的遍歷方式
設LDR分別表示遍歷左子樹、訪問根節點和遍歷右子樹,則對一棵樹的遍歷有三種情況

  • DLR(前序遍歷):先訪問根節點,再遍歷左子樹,最後遍歷右子樹。
  • LDR(中序遍歷):先遍歷左子樹,再訪問根節點,最後遍歷右子樹。
  • LRD(後序遍歷):先遍歷左子樹,再遍歷右子樹,最後訪問根節點。

下面我們來看一例子,來看一下下面這棵樹的前中後序遍歷結果
在這裏插入圖片描述

  • 前序遍歷:A B D E C F G
  • 中序遍歷:D B E A G C G
  • 後序遍歷:D E B F G C A

下面我們就以上面這棵樹爲例介紹二叉樹的構建和遍歷,首先,我們先來定義以下樹中結點的結構

/*---結點中元素類型---*/
typedef char ElementType;

/*---樹結點---*/
typedef struct Node {
	ElementType _val;
	struct Node* _left;
	struct Node* _right;
} Node;

首先,我們先寫三個遞歸遍歷樹的代碼以便驗證構建出來的樹是否正確

/*---前序遍歷(遞歸)---*/
void preTraverse(Node* root) {
	if (root == NULL) {
		return;
	}

	printf("%c ", root->_val);
	preTraverse(root->_left);
	preTraverse(root->_right);
}
/*---中序遍歷(遞歸)---*/
void inTraverse(Node* root) {
	if (root == NULL) {
		return;
	}

	inTraverse(root->_left);
	printf("%c ", root->_val);
	inTraverse(root->_right);
}
/*---後序遍歷(遞歸)---*/
void postTraverse(Node* root) {
	if (root == NULL) {
		return;
	}

	postTraverse(root->_left);
	postTraverse(root->_right);
	printf("%c ", root->_val);
}

下面,我們根據這棵樹的前序序列在內存中構建出這棵樹

/*---前序構建樹---*/
Node* buildTreeByPre(ElementType pre[], int size, int* used) {
	if (size == 0) {
		*used = 0;
		return NULL;
	}

	if (pre[0] == '#') {
		*used = 1;
		return NULL;
	}

	Node* root = (Node*)malloc(sizeof(Node));
	root->_val = pre[0];

	int left_used;
	root->_left = buildTreeByPre(pre + 1, size - 1, &left_used);

	int right_used;
	root->_right = buildTreeByPre(pre + 1 + left_used, 
		size - 1 - left_used, &right_used);

	*used = left_used + right_used + 1;

	return root;
}

這裏,我們寫個測試代碼,看看這個函數是否能夠完成我們的需求

void BinaryTreeTest() {
	// 注意這裏的前序序列需要考慮左右子樹爲空的情況
	// 例如D的左右子樹爲空,所有其後是兩個#表示空
	char* pre = "ABD##E##CF##G";
	int size = strlen(pre);
	int used = 0;
	Node* root = buildTreeByPre(pre, size, &used);

	// 前序遍歷(遞歸)
	printf("The preorder is: \t");
	preTraverse(root);
	// 中序遍歷(遞歸)
	printf("\nThe inorder is: \t");
	inTraverse(root);
	// 後序遍歷(遞歸)
	printf("\nThe postorder is: \t");
	postTraverse(root);
	printf("\n");
}

運行結果如下
在這裏插入圖片描述
可以看到,和我們前面自己寫的前中後序完全一致,說明根據前序序列構建樹的代碼是正確的


雖然,上面根據一個前序序列就可以構建樹,但是它需要將前面葉子結點的左右空孩子補爲#,下面我們看一下不需要補空的構建方式

  • 根據前序和中序構建樹
  • 根據中序和後序構建樹

我們看到根據前序和中序、中序和後序都可以構建樹,那麼根據前序和後序能不能構建樹呢

  • 答案是不行的。因爲前序和中序、中序和後序都有中序,我們知道中序的特點是左子樹在根結點的左邊,右子樹在根結點的右邊
  • 根據這個特點,我們可以根據在前序或後序中拿到的結點值去中序中找根結點所在下標,以此來將中序序列拆分爲左右子樹,依次拆分下去就可以構建二叉樹。

下面,我們來看一下,如何根據前序和中序構建二叉樹,代碼如下

/*---查找元素在數組中的下標---*/
int eleFind(ElementType in[], ElementType root_val, int size) {
	for (int i = 0; i < size; ++i) {
		if (in[i] == root_val) {
			return i;
		}
	}
	return -1;
}
/*---前序和中序構建二叉樹---*/
Node* buildTreeByPreIn(ElementType pre[], ElementType in[], int size) {
	if (size == 0) {
		return NULL;
	}

	ElementType root_val = pre[0];
	int left_size = eleFind(in, root_val, size);

	Node* root = (Node*)malloc(sizeof(Node));
	root->_val = root_val;

	root->_left = buildTreeByPreIn(pre + 1, in, left_size);
	root->_right = buildTreeByPreIn(pre + 1 + left_size, 
		in + 1 + left_size, size - 1 - left_size);

	return root;
}

同樣的,我們寫一個測試代碼

void BinaryTreeTest() {
	// 前序
	char* pre = "ABDECFG";
	// 中序
	char* in = "DBEAFCG";
	Node* root = buildTreeByPreIn(pre, in, strlen(pre));

	// 前序遍歷(遞歸)
	printf("The preorder is: \t");
	preTraverse(root);
	// 中序遍歷(遞歸)
	printf("\nThe inorder is: \t");
	inTraverse(root);
	// 後序遍歷(遞歸)
	printf("\nThe postorder is: \t");
	postTraverse(root);
	printf("\n");
}

運行結果如下
在這裏插入圖片描述
結果沒問題,代碼是對的


下面,再看一下根據中序和後序構建二叉樹的代碼

/*---查找元素在數組中的下標---*/
int eleFind(ElementType in[], ElementType root_val, int size) {
	for (int i = 0; i < size; ++i) {
		if (in[i] == root_val) {
			return i;
		}
	}
	return -1;
}
/*---中序和後序構建二叉樹---*/
Node* buildTreeByInPost(ElementType in[], ElementType post[], int size) {
	if (size == 0) {
		return NULL;
	}

	ElementType root_val = post[size - 1];
	int left_size = eleFind(in, root_val, size);

	Node* root = (Node*)malloc(sizeof(Node));
	root->_val = root_val;

	root->_left = buildTreeByInPost(in, post, left_size);
	root->_right = buildTreeByInPost(in + 1 + left_size, 
		post + left_size, size - 1 - left_size);

	return root;
}

最後,我們來看一下二叉樹前中後序遍歷的非遞歸實現方式

/*---前序遍歷(非遞歸)-- - */
void preTarverseNoRecursion(Node* root) {
	std::stack<std::pair<Node*, bool>> s;
	s.push(std::make_pair(root, false));
	bool visited;
	while (!s.empty()) {
		root = s.top().first;
		visited = s.top().second;
		s.pop();
		if (root == NULL) {
			continue;
		}
		if (visited) {
			printf("%c ", root->_val);
		} else {
			s.push(std::make_pair(root->_right, false));
			s.push(std::make_pair(root->_left, false));
			s.push(std::make_pair(root, true));
		}
	}
}

同樣的,我們寫一個測試代碼,使用根據中序和後序構建的二叉樹,分別使用遞歸和非遞歸進行前序遍歷

void BinaryTreeTest() {
	// 中序
	char* in = "DBEAFCG";
	// 後序
	char* post = "DEBFGCA"
	Node* root = buildTreeByInPost(pre, in, strlen(pre));

	// 前序遍歷(遞歸)
	printf("The recursion preorder is: \t");
	preTraverse(root);
	// 前序遍歷(非遞歸)
	printf("\nThe Unrecursion preorder is: \t");
	preTarverseNoRecursion(root);
	printf("\n");
}

運行結果如下
在這裏插入圖片描述


/*---中序遍歷(非遞歸)-- - */
void inTraverseNoRecursion(Node* root) {
	std::stack<std::pair<Node*, bool>> s;
	s.push(std::make_pair(root, false));
	bool visited;
	while (!s.empty()) {
		root = s.top().first;
		visited = s.top().second;
		s.pop();
		if (root == NULL) {
			continue;
		}
		if (visited) {
			printf("%c ", root->_val);
		} else {
			s.push(std::make_pair(root->_right, false));
			s.push(std::make_pair(root, true));
			s.push(std::make_pair(root->_left, false));
		}
	}
}

同樣的,我們寫一個測試代碼,我們使用前序構建的二叉樹對其使用遞歸和非遞歸的方式進行中序遍歷

void BinaryTreeTest() {
	char* pre = "ABD##E##CF##G";
	int size = strlen(pre);
	int used = 0;
	Node* root = buildTreeByPre(pre, size, &used);
	
	// 中序遍歷(遞歸)
	printf("The recursion inorder is: \t");
	inTraverse(root);
	// 中序遍歷(非遞歸)
	printf("\nThe Unrecursion inorder is: \t");
	inTraverseNoRecursion(root);
	printf("\n");
}

運行結果如下
在這裏插入圖片描述


/*---後序遍歷(非遞歸)-- - */
void postTraverseNoRecursion(Node* root) {
	std::stack<std::pair<Node*, bool>> s;
	s.push(std::make_pair(root, false));
	bool visited;
	while (!s.empty()) {
		root = s.top().first;
		visited = s.top().second;
		s.pop();
		if (root == NULL) {
			continue;
		}
		if (visited) {
			printf("%c ", root->_val);
		} else {
			s.push(std::make_pair(root, true));
			s.push(std::make_pair(root->_right, false));
			s.push(std::make_pair(root->_left, false));
		}
	}
}

同樣的,我們寫一個測試代碼,我們使用前序構建的二叉樹對其使用遞歸和非遞歸的方式進行後序遍歷

void BinaryTreeTest() {
	char* pre = "ABD##E##CF##G";
	int size = strlen(pre);
	int used = 0;
	Node* root = buildTreeByPre(pre, size, &used);

	// 後序遍歷(遞歸)
	printf("The recursion postorder is: \t");
	postTraverse(root);
	// 後序遍歷(非遞歸)
	printf("\nThe Unrecursion postorder is: \t");
	postTraverseNoRecursion(root);
	printf("\n");
}

運行結果如下
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章