图片和转换步骤来自这里
本文主要描述具体实现

用一种略微老土的话描述:

二叉树:每一节点最多有2个子节点,左边的叫左节点,右边的叫右节点,自己叫根节点。

树:每个节点的子节点数量不受限制。

森林:由若干个树构成的整体。

完了。

所以在你回忆完二叉树老生常谈的四种遍历后,又有那么一丁丁想要进军普通树的欲望的话,想想每个树节点应该怎么定义(毕竟想要转换成一个东西,好歹应该先弄清它里面是如何存数据的)。

树节点

每个树节点都有若干个类型相同的子节点,可以利用数组存储。所以我们的树节点有了它应该有的样子,像这样:

template<class T>
class TreeNode
{
public:TreeNode(const T& element, size_t pSize = 0) :m_pData(element),m_pSize(pSize){m_pNext = new TreeNode<T>*[m_pSize];}void setSize(size_t pSize){m_pSize = pSize;delete []m_pNext;m_pNext = new TreeNode<T>*[m_pSize];}T m_pData;size_t m_pSize;TreeNode<T> **m_pNext;
};

因此,每次申请一个树节点指针时至少应该告诉它存储的数据,子节点大小可以在弄清楚有多少个子节点之后利用setSize()函数设置。

完成树节点的定义后,树的类也就没什么特别的,毕竟很少会遇见像是让你在树中插入删除一个节点这种变态的问题,那是(更特别的)二叉树做的事情。唯一需要的可能就一个输出函数(好歹检验一下转换的对不对噻?),其他个性化功能自己添加就好,重点放在二者转换上。

template<class T>
class Tree
{
public:Tree(TreeNode<T> *pRoot = NULL); //构造函数~Tree();//析构函数void levelPrint() const; //层次输出
private:TreeNode<T> *m_pRoot; //根节点size_t m_pSize; //节点数量
};

简单提一句,二叉树节点包括两个节点指针(左右),其余的没什么区别。

二叉树->树

步骤1:
若某个节点X的左孩子存在,则将这个左孩子的右孩子节点,右孩子的右孩子节点,右孩子的右孩子的右孩子节点…,都作为节点X的孩子。将节点X与这些右孩子用线连接起来。
步骤2:
删除原二叉树中所有节点与其右孩子节点的连线(不包括根节点的右节点,右节点的右节点…….,因为树会劈叉啦~)。
步骤3:
层次调整。

首先应该明确的是,
1. 我们应该对二叉树的每一个节点都进行像步骤1那样的操作。
2. 对于步骤二,只需在构建树节点时不将原二叉树的某个根节点的右节点加到这个根节点的子节点数组(next数组)中即可。
3. 需要有个变量记录根节点,用于返回。

你可以试着带着这三步转换一棵二叉树(图片上的例子也不错),兴许在转换的过程中,你会发现比较好的方法来对每一个节点都做步骤1操作。
如果实在太懒,那就接着读吧(-__-)b

第一步,
从二叉树的根节点开始,目的是申请一个树节点,然后对它的next数组赋值。
所以无疑先计算它的子节点个数(查找个数在二叉树中进行),左节点算一个,然后不断查找右节点,直到为NULL。
(注:pNode是此时二叉树子树的根节点)

BinaryTreeNode<T> *pLeftNode = pNode->leftNode;
size_t pCnt = 0;
while(pLeftNode)
{++pCnt;pLeftNode = pLeftNode->rightNode;
}

第二步,
申请一个树节点指针作为这个位置子树的根节点,同时对next数组赋值(赋值的过程和计算子节点个数的过程类似,都是不断查找右节点,遇见一个二叉树的右节点就申请一个树节点):
(注:parentNode是此时树中子树的根节点)

TreeNode<T> *parentNode = new TreeNode<T>(pNode->m_pData, pCnt);
pLeftNode = pNode->leftNode;
pCnt = 0;
while(pLeftNode)
{TreeNode<T> *pChildNode = new TreeNode<T>(pLeftNode->m_pData);parentNode->m_PNext[pCnt++] = pChildNode;pLeftNode = pLeftNode->rightNode;
}

一个树节点申请完毕,同时也另它有了pCnt个子节点。

第三步,
对parentNode节点的所有孩子做同样的事情(从根节点开始想,根节点任务完成了,该是它小弟们的show time了)。

再回忆一下“同样的事情”:
步骤1:
若某个节点X的左孩子存在,则将这个左孩子的右孩子节点,右孩子的右孩子节点,右孩子的右孩子的右孩子节点…,都作为节点X的孩子。将节点X与这些右孩子用线连接起来

哎呀!感觉遇到了些熟悉的字眼呢,或许递归可以解决这个问题(话说第一次接触递归还是在汉诺塔……)!

既然决定使用递归了,就需要考虑如何在两层递归之间衔接。还记得刚才的代码吗,根节点和它的子节点是在同一层申请的,这显然不符合我们的认知(子节点都申请好了,在下一层调用时作为根节点又申请了一次)。想想应该怎么办。

既然不能在同一层申请,那就将根节点放到上一层申请喽(似乎只有这两种可能性,不会有人会想先申请子节点然后才申请根节点吧……)。

既然如此,我们将树中子树的根节点(用于赋值它的next数组)和二叉树中子树的根节点(用于确定next数组大小)作为递归函数的参数。还记得树节点中那个setSize()函数吗,它好像派上用场了!

别忘了树子树的根节点(parentNode)在上一层递归中已经申请了内存,这层递归是用来申请它的孩子们的。

template<class T>
void binaryTreeToTree(TreeNode<T> *parentNode, BinaryTreeNode<T> *pNode)
{//确定子节点数量BinaryTreeNode<T> *pLeftNode = pNode->leftNode;size_t pCnt = 0;while(pLeftNode){++pCnt;pLeftNode = pLeftNode->rightNode;}//第一次调用递归函数,parentNode是NULL(树种没有根节点)//记录根节点,改变子节点数量if(parentNode == NULL){parentNode = new TreeNode<T>(pNode->m_pData, pCnt);m_pRoot = parentNode; //m_pRoot,用于返回根节点}parentNode->setSize(pCnt);//为next数组赋值pLeftNode = pNode->leftNode;pCnt = 0;while(pLeftNode){TreeNode<T> *pChildNode = new TreeNode<T>(pLeftNode->m_pData);parentNode->m_pNext[pCnt++] = pChildNode;pLeftNode = pLeftNode->rightNode;}//递归调用pLeftNode = pNode->LeftNode;for(int i = 0; i < pCnt; ++i){binaryTreeToTree(parentNode->m_pNext[i], pLeftNode);pLeftNode = pLeftNode->rightNode;}
}

基本功能实现后总是需要对细节进行加工的,上述代码没有对二叉树根节点的右节点进行处理(看步骤2,下面,在下面呢)
步骤2:
删除原二叉树中所有节点与其右孩子节点的连线(不包括根节点的右节点,右节点的右节点…….,因为树会劈叉啦~)。
或许你应该思考一下应该怎么做,不过也没什么特别的(希望看完我说的之后你会这么觉得)。

每层递归函数处理子节点之前先判断参数中二叉树子树根节点是否满足步骤2括号中的情况,如果满足,同时又存在右节点,pCnt加一,然后申请参数中树的子树根节点子节点的时候多申请一个用于存放二叉树子树根节点的右节点
可能有点绕口(因为实在是担心会弄混树节点和二叉树节点)。不过更新一下上述代码还是有必要的。

(注:m_pBinaryRoot表示二叉树的根节点)

