您的位置:首页 > 其它

树及树的算法(1) —— 二叉树

2012-05-31 22:55 169 查看
从现在开始,我们研究算法理论中最重要的树的实现问题,其中包括抽象树、N叉树及二叉树,这里重点研究二叉树。树状图是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多个子结点;每一个子结点只有一个父结点;没有前驱的结点为根结点;除了根结点外,每个子结点可以分为m个不相交的子树。



树的总体架构如图所示:





1 二叉树

二叉树是树的数据结构中,最重要,运用场合最多的技术,其总体架构如下:





首先来看抽象树的实现,抽象树是一个容器,各子树的根作为其元素:



class Tree : public virtual Container
{
	class Iter;
public:
	virtual Object& Key () const = 0;
	virtual Tree& Subtree (unsigned int) const = 0;
	virtual bool IsEmpty () const = 0;
	virtual bool IsLeaf () const = 0;
	virtual unsigned int Degree () const = 0;
	virtual int Height () const;
	virtual void DepthFirstTraversal (PrePostVisitor& visitor) const;
	virtual void BreadthFirstTraversal (Visitor& visitor) const;

	Iterator& NewIterator() const;
	void Accept (Visitor& visitor) const;
};




我们看到,抽象树封装了所有树实现的通用元素:

函数Key:返回根节点对象的引用。

函数Subtree:返回给定树的第i课子树。

函数IsEmpty:判断这棵树是否是空树。

函数IsLeaf:判断这个节点是不是叶子节点。

函数Degree:返回根节点的度,即包含子树的个数,叶子节点度为0。

函数Height:返回树的高度。

函数DepthFirstTraversal:树的深度优先遍历。

函数BreadthFirstTraversal:树的广度优先遍历。



2 树的遍历

(1)深度优先遍历



//深度优先遍历
void Tree::DepthFirstTraversal( PrePostVisitor& visitor ) const
{
	if (visitor.IsDone ())
	{
		return;
	}
	
	if (!IsEmpty ())
	{
		//前序遍历
		visitor.PreVisit (Key ());
		for (unsigned int i = 0; i < Degree (); ++i)
		{
			//深度优先遍历子树
			Subtree(i).DepthFirstTraversal (visitor);
		}
		//后序遍历
		visitor.PostVisit (Key ());
	}
}




深度优先遍历是递归地访问根极其子树方式来遍历整个树,算法很简单,举例子:

如果是先序遍历,深度优先遍历首先访问H,然后递归遍历D和G,到D时又递归遍历A和C,到C时又递归遍历B,到G时又递归遍历F,到F时又递归遍历E。所以先序深度优先遍历最后的结果是:HDACBGFE。




如果是后序遍历,则首先递归遍历D和G,再H;到D时又递归遍历A、C再D;到C时又递归遍历B,再C。到G时先F再G,到F时先E再F,最后后续深度优先遍历的最后结果是:ABCDEFGH。



注意,这里的抽象树,还没有到二叉树的那个级别,所以暂不考虑中序遍历。



我们的先序、中序和后续遍历是通过访问者模式来实现的:

class PuttingVisitor : public Visitor
{
	ostream& stream;
	bool comma;
public:
	PuttingVisitor (ostream& s) : stream (s), comma (false)
	{}
	void Visit (Object& object)
	{
		if (comma)
			stream << ", ";
		stream << object;
		comma = true;
	}
    bool IsDone () const
    {
        return comma;
    }
};

//遍历基类
class PrePostVisitor : public Visitor
{
public:
	virtual void PreVisit (Object&) {}
	virtual void Visit (Object&) {}
	virtual void PostVisit (Object&) {}
};

//前序遍历类
class PreOrder : public PrePostVisitor
{
	Visitor& visitor;

public:
	PreOrder (Visitor& v) : visitor (v)
	{}
	void PreVisit (Object& object)
	{ 
		visitor.Visit (object); 
	}
};

//中序遍历类
class InOrder : public PrePostVisitor
{
	Visitor& visitor;

public:
	InOrder (Visitor& v) : visitor (v)
	{}
	void Visit (Object& object)
	{ 
		visitor.Visit (object); 
	}
};

//后序遍历类
class PostOrder : public PrePostVisitor
{
	Visitor& visitor;

public:
	PostOrder (Visitor& v) : visitor (v)
	{}
	void PostVisit (Object& object)
	{ 
		visitor.Visit (object); 
	}
};




(2)广度优先遍历



再来看树的广度优先算法,树的广度优先遍历是按照树的深度来访问的,并在每一层上按照从左到右的顺序访问节点:

