以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 算法理论与分析 』  (http://bbs.xml.org.cn/list.asp?boardid=60)
----  [原创]已知先序中序序列确定二叉树的算法  (http://bbs.xml.org.cn/dispbbs.asp?boardid=60&rootid=&id=25234)


--  作者:binaryluo
--  发布时间:12/10/2005 4:57:00 PM

--  [原创]已知先序中序序列确定二叉树的算法
最近复习都忙不赢上论坛了,前天看二叉树的时候写了个知先序中序序列确定二叉树的算法,现在贴出来供大家参考。:)

算法:(类C语言描述)
#define   FALSE    0
#define   TRUE     1
typedef int Status;

Status createTree(BiTree &T, BiTNode pre[], BiTNode order[]){
          // 已知先序序列pre和中序序列order,构建一个二叉树T的非递归实现。
          // 其中涉及到的数据结构请参看清华大学《数据结构》(C语言版)

          InitStack(Stack);        //初始化栈
          for (i = 0; i < order.length; i ++) visited[ i ] = FALSE;
                                            // 初始化visited,visited用于标记order
                                            // 中的元素是否已经被访问过
          T = (BiTree *) malloc (sizeof(BiTree));
          T.data = pre[0]; T -> lchild = NULL; T -> rchild = NULL;
                                           // 生成根节点,先序pre中的第一个元素肯定是树根,
                                           // 左右孩子初始化为NULL。
          p = LocateElem(order, pre[0]);   // 在order中找到根节点所在的位置
          visited[ p ] = TRUE;                         // 标识order中的根元素已被访问过
          cur = root;                                    // cur表示当前构建的节点

          for (i = 1; i < pre.length; ) {
                p = LocateElem(order, pre[ i ]); // 定位pre[ i ]的位置
                if (p > 0 && !visited[p - 1]) {
                        // 在order中pre[ i ]的左边有元素并且未被访问过,说明有左子树存在,
                        // 生成左子树
                        cur->lchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->lchild.data = pre[i ++];
                                        // 将当前pre中的元素赋给lchild后指向下一个元素
                        cur->lchild->lchild = NULL; cur->rchild->rchild= NULL;
                        visited[ p ] = TRUE;
                        Push(Stack, cur);        // 当前节点进栈
                        cur = cur->lchild;        // 当前节点指向左孩子
                }
                else if (p < order.length - 1 && !visited[p + 1]) {
                        // 生成右子树
                        cur->lchild = NULL;   // 没有左孩子
                        cur->rchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->rchild.data = pre[i ++];
                        cur->rchild.lchild = NULL; cur->rchlid.rchild = NULL;
                        visited[ p ] = TRUE;
                        cur = cur->rchild;
                }
                else {
                        Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                }
          }
}

算法分析:
假设有n个节点,在初始化visited时(第一个for循环)赋值次数等于order的长度n,生成左右子树时(第二个for循环)外层循环为n-1次,其中定位pre[ i ]时最坏比较n次,所以基本操作次数是n+n*(n-1),时间复杂度为O(n方)。
根据这个非递归要写出递归实现很简单,只需将if-else里的不分改成递归调用就行。


--  作者:binaryluo
--  发布时间:12/18/2005 9:36:00 PM

--  
请求加精!!
--  作者:enorm
--  发布时间:12/24/2005 11:32:00 AM

--  
又想起了考研生活~~~~~~~~~~~~有意思!
--  作者:Logician
--  发布时间:12/25/2005 4:01:00 PM

--  
如果先对Pre[i]或Order[i]做一下索引排序的话,应该可以把LocateElem的复杂度降到O(logn),从而把整个算法的复杂度降到O(nlogn)?
--  作者:binaryluo
--  发布时间:12/29/2005 11:43:00 PM

--  
以下是引用Logician在2005-12-25 16:01:00的发言:
如果先对Pre[i]或Order[i]做一下索引排序的话,应该可以把LocateElem的复杂度降到O(logn),从而把整个算法的复杂度降到O(nlogn)?

您所说的索引排序是怎么回事?能否具体点?


--  作者:Logician
--  发布时间:12/30/2005 1:49:00 PM

--  
索引排序就是先为数列建立一个索引表,然后在索引表上进行排序。
举个例子:
原中序数组InOrder[n]={f,e,a,g,d,c,b,h}
那么先建立一个索引(为了清楚起见,我把它所对应的InOrder值也写在旁边):
Idx[n]={<0,f>,<1,e>,<2,a>,<3,g>,<4,d>,<5,c>,<6,b>,<7,h>}
然后以InOrder[n]为关键字,对Idx[n]进行排序。
最后Idx[n]={<2,a>,<6,b>,<5,c>,<4,d>,<1,e>,<0,f>,<3,g>}
(实际上Idx[n]只用保存{2,6,5,4,1,0,3}就可以了,后面的值可以随时在InOrder里查)。
这样排序之后,既可以在O(log n)的时间内,通过Idx数组在InOrder中查到任意需要的节点,又不会破坏原InOrder的顺序。
对照你的重构程序可以看出,上述构造能在O(log n)的时间能帮你找到你所需要的子树边界,从而把时间复杂度降到O(nlogn)。

--  作者:binaryluo
--  发布时间:12/30/2005 6:08:00 PM

--  
以下是引用Logician在2005-12-30 13:49:00的发言:
索引排序就是先为数列建立一个索引表,然后在索引表上进行排序。
举个例子:
原中序数组InOrder[n]={f,e,a,g,d,c,b,h}
那么先建立一个索引(为了清楚起见,我把它所对应的InOrder值也写在旁边):
Idx[n]={<0,f>,<1,e>,<2,a>,<3,g>,<4,d>,<5,c>,<6,b>,<7,h>}
然后以InOrder[n]为关键字,对Idx[n]进行排序。
最后Idx[n]={<2,a>,<6,b>,<5,c>,<4,d>,<1,e>,<0,f>,<3,g>}
(实际上Idx[n]只用保存{2,6,5,4,1,0,3}就可以了,后面的值可以随时在InOrder里查)。
这样排序之后,既可以在O(log n)的时间内,通过Idx数组在InOrder中查到任意需要的节点,又不会破坏原InOrder的顺序。
对照你的重构程序可以看出,上述构造能在O(log n)的时间能帮你找到你所需要的子树边界,从而把时间复杂度降到O(nlogn)。


谢谢:)
我改进下再贴出来。


