Definition
K+R
- 树(tree ),有且仅有一个根结点
- 根(root )
- 路径(path )
- 父结点,子结点
- 边(edge ), ⟨k,k′⟩
- 兄弟结点
- 树叶(leaf )
- 结点的度(degree )
- 祖先(ancestor )
- 子孙
- 层数(level ),根结点的层数为 0
- 有序树(ordered tree )
- 二叉树(binary tree ),度为2且严格区分左右结点的有序树
- 森林(forest )
森林与二叉树的等价转换
森林到二叉树
左子右兄原则
- 将兄弟结点都连起来
- 将父结点和除了第一个子结点的其他子结点之间的连线断开
二叉树到森林
- 断开所有父结点和右子结点
- 连接所有父结点和左子结点的右子结点以及右子结点的右子结点…
抽象数据类型
结点
template<class T>
class TreeNode{
public:
TreeNode(const <T>& value);
virtual ~TreeNode();
T Value();
TreeNode*<T> LeftMostChild();
TreeNode*<T> RightSibling();
bool IsLeaf();
void SetValue(T value);
void SetChild(TreeNode<T>* leftchild);
void SetSibling(TreeNode<T>* rightsibling);
};
树
template <T>
class Tree{
Tree();
virtual ~Tree();
bool isEmpty();
TreeNode<T>* GetRoot(TreeNode<T>* rootValue);
TreeNode<T>* Parent(TreeNode<T>* current);
TreeNode<T>* PreSibling(TreeNode<T>* current);
TreeNode
void CreateRoot(const T& rootValue);
void RootFirstTraverse(TreeNode<T>* root);
void RootLastTraverse(TreeNode<T>* root);
void WidthTraverse(TreeNode<T>* root);
void DelSubTree(TreeNode<T>* subroot);
};
周游
- 先根、后根
- 先根周游和该树对应的二叉树的中序周游等价
先根周游代码
//for循环
template<T>
void Tree<T>::RootFirstTraverse(TreeNode<T>* root)
{
if(root == NULL)
return;
Visit(root);
for(TreeNode<T>* n = root->LeftMostChild();n != NULL;n = root->RightSibling())
RootFirstTraverse(n);
}
//while循环
template<T>
void Tree<T>::RootFirstTraverse(TreeNode<T>* root)
{
while(root != NULL)
{
Visit(root);
RootFirstTraverse(root->LeftMostChild());
root = root->RightSibling()
}
}
后根周游代码
//for循环
template<T>
void Tree<T>::RootLastTraverse(TreeNode<T>* root)
{
if(root == NULL)
return;
for(TreeNode<T>* n = root.LeftMostChild();n != NULL;n = root.RightSibling())
RootLastTraverse(n);
Visit(root);
}
//while循环
template<T>
void Tree<T>::RootFirstTraverse(TreeNode<T>* root)
{
while(root != NULL)
{
RootFirstTraverse(root->LeftMostChild());
Visit(root);
root = root->RightSibling();
}
}
广度优先周游
template<T>
void Tree<T>::WidthTraverse(TreeNode<T>* root)
{
using std::queue;
queue<TreeNode<T>*> Q;
while(root != NULL) //把森林的根结点加入队列
{
Q.push(root);
root = root->RightSibling();
}
while(!Q.empty()) //广搜
{
TreeNode<T>* current = Q.front();
Q.pop();
Visit(n);
TreeNode<T>* next = current->LeftMostChild();
while(next != NULL)
{
Q.push(next);
next = next->RightSibling();
}
}
}
链式存储结构
“子结点表”表示法
- 结点有三个域:值+指向父结点的指针域+指向子结点表的指针域
- 子结点表是一个链表,其中每个结点有两个域:当前的代表的树结点的指针域+指向下一个子结点的指针域
静态“左子右兄”表示法
- 每个结点有4个域:值+指向父结点的指针域+指向第一个子结点的指针域+指向右侧兄弟结点的指针域
- 空间效率更高,且每个结点的存储空间固定
动态表示法
- 为每个结点分配可变的存储空间
- 每个结点有不确定个域:值+子结点个数n+指向n个子结点的指针域
动态“左子右兄”二叉链表表示法
代码
template<class T>
class TreeNode{
private: //增加私有数据成员
T m_Value;
TreeNode<T>* pChild;
TreeNode<T>* pSibling;
public:
TreeNode(const <T>& value);
virtual ~TreeNode();
T Value();
TreeNode*<T> LeftMostChild();
TreeNode*<T> RightSibling();
bool IsLeaf();
void SetValue(T value);
void SetChild(TreeNode<T>* leftchild);
void SetSibling(TreeNode<T>* rightsibling);
}
template<T>
bool TreeNode<T>::IsLeaf()
{
if(pChild == NULL)
return true;
ruturn
false;
}
template<T>
void TreeNode<T>::SetValue(const T& value)
{
m_Value = value;
}
template<T>
void TreeNode<T>::SetChild(TreeNode<T>* leftchild)
{
pChild = leftchild;
}
父指针表示法
父指针表示法(parent pointer)
- 结点用数组存储,每个结点有两个域,值+指向父结点的指针域
- 寻找父结点的时间复杂度是
O(1)
并查集
- 并查集,不相交的子集构成的特殊集合
- 基本操作:Find, Union
- 一种基本数据类型,用于求解等价类问题
划分等价类的过程
- 初始情况,每个元素都是一个独立等价类
- 每次读入关系集
R
中的一个偶对
⟨x,y⟩ ,判断 x,y 所属的等价类是否相等。如果不相等,那么把其中一个等价类并到另一个里面,再把这个等价类置空。 - 完成关系对的读入之后,剩下的所有非空子集就是所有的等价类
用父指针表示法实现划分等价类
- 一个森林中的子树代表初始时的独立等价类
- 判断 x,y 是否在同一个等价类中,分别找到两个结点所在的树的根,根不相同则不在同一个树中
- 把一个树的根接到另一个树的根上就实现了合并的操作
重量权衡合并规则
- 上述父指针表示法实现划分等价类的最坏情况是最后成了一个单链,树的深度 O(n)
- 为了防止这种情况,用“重量权衡合并规则”加以改进
- 合并之前先判断两个树的结点个数,让个数少的树根接到个数多的树根上。树的深度 O(logn) ,Find操作需要 O(logn) 时间
代码实现
template<T>
class ParTreeNode{
private:
T value;
ParTreeNode<T>* parent;
int nCount;
public:
ParTreeNode();
virtual ~ParTreeNode(){};
T getValue();
ParTreeNode<T>* getParent();
int getCount();
void setValue(const T& val);
void setParent(ParTreeNode<T>* par)
void setCount(const int con);
};
template<T>
ParTreeNode<T>::ParTreeNode()
{
parent = NULL;
nCount = 1;
}
template<T>
T ParTreeNode<T>::getValue()
{
return value;
}
template<T>
ParTreeNode<T>* ParTreeNode<T>::getParent()
{
return parent;
}
template<T>
int ParTreeNode<T>::getCount()
{
return nCount;
}
template<T>
void ParTreeNode<T>::setValue(const T& val)
{
value = val;
}
template<T>
void ParTreeNode<T>::setParent(ParTreeNode<T>* par)
{
parent = par;
}
template<T>
void ParTreeNode<T>::setCount(const int count)
{
nCount = count;
}
template<T>
class ParTree{
public:
ParTreeNode<T>* array;
int Size;
ParTree<T> (const int size);
virtual ~ParTree<T> ();
ParTreeNode<T>* Find(ParTreeNode<T>* node);
void Union(int i, int j);
bool Different(int i,int j);
};
template<T>
ParTree<T>::ParTree(const int size)
{
array = new ParTreeNode[size];
Size = size;
}
template<T>
ParTree<T>::~ParTree()
{
delete array;
}
template<T>
ParTreeNode ParTree<T>::Find(ParTreeNode<T>* node)
{
ParTreeNode<T>* pointer = node;
while(pointer->parent != NULL)
{
pointer = pointer->parent;
}
return pointer;
}
template<T>
bool ParTree<T>::Different(int i,int j)
{
return Find(&array[i]) = ParTree::Find(&array[j]);
}
template<T>
void ParTree<T>::Union(int i,int j)
{
ParTreeNode<T>* pointeri = ParTree::Find(&array[i]);
ParTreeNode<T>* pointerj = ParTree::Find(&array[j]);
if(pointeri != pointerj)
{
if(pointeri->getCount() <= pointerj->getCount())
{
pointeri->setParent(pointerj);
pointerj->setCount(pointeri->getCount()+pointerj->getCount());
}
else
{
pointerj->setParent(pointeri);
pointeri->setCount(pointeri->getCount()+pointerj->getCount());
}
}
}
路径压缩算法
- 在实现Find函数的同时将路径进行压缩。不仅返回根结点,还把该结点的所有祖先结点都变成根结点的子结点。
template<T>
ParTreeNode<T>* ParTree<T>::FindPC(ParTreeNode<T>* node)
{
if(node->getParent() == NULL)
return node;
node->setParent(FindPC(node->getParent())); //递归
return node->getParent();
}
Thanks to Wang Tengjiao