高级数据结构 | 创建二叉树 —递归与非递归实现:先序中序创建、中序后序创建 ...
对于使用二叉链表结构存储的二叉树,我们通常使用“递归—先序”的方式创建,并且在输入树中结点的数据时需要人为的加入‘#’以表示叶子节点(度为0的点)。
而我们知道,给定一个二叉树的中序遍历序列和其他任意一种优先深度遍历方式的序列都可以得到其完整的二叉树结构,那么我们能不能使用计算机来完成这一转化呢?
换言之,我们本章重点为:
- 使用先序和中序创建二叉树
- 使用中序和后序创建二叉树
一、使用递归创建二叉树
先序中序创建二叉树
通过先序的第一个元素可知整棵树的根结点。而对于中序遍历来说,根结点在其序列中间位置。
- 因此我们可以通过一个根节点将中序划分为两个部分,根节点之前,跟结点之后。
例如:对于序列 ABDECF(先)、DBEACF(中)来说
先:ABDECF 得到根结点 ⇒ A
中:DBEACF
对于结点A来说,这是一个树的根,也是一个树分叉的位置。 - 如果我们利用分治的思想,把左边和右边分别再次使用相同的方法递归下去。那么最终就可以完整复原树的每一个结点和上面的分叉。
实现如下:
/* 找“根”结点的下标 */
int FindIndex(const ElemType* istr, int n, ElemType x)
{
int pos = -1;
for (int i = 0; i < n; ++i)
{
if (istr[i] == x)
{
pos = i;
break;
}
}
return pos;
}
struct BtNode* CreatePI(const char* pstr, const char* istr,int n)
{
struct BtNode* s = NULL;
if (n > 0)
{
s = Buynode();
s->data = *pstr;
int pos = FindIndex(istr,n,*pstr); // 找当前规模的“根”的下标
if (pos == -1) exit(0);
s->leftchild = CreatePI(pstr + 1, istr, pos);//存储左边部分
s->rightchild = CreatePI(pstr + pos + 1, istr + pos + 1, n - pos - 1);//存储右边部分
}
return s;
}
struct BtNode* CreateTreePI(const char *pstr,const char *istr,int n)
{
if (NULL == pstr || NULL == istr || n < 1) return NULL;
return CreatePI(pstr, istr, n);
}
中序后序创建二叉树
先序和后序是两种相反的遍历方式,先序:根左右,后序:左右根。
有趣的是,我们可以通过同样的算法完成中序、后序创建二叉树的过程,只需要在把后序颠倒过来。
那么,按照后序遍历的最后一个元素我们可以得到根结点,按照根结点可以把中序划分为两段。同样的我们使用分治的思想可以很方便的完成对二叉树的创建。
实现如下:
struct BtNode* CreateIL(const char* istr, const char* lstr, int n)
{
struct BtNode* s = NULL;
if (n > 0)
{
s = Buynode();
s->data = lstr[n - 1];
int pos = FindIndex(istr, n, lstr[n - 1]); // 找叶子结点下标
if (pos == -1) exit(0);
s->leftchild = CreateIL(istr , lstr, pos);
s->rightchild = CreateIL(istr + pos + 1, lstr + pos, n - pos - 1);
}
return s;
}
struct BtNode* CreateTreeIL(const char* istr, const char* lstr, int n)
{
if (NULL == istr || NULL == lstr || n < 1) return NULL;
return CreateIL(istr, lstr, n);
}
二、非递归创建二叉树
使用递归的好处在于递归可以自动回退回上一层的状态,而我们一般把递归改非递归的思路都是引入一种新的数据结构模拟这种回退的功能。
-
先序遍历其实每一步都找的是根节点,只是第一个元素是整棵树的根节点,第二个元素是左子树的根节点,第三个元素是左子树的左子树的根节点。
-
中序遍历可以看做是每一步都在遍历叶子结点,第一个元素是最左边子树的叶子结点,第二个元素是上一层子树的叶子结点… …
先序中序遍历思路:
因此,我们可以使用先序遍历,每次沿着“根”的左子树向下遍历,直到遇到叶子结点,也就是第一个中序遍历的元素。然后我们回退至上一层,回退至与中序的第二个元素结点相同处,找到一个有分叉的“根”节点,再继续以此根的右子树向下遍历,如此循环下去最终遍历完整个树。
在此遍历的过程中,我们已经完成了对二叉树的建立。在此过程中通过保存每次遍历的结点的值与中序序列进行比较,我们可以准确的回退至上一层的根结点处,继续沿着根的另一边子树遍历。
中序后序遍历思路:
后序遍历倒置过来后,首位元素即是我们的根节点元素,第二位元素即是此“根”下右子树的最右边的叶子结点。先序在首次遍历时从左子树最左边的叶子结点处开始,刚好与后序相反。
综上,相对于先序中序的遍历,我们只需做一些小小的改动即可。从后续序列末端开始遍历,与中序序列的末端进行比较,建立的二叉树的顺序是先右子树,再左子树。
先序中序创建二叉树
非递归的先序中序创建二叉树,实现如下:
#include <stack> // 使用C++库中提供的数据结构——栈
/* 非递归 --先序中序 */
struct BtNode* NiceCreatePI(const char* pstr, const char* istr, int n)
{
std::stack<struct BtNode*> st;
bool flag = false;
int i = 0, j = 0; // i 指向先序,j 指向中序
struct BtNode* root = Buynode();
root->data = pstr[0];
st.push(root);
for (i = 1; i < n; ++i)
{
struct BtNode* s = Buynode();
s->data = pstr[i];
struct BtNode* tmp = NULL;
while (j < n - 1 && st.top()->data == istr[j]) // 判断是否遇到分叉(“根”)
{
while (!st.empty() && st.top()->data == istr[j]) // 向上找有分叉的“根”
{
tmp = st.top(); // 该结点(“根”)有右子树
st.pop();
++j;
}
if (tmp != NULL)
{
tmp->rightchild = s; // 把该结点添加至右子树
if (i == n - 1) { flag = true; break; }
st.push(s); // 入栈
s = Buynode();
s->data = pstr[++i];
}
}
if (flag) break; // 创建完成,退出
st.top()->leftchild = s;
st.push(s); // 入栈
}
return root;
}
struct BtNode* NiceCreateTreePI(const char* pstr, const char* istr, int n)
{
if (NULL == pstr || NULL == istr || n < 1) return NULL;
return NiceCreatePI(pstr, istr, n);
}
中序后序创建二叉树
非递归的中序后序创建二叉树,实现如下:
#include <stack> // 使用C++库中提供的数据结构——栈
/* 非递归 --中序后序 */
struct BtNode* NiceCreateIL(const char* istr, const char* lstr, int n)
{
std::stack<struct BtNode*> st;
bool flag = false;
int i = n, j = n; // i 指向后序,j 指向中序
struct BtNode* root = Buynode();
root->data = lstr[n];
st.push(root);
for (i = n - 1; i >= 0; --i)
{
struct BtNode* s = Buynode();
s->data = lstr[i];
struct BtNode* tmp = NULL;
while (j > 0 && st.top()->data == istr[j]) // 判断是否遇到分叉(“根”)
{
while (!st.empty() && st.top()->data == istr[j]) // 向上找有分叉的“根”
{
tmp = st.top(); // 该结点(“根”)有左子树
st.pop();
--j;
}
if (tmp != NULL)
{
tmp->leftchild = s; // 把该结点添加至左子树
if (i == 0) { flag = true; break; }
st.push(s); // 入栈
s = Buynode();
s->data = lstr[--i];
}
}
if (flag) break;
st.top()->rightchild = s;
st.push(s); // 入栈
}
return root;
}
struct BtNode* NiceCreateTreeIL(const char* istr, const char* lstr, int n)
{
if (NULL == istr || NULL == lstr || n < 1) return NULL;
return NiceCreateIL(istr, lstr, n - 1);
}
三、测试用例
输出形如 ABC##DE##F##G#H#
格式的先序序列字符串。
void strPreOrder(struct BtNode* p)
{
if (NULL != p)
{
printf("%c", p->data);
strPreOrder(p->leftchild);
strPreOrder(p->rightchild);
}
else printf("#");
}
这里主要测试五种类型的二叉树,分别是普通的二叉树、只有左子树的二叉树、只有右子树的二叉树,根节点没有右子树的二叉树、根节点左子树的二叉树。
int main()
{
//const char pstr[] = "ABCDEFGH"; //先序序列 // A
//const char istr[] = "CBEDFAGH"; //中序序列 // B G
//const char lstr[] = "CEFDBHGA"; //后序序列 // C D H
// E F
//左单 链表
//const char pstr[] = "ABCDEFGH"; //先序序列 // A
//const char istr[] = "HGFEDCBA"; //中序序列 // B
//const char lstr[] = "HGFEDCBA"; //后序序列 // C
// D
// E
// F
// G
// H
//右单 链表
//const char pstr[] = "ABCDEFGH"; //先序序列 // A
//const char istr[] = "ABCDEFGH"; //中序序列 // B
//const char lstr[] = "HGFEDCBA"; //后序序列 // C
// D
// E
// F
// G
// H
//const char pstr[] = "ABCDEFGH"; //先序序列 // A
//const char istr[] = "DCFEGHBA"; //中序序列 // B
//const char lstr[] = "DFHGECBA"; //后序序列 // C
// D E
// F G
// H
//const char pstr[] = "ABCDFE"; //先序序列 // A
//const char istr[] = "ABFDCE"; //中序序列 // B
//const char lstr[] = "FDECBA"; //后序序列 // C
// D E
// F
// {先序,中序,后序}
const char*str[][20] = {
{"ABCDEFGH","CBEDFAGH","CEFDBHGA"}, // 普通二叉树
{"ABCDEFGH","HGFEDCBA","HGFEDCBA"}, // 只有左子树,单链表结构
{"ABCDEFGH","ABCDEFGH","HGFEDCBA"}, // 只有右子树,单链表结构
{"ABCDEFGH","DCFEGHBA","DFHGECBA"}, // 根节点没有右子树
{"ABCDFE","ABFDCE","FDECBA"} // 根节点没有左子树
};
BinaryTree rootPI = NULL; // 递归先序、中序
BinaryTree rootIL = NULL; // 递归中序、后序
BinaryTree rootPI_r = NULL; // 非递归先序、中序
BinaryTree rootIL_r = NULL; // 非递归中序、后序
int len = sizeof(str) / sizeof(str[0]);
while (len--)
{
int n = strlen(*str[len]);
printf("●--第%d组测试用例:\n",len+1);
const char* pstr = str[len][0];
const char* istr = str[len][1];
const char* lstr = str[len][2];
printf("使用‘#’表示:\n");
rootPI = NiceCreateTreePI(pstr, istr, n); // 使用先序和中序创建二叉树
strPreOrder(rootPI); printf("\n");
rootIL = NiceCreateTreeIL(istr, lstr, n); // 使用中序和后序创建二叉树
strPreOrder(rootIL); printf("\n");
rootPI_r = NiceCreateTreePI(pstr, istr, n);
strPreOrder(rootPI_r); printf("\n");
rootIL_r = NiceCreateTreeIL(istr, lstr, n);
strPreOrder(rootIL_r); printf("\n\n");
printf("前序遍历:\t",""); printf("%*s中序遍历:\t", n, ""); printf("%*s后序遍历:\n", n, "");
PreOrder(rootPI); printf("\t"); InOrder(rootPI); printf("\t"); PastOrder(rootPI); printf("\n");
PreOrder(rootIL); printf("\t"); InOrder(rootIL); printf("\t"); PastOrder(rootIL); printf("\n");
PreOrder(rootPI_r); printf("\t"); InOrder(rootPI_r); printf("\t"); PastOrder(rootPI_r); printf("\n");
PreOrder(rootIL_r); printf("\t"); InOrder(rootIL_r); printf("\t"); PastOrder(rootIL_r); printf("\n");
puts("");
}
return 0;
}