数据结构-树

一、一般树
在这里我们将要讨论一个非线性的数据结构.树结构是一个重要的突破在数据组织中。允许我们运用一些算法更快比起来运用线性型的数据结构,例如列表,容器和序列。树会提供一个自然的组织对于数据,在文件系统中普遍存在,以及图形用户界面,数据库,网站地址和其他的计算机系统。树是一种继承关系,一些对象处在“下面”,另一些对象在“上面”。事实上,这个主要的说法来自树的数据结构来自于成员树,有父对象,孩子节点等等。
*树的定义和属性
一个树是一个抽象类型并且存储元素。这个顶部的元素,在一颗树中每个元素都有一个父类或者0个或更多的孩子节点。一个树通常是可视的在起内部的圆或者是矩形,并且在父类和子类用直线链接起来。我们能够称它为树的顶部元素,画在树的最高部,而其他元素链接在他的下面。
*正式的树的定义
正常来讲,我们定义这个树T设置节点存储这些元素在父-子关系上:
(1)如果T是非空,他有一个特别的节点,称之为根T,没有父节点.
(2)每个节点V对于来自不同的根有一个唯一的父节点w;每一个父节点w也是一个孩子节点w。
根据我们的定义,一棵树可以为空,意味着它不能有任何节点。这将要允许我们定义这个树的循环。例如树T是一个空的或者由节点r构成,称之为根T。
*其他节点的关系
两个相同的父节点是兄弟节点。如果v没有孩子节点,那么A节点是外部的。A节点V是一个外部的如果他有一个或者更多的孩子节点。外部节点也称为叶子节点。
例:
在大多数操作系统,文件是一个继承的组织嵌套的目录,这个组织将要呈现给用户一个树结构的形式。更多的是,这个树的外部节点将要联系这个目录并且这个外部节点联系这些文件。在这个unix和linux操作系统中,这个根树被称之为根目录,并且由符号“/.“表示。
*树的功能
树的抽象数据类型存储这个树节点。节点是我们实现的内部问题,我们并不允许直接访问它。树的每个节点都与一个位置对象关联,该对象提供公共访问节点.讨论我们公共接口功能,我们用符号p来改变这个参数功能是一个位置而非一个节点。并且给定这个链接在两个对象,我们经常模糊他们之间的区别,并且用这个”位置“和”节点“间接改变这个树。树结构的真正特性是访问他临界点的能力。给定这个位置树T,我们将要定义:
p.parent();//返回这个p的父节点,如果p是父节点,将要返回一个错误
p.children();//返回一个孩子节点
p.isRoot();//如果p是一个根节点的话返回true否则为false;
p.isExternal();//如果p是叶子节点返回true,反之为false;
如果一个树是有序的,这个列将要提供这个p.children()提供链接p对象。如果p是一个外部节点,然后p.children()返回一个空列。树本身要提供这些功能。
第一点是大小和是否为空,这些标准功能我们定义这些其他类型我们所看到的。根节点长生一个列,包含全部的树节点。
size();//返回树元素的个数;
empty();//如果树为空,则返回true;
root();//返回树的根节点的位置;
positions();//返回全部树节点的位置。

*C++树的接口
让我们来看一个树型抽象数据类型的c++接口。我们开始展示一个c++数据接口在类中,呈现出一个树的位置。以下代码:
template <typename E>
class Position<E>
{
    E& operator*();    //得到一个元素
    Position parent() const; //得到父类元素
    PositionList children() const; //得到孩子节点
    bool isRoot() const;       //是否为根节点
    bool isExternal() const; //是否为叶子节点
}
接下来,我们将要展示一个c++树形借口。这是一个简单的事例,我们先忽视错误的过程;因此我们不会阐述抛出的异常。
template <typename E>
class Tree<E>
{
public:
    class Position;
    class PositionList;
public:
    int size() const;
    bool empty() const;
    Position root() const;
    PositionList positions() const;
}

在这里我们并没有正式的定义接口类PositionList,我们可能假设给定一个标准列ADT在我们的代码例子中。我们假设这个PositionList是一个STL对象类型的补充,或者更具体来讲std::list<Position>,特别的,我们将要假设这个PositionList提供给一个指针类型,接下来我们将要继续阐述。