--  作者:binaryluo
--  发布时间:12/30/2005 7:05:00 PM

--  
以下是引用Logician在2005-12-30 13:49:00的发言:
索引排序就是先为数列建立一个索引表,然后在索引表上进行排序。
举个例子:
原中序数组InOrder[n]={f,e,a,g,d,c,b,h}
那么先建立一个索引(为了清楚起见,我把它所对应的InOrder值也写在旁边):
Idx[n]={<0,f>,<1,e>,<2,a>,<3,g>,<4,d>,<5,c>,<6,b>,<7,h>}
然后以InOrder[n]为关键字,对Idx[n]进行排序。
最后Idx[n]={<2,a>,<6,b>,<5,c>,<4,d>,<1,e>,<0,f>,<3,g>}
(实际上Idx[n]只用保存{2,6,5,4,1,0,3}就可以了,后面的值可以随时在InOrder里查)。
这样排序之后,既可以在O(log n)的时间内,通过Idx数组在InOrder中查到任意需要的节点,又不会破坏原InOrder的顺序。
对照你的重构程序可以看出,上述构造能在O(log n)的时间能帮你找到你所需要的子树边界,从而把时间复杂度降到O(nlogn)。


刚才改的时候突然遇到一个疑问,在您上面的假设里节点的数据元素的类型是字符,如果他的元素类型不是字符,是一些结构体是不是就不行了啊??


--  作者:Logician
--  发布时间:12/31/2005 6:42:00 PM

--  
不管是什么体,总该有个种可以唯一区别两个不同元素的key吧?(不然你在重构时,万一遇到两个key相同的节点,岂不是要出错了?)
只要有唯一区别两个不同元素的key,就可以比较(实在不行就按它在计算机中的数值表示的大小排就可以了),从而就可以排序。:)
--  作者:binaryluo
--  发布时间:1/1/2006 7:38:00 PM

--  
改进后的算法:
#define   FALSE    0
#define   TRUE     1
typedef int Status;

typedef struct {
       BiTNode node;  // 原来中序中的数据元素
       int index;           // 新加的索引字段
}IndexNode, IndexList;