template<class T>
void binaryTreeToTree(TreeNode<T> *parentNode, BinaryTreeNode<T> *pNode)
{//确定子节点数量BinaryTreeNode<T> *pLeftNode = pNode->leftNode;size_t pCnt = 0;while(pLeftNode){++pCnt;pLeftNode = pLeftNode->rightNode;}//处理步骤2括号中的情况BinaryTreeNode<T> *targetNode = m_pBinaryRoot<T>;bool isRightNode = false;while(targetNode){if(targetNode == pNode && pNode->rightNode != NULL){isRightNode = true;break;}targetNode = targetNode->rightNode;}if(isRightNode)++pCnt;//第一次调用递归函数,parentNode是NULL(树种没有根节点)//记录根节点,改变子节点数量if(parentNode == NULL){parentNode = new TreeNode<T>(pNode->m_pData, pCnt);m_pRoot<T> = parentNode; //m_pRoot,用于返回根节点}parentNode->setSize(pCnt);//为next数组赋值pLeftNode = pNode->leftNode;pCnt = 0;while(pLeftNode){TreeNode<T> *pChildNode = new TreeNode<T>(pLeftNode->m_pData);parentNode->m_pNext[pCnt++] = pChildNode;pLeftNode = pLeftNode->rightNode;}//处理步骤2括号中情况if(isRightNode){TreeNode<T> *pChildNode = new TreeNode<T>(pNode->rightNode->m_pData);parentNode->m_pNext[pCnt++] = pChildNode;}//递归调用pLeftNode = pNode->LeftNode;for(int i = 0; i < pCnt; ++i){binaryTreeToTree(parentNode->m_pNext[i], pLeftNode);if(i == pCnt - 1 && isRightNode) //处理步骤2括号中情况pLeftNode = pNode->rightNode;elsepLeftNode = pLeftNode->rightNode;}
}

功能基本实现完成,为了结构清晰,另外一个函数用于调用这个递归函数

template<class T>
BinaryTreeNode<T> *m_pBinaryTree = NULL;template<class T>
TreeNode<T> *m_pRoot = NULL;template<class T>
TreeNode<T>* binaryTreeToTree(BinaryTreeNode<T> *pRoot)
{m_pBinaryRoot<T> = pRoot;binaryTreeToTree<T>(NULL, pRoot);return m_pRoot<T>;
}

树->二叉树

步骤1:
在所有兄弟节点之间添加连线。
步骤2:
树中的每一个节点,只保留它与第一个孩子节点的连线,删除它与其他孩子节点之间的连线。
步骤3:
层次调整。

受益于树节点结构,对于某个节点来说,它的所有兄弟节点和它在同一个数组中,这就省得花时间去弄明白它的兄弟在哪,过的怎么样,是谁,叫什么名字,长得好不好看。。。。

另外我们可以认为,兄弟之间添加的那些线都是指向右节点的线。所以根据上面的经验,处理完某个根节点X后,将X的每一个孩子都作为新的根节点,做和X同样的事情。

第一步:
从树的根节点开始,构建一个二叉树节点,使其左节点是树根的第一个孩子节点。
(注:pNode是树中此时子树的根节点)

BinaryTreeNode<T> *pLeftNode = new BinaryTreeNode(pNode->m_pNext[0]->m_pData);
parentNode->leftNode = pLeftNode; //保留它与第一个孩子的连线(步骤2)

第二步:
让根节点的左孩子节点的右指针指向它的下一个兄弟,一直指到最后一个兄弟。

size_t pCnt = pNode->m_pSize;
for(int i = 1; i < pCnt; ++i)
{BinaryTreeNode<T> *pRightNode = new BinaryTreeNode(pNode->m_pNext[i]->m_pData);pLeftNode->rightNode = pRightNode;pLeftNode = pRightNode;
}

第三步:
将树中子树的根节点的每一个孩子作为新的根节点,对它的孩子们做同样的事情(兄弟之间连线的事情啦)。
根据前面的经验,递归函数的参数是二叉树子树的根节点和树子树的根节点。
需要注意的是,二叉树子树的根节点(parentNode)在上一层递归中已经申请了内存,这层递归是用来申请它的左孩子,以及左孩子的右孩子,左孩子的右孩子的右孩子…的。

template<class T>
void treeToBinaryTree(BinaryTreeNode<T> *parentNode, TreeNode<T> *pNode)
{//记录根节点if(parentNode == NULL){parentNode = new BinaryTreeNode<T>(pNode->m_pData);m_pRoot<T> = parentNode; //m_pRoot保存二叉树根节点,用于返回。}size_t pCnt = pNode->m_pSize;if(pCnt == 0)return;//保留与第一个孩子节点的连线BinaryTreeNode<T> *pLeftNode = new BinaryTreeNode(pNode->m_pNext[0]->m_pData);parentNode->leftNode = pLeftNode;//将所有兄弟连在一起(这里将兄弟当作右节点)for(int i = 1; i < pCnt; ++i){BinaryTreeNode<T> *pRightNode = new BinaryTreeNode(pNode->m_pNext[i]->m_pData);pLeftNode->rightNode = pRightNode;pLeftNode = pRightNode;}//递归调用pLeftNode = parentNode->leftNode;for(int i = 0; i < pCnt; ++i){treeToBinaryTree(pLeftNode, pNode->m_pNext[i]);pLeftNode = pLeftNode->rightNode;}
}

