1. 程式人生 > >無空間複雜度(無棧)的非遞迴二叉樹中序遍歷

無空間複雜度(無棧)的非遞迴二叉樹中序遍歷

常見的二叉樹非遞迴演算法都是用棧儲存訪問路徑上的結點,這樣使空間複雜為o(n),其中n為樹最大深度。空間複雜度為o(1)的演算法並沒有以犧牲時間複雜度為代價,它只是巧妙的運用葉子結點左右孩子指標為空這一事實,將所有的葉子組成一鏈棧用於儲存回退資訊,其中葉子結點的lchild域相當於連結串列的data域,rchild相當於連結串列的next域,是一種“廢物利用”的思想,本質上還是用了棧,只是沒用分配棧空間而已。

事實上,像二叉樹這種遞迴定義的非線性結構,在設計相應的非遞迴程式時最難考慮的是從當前結點向上回退的情況,向下走的情況很簡單:先向左走,左面走不下去了就向右。回退的時候有兩個要點:

1,找到其父結點的指標以便回退。

2,判斷出它是父結點的左孩子還是右孩子。常見的用棧儲存資訊的演算法,棧中儲存了從根結點到當前結點的路徑資訊,當然可以正確的回退。


該演算法的大概思想是:p作為當前結點,q作為它的父結點(滿足要點1),如此深入下去,如果p是q的左孩子,就把q的左指標指向q的父親,這樣q作指標所指向的結點就不在是p了,而是p的爺爺結點了,這樣的做法是為了在回退時找到父結點。為了滿足要點2:判斷回退時它是父結點的左孩子還是右孩子,有三種情況要考慮:1,回退時如果q的lchild為空,表示q只有右子樹p,這樣就判斷出來了p只可能是q的右孩子,剩下的問題就是回退和恢復q的rchild了。2,如果q的rchild為空,這個情況和1相似......3,如果q的lchild和rchild都不為空,這樣回退時候就比較難判斷p到底是q的左還是右兒子了?該演算法的解決方法為:用av儲存當前葉子結點以作為為棧分配的元素,這個棧用於記錄遇到的雙子樹結點。 具體操作如下:

                                av->lchild=lr;/*入棧加回退操作*/
                                    av
->rchild=top;
                                    top
=av; lr=q;                                  r=top; /*退棧*/
                                 lr
=r->lchild; /*lchild相當於連結串列的data域,rchild相當next域
*/
 
                                     top
=r->rchild;
                                  r
->lchild=r->rchild=NULL;/*退棧結束*/

用lr表示當前遇到的雙子樹結點,也就是p從q的lchild域回退時才判斷出來q有兩個子樹(怎知道是lchild回退?這是程式技巧問題,看程式!),這時就把q記錄在lr內,這時lr並不急於進av棧,只有當lr的右子樹中還有雙子樹結點時,lr才進棧!如果lr的右子樹中沒有雙子樹結點,那麼當p回退的時候如果碰到了lr就表示p為q的右兒子,具體演算法還要看程式說明。 至於作為棧的葉子結點是完全夠用的,這是由中序遍歷的性質決定的,因為只有訪問了一個葉子結點才有可能訪問一個雙子樹結點。

/*非遞迴無棧二叉樹中序遍歷演算法*/
void InOrder2(BiTree T) 
    BiTree top
=NULL,lr=NULL,r,av,
        p
=T,q=T;
        
/*top為棧頂指標,lr記錄當前2子樹結點,r為臨時變數,
    av為當前可用葉子結點,p為當前訪問結點,q為p的父親結點
*/

    
if(T==NULL) return ;
    
while(1/*向下訪問搜尋*/
        
while(1
            
if(p->lchild==NULL&&p->rchild==NULL) {/*如果訪問到底,就輸出該結點*/
                printf(
"%c ",p->data);
                
break;
            }

            
elseif(p->lchild==NULL) {/*沿左子樹訪問到底,轉向訪問右子樹*/
                printf(
"%c ",p->data);
                r
=p->rchild; 
                p
->rchild=q;
                q
=p; p=r;
            }

            
else{/*一直沿左子樹訪問下去*/
                r
=p->lchild; 
                p
->lchild=q;
                q
=p; p=r;
            }

        }

        av
=p;/*p為葉子結點,av記錄當前葉子結點*/
        
while(1{
            
if(p==T) return;/*如果回退到根,返回*/
                            
elseif(q->lchild==NULL) {/*如果父結點q的lchild為空,
                                表示p為q的右兒子
*/

                                r
=q->rchild; /*向父結點的右兒子方向回退*/
                                q
->rchild=p; /*重新連線父指標*/
                                p
=q; q=r;
                            }

                            
elseif(q->rchild==NULL) {/*類似上面*/
                                r
=q->lchild;
                                q
->lchild=p;
                                p
=q; q=r;
                                printf(
"%c ",p->data);
                            }

                            
else{/*如果p的父親q有兩個子樹,用以下方法判斷p是q的左兒子還是右兒子*/
                                
if(q==lr) {/*p是q的右兒子*/
                                    r
=top; /*退棧*/
                                    lr
=r->lchild; /*lchild相當於連結串列的data域,rchild相當next域*/ 
                                    top
=r->rchild;
                                    r
->lchild=r->rchild=NULL;/*退棧結束*/
                                    r
=q->rchild; 
                                    q
->rchild=p;
                                    p
=q; q=r;
                                }

                                
else/*p是q的左兒子*/
                                    printf(
"%c ",q->data);
                                    av
->lchild=lr;/*入棧加回退操作*/
                                    av
->rchild=top;
                                    top
=av; lr=q;
                                    r
=q->lchild;
                                    q
->lchild=p;
                                    p
=q->rchild;
                                    q
->rchild=r;
                                    
break;
                                }

                            }

        }

    }

    
}