Status IndexSort(BiTNode order[], IndexList idx){
       for (i = 0; i < order.length; i ++){  // 构建一个索引表
             idx[ i ].node = order[ i ];
             idx[ i ].index = i;
       }

       QSort(idx, 0, L.length);  // 快速排序,需在清华数据结构274-275页算法的基础上稍加改变

       return OK;
}
Status createTree(BiTree &T, BiTNode pre[], BiTNode order[]){
          // 已知先序序列pre和中序序列order,构建一个二叉树T的非递归实现。
          // 其中涉及到的数据结构请参看清华大学《数据结构》(C语言版)

          IndexList *idx;            
          IndexSort(order, idx);  // 对order索引排序
          InitStack(Stack);        //初始化栈
          for (i = 0; i < order.length; i ++) visited[ i ] = FALSE;
                                            // 初始化visited,visited用于标记order
                                            // 中的元素是否已经被访问过
          T = (BiTree *) malloc (sizeof(BiTree));
          T.data = pre[0]; T -> lchild = NULL; T -> rchild = NULL;
                                           // 生成根节点,先序pre中的第一个元素肯定是树根,
                                           // 左右孩子初始化为NULL。
          p = LocateElem(order, pre[0]);   // 在order中找到根节点所在的位置
          visited[ p ] = TRUE;                         // 标识order中的根元素已被访问过
          cur = root;                                    // cur表示当前构建的节点

          for (i = 1; i < pre.length; ) {
                p = idx[pre[ i ].node.data - order[ 0 ].node.data].index; // 用索引在order中定位
                if (p > 0 && !visited[p - 1]) {
                        // 在order中pre[ i ]的左边有元素并且未被访问过,说明有左子树存在,
                        // 生成左子树
                        cur->lchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->lchild.data = pre[i ++];
                                        // 将当前pre中的元素赋给lchild后指向下一个元素
                        cur->lchild->lchild = NULL; cur->rchild->rchild= NULL;
                        visited[ p ] = TRUE;
                        Push(Stack, cur);        // 当前节点进栈
                        cur = cur->lchild;        // 当前节点指向左孩子
                }
                else if (p < order.length - 1 && !visited[p + 1]) {
                        // 生成右子树
                        cur->lchild = NULL;   // 没有左孩子
                        cur->rchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->rchild.data = pre[i ++];
                        cur->rchild.lchild = NULL; cur->rchlid.rchild = NULL;
                        visited[ p ] = TRUE;
                        cur = cur->rchild;
                }
                else {
                        Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                }
          }
}

改进后的算法生成左右子树时(第二个for循环)的时间复杂度降为O(n),而在进行索引排序时的时间复杂度由选择的排序算法决定,在此选择了快速排序,所以时间复杂度为O(nlogn)。总的算法时间复杂度:O(nlogn) + O(n),取O(nlogn)。但是由于要创建一个索引表,所以空间复杂度为O(n)。在此特别感谢 Logician 的提示。^_^


[此贴子已经被作者于2006-1-1 22:49:52编辑过]

--  作者:binaryluo
--  发布时间:1/5/2006 10:15:00 PM

--  [注意]错误更正!!
上述的算法在构建时候有问题,现已更正如下:

算法1:(类C语言描述)
#define   FALSE    0
#define   TRUE     1
typedef int Status;