//广度优先遍历
void Tree::BreadthFirstTraversal( Visitor& visitor ) const
{
	Queue& queue = *new QueueAsLinkedList ();
	queue.RescindOwnership ();

	if (!IsEmpty ())
	{
		queue.Enqueue (const_cast<Tree&> (*this));
	}

	while (!queue.IsEmpty () && !visitor.IsDone ())
	{
		Tree const& head =
			dynamic_cast<Tree const &> (queue.Dequeue ());

		visitor.Visit (head.Key ());
		for (unsigned int i = 0; i < head.Degree (); ++i)
		{
			Tree& child = head.Subtree (i);
			if (!child.IsEmpty ())
				queue.Enqueue (child);
		}
	}
	delete &queue;
}




广度优先算法由于是按照高度来遍历的,所以不能用递归的方式了。我们还是通过一个例子来研究这个算法:



首先高度为3的H入队列,进入循环,H出队并接受访问;随后高度为2的D和G入队;

到D时,高度为A和C的节点又入队,D接受访问;

到G时F入队,G接受访问;

到A时没有入队,A接受访问;

到C时B入队,C接受访问;

到F时E入队,F接受访问;

到B时没有入队,B接受访问;

到E时没有入队,E接受访问。

最终广度优先算法是:HDGACFBE



(3)树的迭代器



使用树的迭代器也是一种遍历的方法:



//树的迭代器
class Tree::Iter : public Iterator
{
	Tree const& tree;
	Stack& stack;
public:
	Iter (Tree const& _tree);
	~Iter ();
	void Reset ();
	bool IsDone () const;
	Object& operator * () const;
	void operator ++ ();
};

Tree::Iter::Iter( Tree const& _tree ):
tree(_tree),
stack (*new StackAsLinkedList ())
{
	stack.RescindOwnership ();
	Reset ();
}

Tree::Iter::~Iter()
{
	delete &stack; 
}

void Tree::Iter::Reset()
{
	stack.Purge ();
	if (!tree.IsEmpty ()){
		stack.Push (const_cast<Tree&> (tree));
	}
}

bool Tree::Iter::IsDone() const
{
	return stack.IsEmpty ();
}

Object& Tree::Iter::operator*() const
{
	if (!stack.IsEmpty ())
	{
		Tree const& top =
			dynamic_cast<Tree const&> (stack.Top ());
		return top.Key ();
	}
	else
	{
		return NullObject::Instance ();
	}
}

void Tree::Iter::operator++()
{
	if (!stack.IsEmpty ())
	{
		Tree const& top =
			dynamic_cast<Tree const&> (stack.Pop ());

		for (int i = top.Degree () - 1; i >= 0; --i)
		{
			Tree& subtree = top.Subtree (i);
			if (!subtree.IsEmpty ())
			{
				stack.Push (subtree);
			}
		}
	}
}




我们主要关注++操作符,这个操作符号利用栈来实现子树的遍历。对于栈的运用,在这里是一个很好的提现,希望好好学习这个思想。

3 二叉树的实现

二叉树是抽象树Tree的派生类,但也是很多重要数据结构的基类,所以我们暂时不会实现一些通用的接口:

class BinaryTree : public virtual Tree
{
protected:
	Object* key;
	BinaryTree* left;
	BinaryTree* right;
public:
	BinaryTree ();
	BinaryTree (Object& _key);
	void purge();
	~BinaryTree ();

	Object& Key () const;
	virtual BinaryTree& Left () const;
	virtual BinaryTree& Right () const;

	virtual void AttachLeft (BinaryTree&);
	virtual void AttachRight (BinaryTree&);
	virtual void AttachKey (Object&);

	virtual Object& DetachKey ();
	virtual BinaryTree& DetachLeft ();
	virtual BinaryTree& DetachRight ();

	// ...
	void DepthFirstTraversal (PrePostVisitor& visitor) const;
};

BinaryTree::BinaryTree():
key(0),
left(0),
right(0){}

BinaryTree::BinaryTree( Object& _key ):
key(&_key)
left(new BinaryTree()),
right(new BinaryTree())
{}

void BinaryTree::purge()
{
	if (!IsEmpty ())
	{
		if (IsOwner ())
			delete key;
		delete left;
		delete right;
		key = 0;
		left = 0;
		right = 0;
	}
}

BinaryTree::~BinaryTree()
{
	purge();
}

//二叉树的遍历,采用先序方式
void BinaryTree::DepthFirstTraversal( PrePostVisitor& visitor ) const
{
	if (visitor.IsDone ())
	{
		return;
	}
	
	if (!IsEmpty ())
	{
		visitor.PreVisit (*key);
		left->DepthFirstTraversal (visitor);
		visitor.Visit (*key);
		right->DepthFirstTraversal (visitor);
		visitor.PostVisit (*key);
	}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: