二叉查找树的Morris遍历算法

1,338 阅读3分钟

二叉查找树的遍历方法有多种,递归实现,利用栈实现,线索树实现,这几种遍历方法,其时间复杂度都为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