一般树的链接结构
我们很自然意识到树是一个链接结构的数据类型,在这里我们代表每个T节点的位置对象。有提供一个节点元素,连接到它的父节点,并且一些列表或数组来存储
连接到这些节点的孩子。如果p是T的根节点,这个父领域是空的。并且我们存储一个根节点T和内部变量的节点数。
以下是n个节点关联树的功能,让Cp来表示这个孩子节点p的数量。空间利用用O(n)。
Operation        Time
isRoot,isExternal       O(1)
parent            O(1)         
children(p)        O(Cp)
size,empty        O(1)
root            O(1)
positions         O(n)

*树遍历算法
接下来,我们展示这个遍历对于树通过这个树的抽象类型功能。
*深度和高度
P是树T的节点。这个p的深度是p父类的个数,不包括P本身。p节点的深度也能定义如下:
(1)如果P是一个根节点,然后深度p为0;
(2)否则,这个p的深度是加上父的深度并且+1
基于上述的定义,这个递归算法depth(T,P)将要计算节点的深度并供及一个p位置,通过调用本身进行递归在这个父类P,并且加1的返回值。
Algorithm Depth(T,p):
if(p.isRoot()) then
return 0;
else
return 1+Depth(T,p.parent())
*以下是C++补充,算法展示:
Algorithm Depthm(const Tree& T,const Position& p)
{
    if(p.isRoot())
       return 0;
    else
           return 1+Depthm(T,p.parent())
}

算法Depth(T,p)的运行时间为O(dp)阐述了这个树T的节点P的深度,因为这个算法有表现出一个常时间的递归步奏对于每一个P的祖类。因此,最糟糕的事情。这Depth算法的运行时间为O(n)时间,这个n是这个整个T树下的节点数,虽然这样的运行时间是一个输入的函数,它是更准确的表征参数的DP的运行时间,因为它往往是远小于n.

这个节点p的高度在树中也被如下定义。
 (1)如果p是一个子节点的,他的高度被定义为0;
 (2)这个P的高度是1+p的子节点的最大高度。
如下:
Algorithm Height(T)
h=0
for each p ∈ T.posistion() do
if p.isExternal() then
    h = max(0,depth(T,p))
return h;
*以下是C++补充,算法展示:
Algorithm Height(const Tree& T)
{
    int h =0;
    PositionList node = T.Positions(); //list of all element
    for(Iterator q = node.begin();q != node.end();++q)
    {
        if(q->isExternal)
        h = max(h,Depth(T,*q));
    }
    return h;
}
不幸的是,算法Height并不是有效率的。Height调用算法depth对于每一个外部p节点,这个运行的时间是O(n+Ep(1+dp)),下面是depth2展示一个算法。
Algorithm height2(T,p)
if p.isExternal() then
return 0
else
h=0
for each q ∈ p.children do
h = max(h,depth(T,q))
return 1+h;
*以下是C++补充,算法展示:
Algorithm height2(const Tree& T,const Position& p)
{
    if(p.isExternal())
    return 0;        //leaf has height equal to 0
    else
    int h = 0;
    PositionList ch = p.children();   //list of children
    for(Iterator q = ch.begin();q != ch.end();++q)
    {
    h = max(h.height2(T,*q));
    return 1+h;
    }
}


突然想起二叉搜索树,于是乎搭了个小架子,已测过无误咯。

#ifndef TREE_H
#define TREE_H
struct _NXTree
{
    int data;
    struct _NXTree *lchildren;
    struct _NXTree *rchildren;
}
class Tree
{
public:
    Tree();
    struct _NXTree m_tree;
    List<_NXTree*> NodeTree;
    void initialTree(struct _NXTree *p,int a[],int n);
    bool insertTreeEle(struct _NXTree *p,int ele);
}
#endif
Tree::Tree()
{
}
void Tree::initilalTree(struct _NXTree *p,int a[],int n)
{
    p = 0;
    for(int i = 0;i<n;i++)
    {
       insertTreeEle(p,a[i]);
    }
}
bool Tree::insertTreeEle(struct _NXTree *p,int ele)
{
    if(p == 0)
    {
        p = new _NXTree;
        p->data = ele;
        p->lchildren = 0;
        p->rchildren = 0;
        NodeTree.append(p);
    }
    if(p->data == ele)
        return false;
    if(p->data>ele)
        return insertTreeEle((_NXTree*)p->lchildren,ele);
                else
        return insertTreeEle((_NXTree*)p->rchildren,ele);    
}