Status createTree(BiTree &T, BiTNode pre[], BiTNode order[]){
          // 已知先序序列pre和中序序列order,构建一个二叉树T的非递归实现。
          // 其中涉及到的数据结构请参看清华大学《数据结构》(C语言版)

          InitStack(Stack);        //初始化栈
          for (i = 0; i < order.length; i ++) visited[ i ] = FALSE;
                                            // 初始化visited,visited用于标记order
                                            // 中的元素是否已经被访问过
          T = (BiTree *) malloc (sizeof(BiTree));
          T.data = pre[0]; T -> lchild = NULL; T -> rchild = NULL;
                                           // 生成根节点,先序pre中的第一个元素肯定是树根,
                                           // 左右孩子初始化为NULL。
          p = LocateElem(order, pre[0].data);   // 在order中找到根节点所在的位置
          visited[ p ] = TRUE;                         // 标识order中的根元素已被访问过
          cur = T;                                    // cur表示当前构建的节点

          for (i = 1; i < pre.length; ) {
               
                if (p > 0 && !visited[p - 1]) {
                        // 在order中pre[ i ]的左边有元素并且未被访问过,说明有左子树存在,
                        // 生成左子树
                        cur->lchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->lchild.data = pre[ i ];
                                        // 将当前pre中的元素赋给lchild后指向下一个元素
                        cur->lchild->lchild = NULL; cur->rchild->rchild= NULL;
                        p = LocateElem(order, pre[ i ++].data); // 定位pre[ i ]的位置
                        visited[ p ] = TRUE;
                        Push(Stack, cur);        // 当前节点进栈
                        cur = cur->lchild;        // 当前节点指向左孩子
                }
                else if (p < order.length && !visited[p + 1]) {
                        // 生成右子树
                        cur->lchild = NULL;   // 没有左孩子
                        cur->rchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->rchild.data = pre[ i ];
                        cur->rchild.lchild = NULL; cur->rchlid.rchild = NULL;
                        p = LocateElem(order, pre[ i ++].data); // 定位pre[ i ]的位置
                        visited[ p ] = TRUE;
                        cur = cur->rchild;
                }
                else {
                        Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                        p = LocateElem(order, cur.data); // 重新定位p的位置
                }
          }
}

算法2:(红色的部分是在原算法上改进的地方)
#define   FALSE    0
#define   TRUE     1
typedef int Status;

typedef struct {
       BiTNode node;  // 原来中序中的节点
       int index;           // 新加的索引字段
}IndexNode, IndexList;

Status IndexSort(BiTNode order[], IndexList idx){
       for (i = 0; i < order.length; i ++){  // 构建一个索引表
             idx[ i ].node = order[ i ];
             idx[ i ].index = i;
       }

       QSort(idx, 0, L.length);  // 快速排序,需在清华数据结构274-275页算法的基础上稍加改变

       return OK;
}


Status createTree(BiTree &T, BiTNode pre[], BiTNode order[]){
          // 已知先序序列pre和中序序列order,构建一个二叉树T的非递归实现。
          // 其中涉及到的数据结构请参看清华大学《数据结构》(C语言版)
            
          IndexList *idx;            
          IndexSort(order, idx);  // 对order索引排序
          InitStack(Stack);        //初始化栈
          for (i = 0; i < order.length; i ++) visited[ i ] = FALSE;
                                            // 初始化visited,visited用于标记order
                                            // 中的元素是否已经被访问过
          T = (BiTree *) malloc (sizeof(BiTree));
          T.data = pre[0]; T -> lchild = NULL; T -> rchild = NULL;
                                           // 生成根节点,先序pre中的第一个元素肯定是树根,
                                           // 左右孩子初始化为NULL。
          p = idx[ pre[ 0 ].data - idx[ 0 ].data].index   // 在order中找到根节点所在的位置
          visited[ p ] = TRUE;                         // 标识order中的根元素已被访问过
          cur = T;                                    // cur表示当前构建的节点

          for (i = 1; i < pre.length; ) {
               
                if (p > 0 && !visited[p - 1]) {
                        // 在order中pre[ i ]的左边有元素并且未被访问过,说明有左子树存在,
                        // 生成左子树
                        cur->lchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->lchild.data = pre[ i ];
                                        // 将当前pre中的元素赋给lchild后指向下一个元素
                        cur->lchild->lchild = NULL; cur->rchild->rchild= NULL;
                        p = idx[pre[ i  ++].data - idx[ 0 ].data].index; // 用索引在order中定位
                        visited[ p ] = TRUE;
                        Push(Stack, cur);        // 当前节点进栈
                        cur = cur->lchild;        // 当前节点指向左孩子
                }
                else if (p < order.length && !visited[p + 1]) {
                        // 生成右子树
                        cur->lchild = NULL;   // 没有左孩子
                        cur->rchild = (BiTNode *) malloc (sizeof(BiTNode));
                        cur->rchild.data = pre[ i ];
                        cur->rchild.lchild = NULL; cur->rchlid.rchild = NULL;
                        p = idx[pre[ i  ++].data - idx[ 0 ].data].index; // 用索引在order中定位
                        visited[ p ] = TRUE;
                        cur = cur->rchild;
                }
                else {
                        Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                        p = idx[cur.data - idx[ 0 ].data].index; // 用索引在order中定位
                }
          }
}

