《算法问题实战策略》-chaper21-树的实现和遍历

时间:2020-12-06 06:20:17

这一章节开始介绍一个数据结构中的一个基本概念——树。

我们从数据结构的解读来解释树结构的重要性,现实世界的数据除了最基本的线性结构(我们常用队列、数组和链表等结构表征),还有一个重要的特性——层级结构需要我们去表征,例如世界杯的对阵表、遗传系谱图等等,这时候我们基于对现实世界的抽象,会很自然的理解为什么会有树这样一个数据结构。

而树这种数据结构也是能够分类的,我们将每个节点记录某种抽象的概念或者具象的事物,这样用来表征一种从属关系,我们称其为抽象型数据结构的树。或者将每个节点储存一些数据,基于这棵树我们能够完成检索查找之类的操作,我们称其为检索型数据结构的树。

关于树的基本的术语和其中的基本概念,这里不再累述,读者可以通过查找离散数学或者图论的资料了解。

树的实现:

既然了解到了树存在的必要性和重要性,下面我们就应该去关注怎样实现一棵树了。在《啊哈算法》中,作者介绍了用一维数组实现的二叉树,这是基于二叉树一些非常特殊的形式,但是对于更加一般的情况的多叉树呢?我们需要采用更加一般的方法。对于树中的每一个节点,我们很关注如下集合很有价值的数据:它的父节点?它的子节点?存在该节点的数据?关于它的父节点和子节点,其实就是需要我们表征一种“联系”,即通过该节点,我们能够轻松的找到和它关联的父节点和子节点,我们很容易便可以想到指针。而基于一维数组实现的二叉树,就是二叉树的基本性质,通过为节点标号从而实现了指针的作用。

通过上面的分析,我们能够定义树节点的结构体。

struct Treenode

{

    <data>

    TreeNode* parent;

    vector<TreeNode*> children;

};

树的遍历:

树本质上也是一种图,因此这里采用基本的深搜就可以完成对树的遍历。

void printLabels(TreeNode* root)

{

    cout<< root-> <data><<endl;

    for(int i = ;i < root->chilren.size();i++)

          printLabels(root->children[i]);

}

基于对树的遍历,我们同时可以完成对树的高度的计算。

int height(TreeNode* root)

{

    int h = ;

    for(int i = ;i < root->children.size();i++)

          h = max(h,+height(root->children[i]));

    return h;

}

关于一个树结构的一个简单建模:

Q:中世纪时,为了更好的首尾保护更多领地,城市和要塞之间都具有多层城墙。偏执狂领主建筑的Strawgoh要塞达到了这中间城模式的极致。下图是这种要塞的结构图。那么现在给出一个要塞的图,两个城市之间没有门只能通过*翻越城墙,请问再给出的城墙图中,从一个城市到另一个城市最多翻阅多少城墙?

Input:

将每个城市看成一个圆,然后程序会给出n个圆的圆心和半径用来表示这个城墙结构。

用树建立起来的解题模型:这道问题从原始的图上来看并不好直观的得到它会与树联系起来,但是我们进一步的看这个图,是否像集合论当中的维恩图,而维恩图就是典型的层级结构,这正与树结构能够表征的东西呼应起来。如果基于树结构看这个问题,我们就能够将问题化约成,求解树结构的最长路径,也就是树的深度。这个在前面介绍“树的实现和遍历”当中曾经简单的介绍过它的实现方法。

显然整体的思路有了,下面我们面临的最大问题就是,如何基于题目给出的数据,建立这样一棵树结构?

建树的方法:首先基于一个基本的递归过程,也可以说成一个深度优先建树的过程。我们从根节点开始,假设我们已经有了一个判断两个城市是否是父子关系的函数,我们能够写出如下的过程:

TreeNode* getTree(int root)

{

    TreeNode* ret = new TreeNode();//指向该节点的指针

    for(int i = ;i < n;i++)

          if(isChild(root,i))

               ret -> children.push_bcak(getTree(ch));//树节点数据中储存儿子结点的向量表

    return ret;

}

那么好了,下面我们面临的问题就变成了如何设计判断两个城市之间是否是父子关系的isChild()函数了。如何判断两个点在树结构中是否是父子关系呢?通过观察我们能够发现,必须两个区域是直接的包含关系,即对于圆B,A,如果B内含与A并且不存在这样一个圆C使得圆C内含与A且B内含于C。清楚了这一点,在拿到两个区域判断其双方是否是父子关系的时候,首先我们当然要利用简单的几何知识要判断他们是否是包含关系,随后我们通过穷举的方法来遍历剩下的所有区域由此来判断是否存着这样一个上文中提到的区域C,即我们能够写出如下的简单代码。

 int n , y[],x[],radius[];

bool inclose(int a , int b)//判断圆b是否在圆a的内部

{

      double dis;//圆心距

      dis = sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));

    if(radius[b]-radius[a] >= dis)

          return  true;

    else

          return false;

}

bool isChild(int parent , int child)

{

     if(!inclose(parent,child)) return false;

       int flag = ;

        for(int i = ;i < n;i++)

        {

             if(i == parent || i == child)  continue;

             if(inclose(parent,i) && inclose(i,child))  {flag = ;break;}

        }

        if(flag)  return true;

        else      return false;

}