前序遍历
一颗T树有一个“系统”的方式来进行访问所以的节点。在这章中,我们要构造一个基本的遍历树,称它为前序遍历。在下一章我们要study一个基本的遍历计划
称之为后序遍历。
前序遍历一棵树,这个T要参考第一个根节点并且这个子树的根是他的一个孩子节点来递归调用。如下所示:
Algorithm preorder(T,p):
perform the "visit" action for node p
for each child q of p do
recursively traverse the subtree rooted at q by calling preorder(T,q)
前序遍历算法是有用的对于节点的线性排序,父类必须总是在他们孩子序列的前面。这些序列有不同的应用。我们将要探索一个简单的例子。
void prerorderPrint(const Tree& T,const Position& P)
{
    cout<<*p;
    PositionList ch = p.children();
    for(Iterator q = ch.begin();q!=ch.end();++q)
    {
    cout<<"";
    preorderPrint(T,*q);
    }
}
这里有一个有趣的preorderPrint功能来输出这个树的全部表示方式。表示这个树是一个递归。如果T由单个节点P组成:
P(T) = *p;
否则,
P(T) = *p+"("+p(T1)+p(T2)+p(T3)+")",
p是一个根节点并且T1,T2....TK三子树的根在这个p孩子节点,如果T是一颗有序树。
这个定义P(T)是一颗递归的树。并且我们用“+”来表示字符串的链接。下面是对c++算法的补充:
void preorderPrint(const Tree& T,const Position& P)
{
    cout<<*P<<endl;
    Position ch = P.children();
    for(Iterator it = ch.begin();it != ch.end();++it)
    {
    if(it != ch.begin())
    preorderPrint(T,*q);
    }

}









后序遍历
另一个重要的树的遍历算法是后续遍历。这个算法与先序遍历相反,这是因为它的递归遍历这个子树的根首先在孩子的根节点,并且然后访问这个根节点。
这相似与先序遍历,然而,我们用它来解决这些特别的问题通过访问p节点。作为先序遍历,如果这个树是线性的,我们将要递归调用他的孩子节点p.以下
是一个小范例:
Alogorithm postorder(T,p)
    for each child q of p do
    recursively traverse the subtree rooted at q by calling postorder(T,q)
    perform the "visit" action for node p
分析这个运行后序遍历的运行时间来分析。这个总共的时间花费非递归部分花费在访问每一个孩子节点的时间。因为后续遍历树的n节点访问花费时间为O(n),
假设访问每个节点为O(1),也就是说后序遍历在时间线性内运转。
void postorderPrint(const Tree& T,const Position& P)
{
    Position ch = P.children();
    for(Iterator q = P.begin(); q != P.end();++q)
    {
    postorderPrint(T,*q);
    cout<<*p;
}
}
 



二叉树
一颗二叉树是一颗有序树在每个节点都有两个孩子节点。
1、每个节点最多有两个节点。
2、每个节点标记为左孩子节点和右孩子节点。
3、左子树节点要优于右子树节点。
子树的根在这个左孩子或者右孩子的内部节点.
一个递归二叉树定义
事实上,我们也能定义这个二叉树用这个递归方式,它由下面组成:
*节点r,称之为根节点的T并且存储元素
*二叉树,称之为T的左子树。
*二叉树,称之为T的右子树。
二叉树的抽象数据类型
在这章中,我们要介绍一个抽象的二叉树数据类型。我们早先的抽象树,每个树节点要存储一个元素并且联系每一个位置对象,来公开访问每个节点。通过载入这个操作,
这个元素要联系p位置并且访问P节点。此外,一个p节点支持如下的操作。
p.left()
返回这个左孩子节点;如果p是外部节点将要导致错误发生。
p.right()
返回这个右孩子节点,如果p是外部节点将要导致错误发生。
p.parent();
返回这个父节点,如果p是根节点,将导致错误发生。
p.isRoot()
如果p是根节点返回为真。
p.isExternal()
如果o是外部节点返回真否则为fals;
树本身要提供他自己相同的操作作为抽象树标准。调用这个位置:
size()
返回一个树的个数。
empty()
如果是空树返回真。
root()
返回一个根节点的位置;如果树为空将有错误产生。
positions()
返回树节点的全部位置。

C++二叉树接口
让我们展示一个标准的C++的二叉树借口。我们开始通过展示一个标准的c++接口对于类Position,来代表这个树的位置。它是不同于树的借口通过取代这个树的成员
功能在左子树和右子树。
template <typename E>
class Position<E>
{
    E& operator*();
    Position left() const;
    Position right() const;
    Position parent() const;
    bool isRoot() const;
    bool isExternal() const;
};

