二叉查找樹的Morris遍歷算法

二叉查找樹的遍歷方法有多種,遞歸實現,利用棧實現,線索樹實現,這幾種遍歷方法,其時間複雜度都爲O(n),而空間複雜度遞歸和棧爲O(h),線索樹需要額外的標識位來表明是線索還是節點指針,空間複雜度爲O(n),當節點數量非常大時,樹高h = log(n)仍然較大,有沒有其他的遍歷算法,其空間效率更高呢?這就是Morris算法,其時間複雜度爲O(n),空間複雜度做到了O(1)。這是較其他幾種遍歷方法最不同的地方。

其算法的思想有點類似線索樹,只不過線索樹中的後繼指針是一直存儲在節點中,而morris算法中也有後繼指針,但卻是臨時的,即遍歷的過程中生成臨時後繼指針,節點遍歷過後檢測到其右子節點指針爲後繼指針刪除該指針,恢復爲空指針。

中序遍歷

中序遍歷算法描述如下:

1. 如果當前節點的左孩子爲空,則輸出當前節點並將其右孩子作爲當前節點。

2. 如果當前節點的左孩子不爲空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。

   a) 如果前驅節點的右孩子爲空,將它的右孩子設置爲當前節點。當前節點更新爲當前節點的左孩子。

   b) 如果前驅節點的右孩子爲當前節點,將它的右孩子重新設爲空(恢復樹的形狀)。輸出當前節點。當前節點更新爲當前節點的右孩子。

3. 重複以上1、2直到當前節點爲空。


中序遍歷Morris算法實現如下:

template<class T>
void BST<T>::morrisInorder() {
	Node<T>* p = root;
	Node<T>* tmp;
	while (p != NULL) {
		if (p->left == 0) {		// 如果當前節點的左孩子爲空,打印當前節點,然後進入右孩子
			visit(p);
			p = p->right;
		} else {
			tmp = p->left;		// 根據當前節點,找到其前序節點(左子樹最右節點),然後進入當前節點的左孩子。
			while (tmp->right != 0 && tmp->right != p)
				tmp = tmp->right;
			if (tmp->right == NULL) {	// 如果前序節點的右孩子是空,那麼把前序節點的右孩子指向當前節點
				tmp->right = p;
				p = p->left;
			} else {		// 如果當前節點的前序節點其右孩子指向了它本身,那麼把前序節點的右孩子設置爲空,打印當前節點,然後進入右孩子。
				visit(p);
				tmp->right = 0;
				p = p->right;
			}
		}
	}
} 

前序遍歷

前序遍歷算法描述如下:

1. 如果當前節點的左孩子爲空,則輸出當前節點並將其右孩子作爲當前節點。

2. 如果當前節點的左孩子不爲空,在當前節點的左子樹中找到當前節點在中序遍歷下的前驅節點。

   a) 如果前驅節點的右孩子爲空,將它的右孩子設置爲當前節點。輸出當前節點(在這裏輸出,這是與中序遍歷唯一一點不同)。當前節點更新爲當前節點的左孩子。

   b) 如果前驅節點的右孩子爲當前節點,將它的右孩子重新設爲空。當前節點更新爲當前節點的右孩子。

3. 重複以上1、2直到當前節點爲空。


前序遍歷Morris算法實現代碼如下:


template<class T>
void BST<T>::morrisPreorder() {
	Node<T>* p = root;
	Node<T>* tmp;
	while (p != NULL) {
		if (p->left == NULL) {
			visit(p);
			p = p->right;
		} else {
			tmp = p->left;
			while (tmp->right != NULL && tmp->right != p)
				tmp = tmp->right;
			if (tmp->right == NULL) {
				visit(p);
				tmp->right = p;
				p = p->left;
			} else {
				tmp->right = NULL;
				p = p->right;
			}
		}
	}
}

後序遍歷

後序遍歷這裏不再細述。

完整代碼見bstree.h

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