和上面一样,增加一个函数调用这个递归函数。

template<class T>
BinaryTreeNode<T>* m_pRoot = NULL;template<class T>
BinaryTreeNode<T>* treeToBinaryTree(const Tree<T> &pRoot)
{treeToBinaryTree(NULL, pRoot.root());return m_pRoot<T>;
}

二叉树->森林

步骤1:
从根节点开始,若右孩子存在,则把与右孩子节点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有根节点都没有右节点。
步骤2:
将每棵分离后的二叉树转化成树。

将二叉树拆开,会出现很多个二叉树,可以考虑用vector来存储这些二叉树的根节点,然后对vector中的每一个二叉树进行转换,将树指针存在另一个vector中用于返回。

vector<BinaryTreeNode<T> *> pBinaryVector;
vector<Tree<T> *> pForestVector;
//拆分二叉树
BinaryTreeNode<T> *targetNode = pRoot.root();
BinaryTreeNode<T> *nextNode = NULL;
whlie(targetNode != NULL)
{nextNode = targetNode->rightNode;targetNode->rightNode = NULL;pBinaryVector.push_back(targetNode);targetNode = nextNode;
}//将每个二叉树都转化成树
for(int i = 0; i < pBinaryVector.size(); ++i)
{Tree<T>* pTree = new Tree<T>(binaryTreeToTree<T>(m_pBinaryVector.at(i)));pForestVector.push_back(pTree);
}

森林->二叉树

步骤1:
将每棵树转换成二叉树
步骤2:
第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根节点作为前一棵二叉树根节点的右孩子。

和二叉树转森林相似(实际上是步骤正好相反),将每棵树转换成二叉树,然后将这些二叉树连起来。

vector<Tree<T> *> pForestVector;
BinaryTreeNode<T>* m_pRoot = treeToBinaryTree(pForestVector.at(0));
BinaryTreeNode<T>* curRoot = m_pRoot;
size_t pSize = pForestVector.at(0)->size();for(int i = 1; i < pForestVector.size(); ++i)
{BinaryTreeNode<T> *pNextRoot = treeToBinaryTree(pForestVector.at(i));pSize += pForestVector.at(i)->size();curRoot ->rightNode = pNextRoot;curRoot = pNext;
}BinaryTree<T> *binaryTree = new BinaryTree<T>(m_pRoot, pSize);
return binaryTree;

问题和改进

大体的实现已经完成,剩下的就是细节加工,并且代码仍然存在安全隐患。

  1. 二叉树转换森林的过程中返回的是存储着树指针的vector,我们在自己设计的函数中申请大量节点,然后返回给用户,用户并不知道这些节点是怎么来的,所以更不会手动将这些节点内存释放。这就造成了内存泄漏。

    解决方案:用vector存储智能指针,智能指针管理每一个树。这样当程序结束时,智能指针自动调用管理对象的析构函数,而树的析构函数刚好可以释放我们申请的大量节点的内存。

  2. 森林转换二叉树的过程中返回的二叉树指针也是利用动态内存申请的,用户不会自己释放,同样造成内存泄漏。

    解决方案:返回智能指针,让智能指针管理二叉树对象,二叉树的析构函数释放申请的内存。

完整代码