接下来,让我们使借口尽可能简单一些,创建一个二叉树,我们并不注意错误,然后我们也不声明任何抛出异常。
template <typename E>
class BinaryTree<E>
{
public:
    class Position;
    class PositionList;
public:
    int size() const;
    bool empty();
    Position root() const;//get the root
    PositionList positions() const;//list of nodes
};

*二叉树的链表结构
在本章中,我们将要补充一个二差树T作为链表结构,称它为LinkBinaryTree.我们将要使每个T的节点V在存储每个节点对象并且指向他的父节点和两个孩子节点。
简单来讲我们将要假设,每个节点不是0就是两个孩子节点。
下图我们将要展示这个链表树来表示这个二叉树。这个结构要存储树的大小,这个树节点,指向树的根节点。余下的数据结构将有一起链接的节点构成。如果T是v的根
节点,将要指向父节点为null,如果v是一个外部节点,将要指向孩子节点为null。


下面是数据结构NODE的二叉树结构。
struct Node{
    Elem elt;
    Node* par;
    Node* left;
    Node* right;
    Node():elt(),par(NULL),left(NULL),right(NULL){}//constructor
}
虽然所有的数据节点都是共有的,类Node将要在内部LinkerBinaryTree类中声明为保护部分。因此,每个节点都有一个成员变量elt,并且来联系这些节点,指向父,
左孩子,右孩子,并且关联他们。
下一步我们将要定义公共类Position。他的数据成员组成是由v节点在整个树。通过每个节点元素重载这个操作符“*”。我们将要声明这个linkBinaryTree为友元,提供
他来访问私有数据。
class Position{
private:
    Node* V;
public:
    Position(Node* _v=NULL):v(_v) {}
    Elem& operator*()
    {return v->elt;}
    Position left() const
    {return Position(v->letf);}
    Position right() const
    {return Position(v->right);}
    Position parent() const
    {return Position(v->par);}
    bool isRoot() const
    {return v->par == NULL;}
    bool isExternal() const
    {return v->left == NULL && v->right == NULL;}
    friend class LinkedBinaryTree;
};
    typedef std::list<Position> PositionList;
大多数功能类Position通过一些Node成员结构。我们包括声明类PositionList,作为STL列表的位置。这将要用来收集节点。来保证代码尽量简单。我们要检测
错误,而不是使用模板。我们将要提供一个类型定义对于基本的元素类型,调用elem。
我们表现这个主要的类LInkedBinaryTree代码,这个类成名开始插入到Node节点位置。并且声明一个公共节点和私有的数据成员。
typedef int Elm;
class LinkedBinaryTree{
protected:
public:
LinkedBinaryTree();
int size() const;
bool empty();
Position root() const;
PositionList positions() const;
void addRoot();
void expandExternal(const Position& p);
Position removeAboveExternal(const Position& p);
protected:
    void preorder(Node* v,PositionList& pl) const;
private:
    Node* _root;
    int n;
}

私有数据来自于类LinkedBinaryTree组成由指针_root到根节点和变量n,包括这些在树中的节点。此外这个抽象数据类型功能,我们有介绍这些更新的功能,addRoot,expandEXternal,以及removeAboveExternal,提供并且改变这颗树。他们有讨论我们定义的功能。我们将要介绍一个简单的结构定义以及一个成员LinkBinaryTree功能。这个
addRoot功能假设这个树为空,并且创造一个单独树根节点。
LinkBinaryTree::LinkedBinaryTree():_root(NULL),n(0){}
int LinkedBinaryTree::size() const
{return n;}
bool LinkedBinaryTree::empty() const
{return size() == 0;}
LinkedBinaryTree::Position LinkedBinaryTree::root() const
{return Position(_root);}
void LinkedBinaryTree:addRoot()
{_root = new Node;n=1;}

*二叉树的更新
我们现在用这个加入节点和删除节点。
expandExternal(p):
转变p节点来自于外部节点到一个内部节点,并且创造两个新的节点并且使他们分别为左子树节点和右子树节点,如果P是外部节点引发错误。
removeAboveExternal(p):
删除这个外部节点p并且和他的父节点q。