实在不好意思了,原来写完以后一高兴就忘记检查了,今天才发现错误。
敬请原谅。


[此贴子已经被作者于2006-1-6 12:26:14编辑过]

--  作者:无聊居士
--  发布时间:2/15/2006 10:44:00 PM

--  
我真菜。。。
什么都看不懂。。。
这大学白读了。。。
--  作者:yangyifeng
--  发布时间:5/11/2006 9:22:00 PM

--  
呵呵辛苦了你
能不能给个递规的啦谢谢先
--  作者:yangjie123
--  发布时间:5/16/2006 4:03:00 PM

--  
怎么用程序编写
--  作者:xiaoyaochenyan
--  发布时间:5/16/2006 4:44:00 PM

--  
谢谢哦
--  作者:binaryluo
--  发布时间:5/17/2006 2:40:00 PM

--  
以下是引用yangjie123在2006-5-16 16:03:00的发言:
怎么用程序编写

这个用类C语言描述的算法已经很接近程序了,只需要加上变量声明等一些语法规则就可以了啊。


--  作者:phoenixinter
--  发布时间:5/18/2006 6:56:00 AM

--  
if the nodes of the binary trees are labeled in both preorder and inorder...then a slight modification can made it O(n)
--  作者:binaryluo
--  发布时间:5/18/2006 11:17:00 AM

--  
以下是引用phoenixinter在2006-5-18 6:56:00的发言:
if the nodes of the binary trees are labeled in both preorder and inorder...then a slight modification can made it O(n)

你说的加标签有什么意义呢?如果是为了排序的话最好就是O(nlgn)了。


--  作者:phoenixinter
--  发布时间:5/18/2006 11:25:00 AM

--  
label means we can hash to find the pivot...
--  作者:binaryluo
--  发布时间:5/18/2006 1:52:00 PM

--  
以下是引用phoenixinter在2006-5-18 11:25:00的发言:
label means we can hash to find the pivot...

这个hash函数不好构造……


--  作者:phoenixinter
--  发布时间:5/18/2006 7:18:00 PM

--  
......you don't quite catch me...
--  作者:binaryluo
--  发布时间:5/19/2006 10:11:00 AM

--  
以下是引用phoenixinter在2006-5-18 19:18:00的发言:
......you don't quite catch me...

o?!


--  作者:phoenixinter
--  发布时间:5/19/2006 2:55:00 PM

--  
我的意思是,如果这棵树的每个顶点是标号的
那么直接hash一下就可以每次找出根了
--  作者:波多黎西橘子15
--  发布时间:6/4/2006 3:46:00 AM

--  
受打击啊
--  作者:jgh37
--  发布时间:12/20/2006 11:18:00 AM

--  
好文章啊,就是不知道问什么到了叶子哪里就不添加到树里了

else {
                        Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                        p = LocateElem(order, cur.data); // 重新定位p的位置
}


--  作者:binaryluo
--  发布时间:12/22/2006 1:10:00 PM

--  
以下是引用jgh37在2006-12-20 11:18:00的发言:
好文章啊,就是不知道问什么到了叶子哪里就不添加到树里了

else {
                         Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                         p = LocateElem(order, cur.data); // 重新定位p的位置
}


首先要说明的是栈只是用来临时保存左子树。

else {
                         Pop(Stack, cur);        // 左右都没有子树即是叶子,则退栈
                         p = LocateElem(order, cur.data); // 重新定位p的位置
}
的意思是当当前结点的左子树生成完(if语句),且右子树也生成完(else if语句)后,如果栈不空,说明还有其它节点的子树未构造,那么就接着处理这些节点,所以要退栈。


--  作者:bigc
--  发布时间:12/23/2006 7:14:00 PM

--  
不明白,怎么可能是O(lnN)?
每个节点都要访问一次,所以至少是O(N)
实际上是它的一个确切界。
--  作者:suiyufei110
--  发布时间:1/18/2007 1:27:00 PM

--  
你给的第一种算法运行不了啊...
--  作者:binaryluo
--  发布时间:1/18/2007 6:33:00 PM

--  
以下是引用suiyufei110在2007-1-18 13:27:00的发言:
你给的第一种算法运行不了啊...

第一次写的有个错误,在第二页上我已更正,你看的是更正后的吗?


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
609.375ms