数据结构-----二叉树,树,森林之间的转换相关推荐

  1. 二叉树与树、森林之间的转换

    转载于:https://www.cnblogs.com/fazero/p/5024002.html

  2. 数据结构--二叉树与森林记事本

    基本性质 5条基本性质 基操 #include <bits/stdc++.h> using namespace std; #define MAXSIZE 100 typedef struc ...

  3. 数据结构——树、森林和二叉树之间的转换

    摘自大佬博客http://www.cnblogs.com/zhuyf87/archive/2012/11/04/2753950.html 树转换为二叉树 (1)加线.在所有兄弟结点之间加一条连线. ( ...

  4. 树,森林,二叉树之间的转换

    树.森林和二叉树的转换 树转换为二叉树 (1)加线.在所有兄弟结点之间加一条连线. (2)去线.树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线. (3)层次调整.以树的 ...

  5. 树,森林与二叉树之间的转换

    1.树转换为二叉树 由于二叉树是有序的,为了避免混淆,对于无序树,我们约定树中的每个结点的孩子结点按从左到右的顺序进行编号. 将树转换成二叉树的步骤是: (1)加线.就是在所有兄弟结点之间加一条连线: ...

  6. 树/二叉树/森林之间的相互转换 与遍历

    森林就是多棵树的集合,但是森林也可以只有一棵树,二叉树是一种特殊的树,固定的度为2,这是基本前情提要- 树常见的存储方式有三种: (1)双亲表示法 仅用定义一个结点对象,一个数组,代码定义如下: ty ...

  7. 数据结构-树,二叉树,森林

    树,二叉树,森林 王卓老师的数据结构课程笔记 树和二叉树 定义 结点之间有分支,具有层次关系 是n个结点的有限集. 若n = 0,称为空树: 若n > 0,则它满足如下两个条件: 有且仅有一个特 ...

  8. 数据结构与算法分析-二叉树,树和森林

    二叉树,树和森林 考试内容 二叉树.树和森林的定义 树: 树(Tree)是n(n>=0)个结点的有限集,它或为空树(n= 0); 或为非空树,对于非空树T: 有且仅有一个称之为根的结点: 除根结 ...

  9. 理论基础 —— 二叉树 —— 树、森林、二叉树的转换

    [概述] 从树的孩子兄弟表示法和二叉树的二叉链表表示可以看出,树的孩子兄弟表示法实质上是二叉树的二叉链表存储形式,第一个孩子指针和右兄弟指针分别相当于二叉链表的左孩子指针和右孩子指针. 因此,从物理结 ...

最新文章

  1. 这么简单的目标检测赛题,竟然设置260万现金奖!
  2. iOS SDK: Send E-mail In-App
  3. (原创)让mongodb的secondary支持读操作
  4. 中控指纹采集器开发指纹识别项目(说明)
  5. 虚幻4毛发系统_虚幻引擎复活!苹果与Epic对决,有哪些游戏险些中枪?
  6. mysql增删改查扩展_MySQL(增删改查补充)
  7. 创建动态链接库时设置导出函数的方法
  8. css 类别选择器 并集,CSS常用选择器
  9. mysql 去除空格
  10. 3D造型软件:Rhino 7 for Mac
  11. Android – ListView 中添加按钮,动态删除添加ItemView的操作
  12. 黑群晖XPEnoboot for DSM 5.2-5967.1
  13. ARM嵌入式系统开发指南-设计和优化系统软件(译作连载)
  14. POJ 1080 Gene
  15. 挨踢人生路--记我的10年18家工作经历 续 .转
  16. Unity|一键复制log日志|小技巧
  17. 怎样设置二维码的尺寸
  18. Docker容器化实战第三课 dockerfile介绍、容器安全与监控讲解
  19. 【时间序列分析】12.MA(q)模型
  20. 吴军的谷歌方法论|周末互动|如何避免成为耍小聪明的人

热门文章

  1. Java黑皮书课后题第2章:*2.20(金融应用:计算利息)编写程序,读取余额和年利率百分比,打印下个月的利息
  2. Groovy 设计模式 -- 借贷
  3. php数组array_push()和array_pop()以及array_shift()函数
  4. 深入剖析C++多态、VPTR指针、虚函数表
  5. version robot
  6. 成功者五大因素 奸的好人-笔记
  7. HDU 3081 Marriage Match II【并查集+二分图最大匹配】
  8. WEB前端技术趋势图示-JS库
  9. String案例 获取一个字符串在另一个字符串中出现的次数(两种方法)
  10. switch 根据键盘录入成绩 显示分数及判断等级(第一次)