void LinkedBinaryTree::expandExternal(const Position& p)
{
    Node* v = p.v;
    v->left = new Node;
    v->left->par = v;
    v->right = new Node;
    v->right->par = v;
    n += 2;
}
这个removeAboveExternal(p)有展示这些代码。w联到p节点并且v为他们的父节点.我们假设这个w节点是外部并且不是根节点。这有两件事情。如果w是根节点的孩子节点,
移除w他的父节点使得w的xiongdi成为树的新根。如果不是,我们将要替代w的父节点通过w的xiongdi节点。这包括寻找w的祖父母和确定v是祖父母的左边或右边的孩子。
我们将要设置这个链接对于孩子节点到祖父节点。不链接w和v,我们要删除这些节点。最后,我们要更新这个节点数字在这个树上的。
LinkedBinaryTree::Position
LinkedBinaryTree::removeAboveExternal(const Position& p)
{
    Node* w = p.v;
    Node* v = w->par;
    Node* sib =(w ==v->left?v->right:v->left);
     if (v == root) {                                 // child of root?
       _root = sib;                                  // ...make sibling root
        sib->par = NULL;
  }
  else {
     Node* gpar = v->par;                          // w's grandparent
     if (v == gpar->left) gpar->left = sib;        // replace parent by sib
     else gpar->right = sib;
     sib-> par = gpar;
  }
     delete w;  delete v;                             // delete removed nodes
     n -= 2;                                          // two fewer nodes
     return Position(sib);
   }
}
*这个position功能,遍历这个树并且存储STL模板
LinkedBinaryTree::PositionList LinkedBinaryTree::positions() const
{
    PositionList pl;
    preorder(_root,pl);
    return PositionList(pl);
}
void LinkedBinaryTree::preorder(Node* v,PositionList& pl) const
{
    pl.pushback(Position(v));
    if(v->left != null)
    preorder(v->left,pl);
    if(v->right != null)
    preorder(v->right,pl);
}

binaryPreorder(T,p.left())
binaryPreorder(T,p.right())
*后序遍历树
Algorithm binaryPostorder(T,p)
{
     if p is an internal node then
       binaryPostorder(T,p.left)
       binaryPostorder(T,p.right)
  "visit " action for the node p
}


*有序的二叉树
一个额外的遍历方式是二叉树有序的遍历。在这个遍历中,我们要访问节点用递归方式在它的左右子树中。这个有序的遍历子树的根节点p。
Algorithm inorder(T,p)
if p is an internal node then
    inorder(T,p.left())       //遍历左子树
    perform the "visit" action for node p
if p is an internal node then
    inorder(T,p.right())    //遍历右子树


*模板函数模式
树的遍历方式在上面例子中已经介绍,都是一些有趣的面向对象的设计模式。这里并不能阻挡这个模板类和c++功能,但是这个原理是简单。这个模板功能有描述
这个总体的计算机制能够执行特定的步奏。
*templateEulerTour功能将要继续足头的子树遍历,这个功能将要调用InitResult,visitExternal,visitLeft,visitBelow,visitRight,and returnResult;
Algorithm templateEulerTour(T, p):
  r ← initResult ()
  if p.isExternal () then
     r.finalResult ← visitExternal(T,p,r)
  else
     visitLeft(T,p,r)
     r.leftResult ← templateEulerTour(T, p.left())
     visitBelow(T,p,r)
     r.rightResult ← templateEulerTour(T, p.right())
     visitRight(T,p,r)
  return returnResult(r)

c++实现
完成一个c++实现这个类EurerTour类,他们基于连接二叉树实现。我们开始定义一个局部的结构体Result并且有leftResult,rightResult,以及finalResult,
存储这个中间部分。为了避免类型长度命名,我们给出了两个类型定义,二叉树以及Position.这个数据类型是一个指针类型。我们要提供一个简单的功能,称为
初始化,设置这个指针指向存在的二叉树。这个功能属于保护结构,他们并不能直接被应用。如下:
template <typename E,typename R>
class EulerTour
{
    protected:
    struct Result{
    R leftResult;
    R rightResult;
    R finalResult;
};
    typedef BinaryTree<E> BinaryTree;
    typedef typename BinaryTree::Position Position;
protected:
    const BinaryTree* tree;
public:
    void initialize(const BinaryTree& T)
    {tree = &T;}
protected:
    int eulerTour(const BinaryTree& T) const;
    virtual void visitExternal(const Position& p, Result& r) const {}
     virtual void visitLeft(const Position& p, Result& r) const {}
    virtual void visitBelow(const Position& p, Result& r) const {}
     virtual void visitRight(const Position& p, Result& r) const {}
     Result initResult() const { return Result(); }
      int result(const Result& r) const { return r.finalResult; }
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值