1. 程式人生 > >資料結構開發(25):二叉樹中屬性操作、層次遍歷與典型遍歷

資料結構開發(25):二叉樹中屬性操作、層次遍歷與典型遍歷

0.目錄

1.二叉樹的比較與相加

2.二叉樹的線索化實現

3.二叉樹的經典面試題分析

4.小結

1.二叉樹的比較與相加

二叉樹的克隆操作:

  • SharedPointer< BTree<T> > clone() const
    1. 克隆當前樹的一份拷貝
    2. 返回值為堆空間中的一棵新二叉樹 ( 與當前樹相等 )

二叉樹的克隆:

  • 定義功能:clone(node)
    1. 拷貝 node 為根結點的二叉樹 ( 資料元素在對應位置相等 )

在BTree.h中實現二叉樹的克隆操作:

protected:
    BTreeNode<T>* clone(BTreeNode<T>* node) const
    {
        BTreeNode<T>* ret = NULL;

        if( node != NULL )
        {
            ret = BTreeNode<T>::NewNode();

            if( ret != NULL )
            {
                ret->value = node->value;

                ret->left = clone(node->left);
                ret->right = clone(node->right);

                if( ret->left != NULL )
                {
                    ret->left->parent = ret;
                }

                if( ret->right != NULL )
                {
                    ret->right->parent = ret;
                }
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ...");
            }
        }

        return ret;
    }
public:
    SharedPointer< BTree<T> > clone() const
    {
        BTree<T>* ret = new BTree<T>();

        if( ret != NULL )
        {
            ret->m_root = clone(root());
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new tree ...");
        }

        return ret;
    }

二叉樹比較操作的定義:

  • 判斷兩棵二叉樹中的資料元素是否對應相等
    1. bool operator == (const BTree<T>& btree)
    2. bool operator != (const BTree<T>& btree)

二叉樹的比較:

  • 定義功能:equal(lh, rh)
    1. 判斷 Ih 為根結點的二叉樹與 rh 為根結點的二叉樹是否相等

在BTree.h中實現二叉樹的比較操作:

protected:
    bool equal(BTreeNode<T>* lh, BTreeNode<T>* rh) const
    {
        if( lh == rh )
        {
            return true;
        }
        else if( (lh != NULL) && (rh != NULL) )
        {
            return (lh->value == rh->value) && equal(lh->left, rh->left) && equal(lh->right, rh->right);
        }
        else
        {
            return false;
        }
    }
public:
    bool operator == (const BTree<T>& btree)
    {
        return equal(root(), btree.root());
    }

    bool operator != (const BTree<T>& btree)
    {
        return !(*this == btree);
    }

統一mian.cpp測試:

#include <iostream>
#include "BTree.h"

using namespace std;
using namespace StLib;

int main()
{
    BTree<int> bt;
    BTreeNode<int>* n = NULL;

    bt.insert(1, NULL);

    n = bt.find(1);
    bt.insert(2, n);
    bt.insert(3, n);

    n = bt.find(2);
    bt.insert(4, n);
    bt.insert(5, n);

    n = bt.find(4);
    bt.insert(8, n);
    bt.insert(9, n);

    n = bt.find(5);
    bt.insert(10, n);

    n = bt.find(3);
    bt.insert(6, n);
    bt.insert(7, n);

    SharedPointer< BTree<int> > btClone = bt.clone();

    int a[] = {8, 9, 10, 6, 7};

    cout << "Clone: " << endl;
    for(int i=0; i<5; i++)
    {
        TreeNode<int>* node = btClone->find(a[i]);

        while( node )
        {
            cout << node->value << " ";
            node = node->parent;
        }
        cout << endl;
    }
    cout << endl;

    cout << "Old BTree: " << endl;
    for(int i=0; i<5; i++)
    {
        TreeNode<int>* node = bt.find(a[i]);

        while( node )
        {
            cout << node->value << " ";
            node = node->parent;
        }
        cout << endl;
    }
    cout << endl;

    cout << "bt == *btClone : " << (bt == *btClone) << endl;

    return 0;
}

執行結果為:

Clone: 
8 4 2 1 
9 4 2 1 
10 5 2 1 
6 3 1 
7 3 1 

Old BTree: 
8 4 2 1 
9 4 2 1 
10 5 2 1 
6 3 1 
7 3 1 

bt == *btClone : 1

二叉樹的相加操作:

  • SharedPointer< BTree<T> > add(const BTree<T>& btree) const
    1. 將當前二叉樹與引數 btree 中的資料元素在對應位置處相加
    2. 返回值 ( 相加的結果 ) 為堆空間中的一棵新二叉樹

二叉樹的加法:

  • 定義功能:add(Ih, rh)
    1. Ih 為根結點的二叉樹與 rh 為根結點的二叉樹相加

在BTree.h中實現二叉樹的相加操作:

protected:
    BTreeNode<T>* add(BTreeNode<T>* lh, BTreeNode<T>* rh) const
    {
        BTreeNode<T>* ret = NULL;

        if( (lh == NULL) && (rh != NULL) )
        {
            ret = clone(rh);
        }
        else if( (lh != NULL) && (rh == NULL) )
        {
            ret = clone(lh);
        }
        else if( (lh != NULL) && (rh != NULL) )
        {
            ret = BTreeNode<T>::NewNode();

            if( ret != NULL )
            {
                ret->value = lh->value + rh->value;

                ret->left = add(lh->left, rh->left);
                ret->right = add(lh->right, rh->right);

                if( ret->left != NULL )
                {
                    ret->left->parent = ret;
                }

                if( ret->right != NULL )
                {
                    ret->right->parent = ret;
                }
            }
            else
            {
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ...");
            }
        }

        return ret;
    }
public:
    SharedPointer< BTree<T> > add(const BTree<T>& btree) const
    {
        BTree<T>* ret = new BTree<T>();

        if( ret != NULL )
        {
            ret->m_root = add(root(), btree.root());
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new tree ...");
        }

        return ret;
    }

mian.cpp測試:

#include <iostream>
#include "BTree.h"

using namespace std;
using namespace StLib;

int main()
{
    BTree<int> bt;
    BTreeNode<int>* n = NULL;

    bt.insert(1, NULL);

    n = bt.find(1);
    bt.insert(2, n);
    bt.insert(3, n);

    n = bt.find(2);
    bt.insert(4, n);
    bt.insert(5, n);

    n = bt.find(4);
    bt.insert(8, n);
    bt.insert(9, n);

    n = bt.find(5);
    bt.insert(10, n);

    n = bt.find(3);
    bt.insert(6, n);
    bt.insert(7, n);


    BTree<int> nbt;

    nbt.insert(0, NULL);

    n = nbt.find(0);
    nbt.insert(6, n);
    nbt.insert(2, n);

    n = nbt.find(2);
    nbt.insert(7, n);
    nbt.insert(8, n);

    SharedPointer< BTree<int> > r = bt.add(nbt);

    int a[] = {8, 9, 10, 13, 5};

    cout << "Add result: " << endl;
    for(int i=0; i<5; i++)
    {
        TreeNode<int>* node = r->find(a[i]);

        while( node )
        {
            cout << node->value << " ";
            node = node->parent;
        }
        cout << endl;
    }
    cout << endl;

    SharedPointer< Array<int> > tr = r->traversal(PreOrder);

    cout << "先序遍歷:" << endl;
    for(int i=0; i<(*tr).length(); i++)
    {
        cout << (*tr)[i] << " ";
    }
    cout << endl;

    return 0;
}

執行結果為:

Add result: 
8 1 
9 4 8 1 
10 5 8 1 
13 5 1 
5 8 1 

先序遍歷:
1 8 4 8 9 5 10 5 13 15 

2.二叉樹的線索化實現

什麼是線索化二叉樹?

  • 將二叉樹轉換為雙向連結串列的過程 ( 非線性 → 線性 )
  • 能夠反映某種二叉樹的遍歷次序 ( 結點的先後訪問次序 )
    1. 利用結點的 right 指標指向遍歷中的後繼結點
    2. 利用結點的 left 指標指向遍歷中的前驅結點

如何對二叉樹進行線索化?
思維過程:

二叉樹的線索化:

本節目標:

  • 新增功能函式 traversal(order, queue)
  • 新增遍歷方式 BTTraversal::LevelOrder
  • 新增公有函式 BTreeNode<T>* thread(BTTraversal order)
  • 消除遍歷和線索化的程式碼冗餘 ( 程式碼重構 )

層次遍歷演算法小結:

  1. 將根結點壓入佇列中
  2. 訪問隊頭元素指向的二叉樹結點
  3. 隊頭元素彈出,將隊頭元素的孩子壓入佇列中
  4. 判斷佇列是否為空 ( 非空:轉 2,空:結束 )

層次遍歷演算法示例:

函式介面設計:

  • BTreeNode<T>* thread(BTTraversal order)
    1. 根據引數 order 選擇線索化的次序 ( 先序,中序,後序,層次 )
    2. 返回值線索化之後指向連結串列首結點的指標
    3. 線索化執行結束之後對應的二叉樹變為空樹

線索化流程:

佇列中結點的連線演算法 [ connect(queue) ]:

列舉中加入層次遍歷:

enum BTTraversal
{
    PreOrder,
    InOrder,
    PostOrder,
    LevelOrder
};

二叉樹的線索化:

protected:
    void levelOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) // 層次遍歷
    {
        if( node != NULL )
        {
            LinkQueue<BTreeNode<T>*> tmp;

            tmp.add(node);

            while( tmp.length() > 0 )
            {
                BTreeNode<T>* n = tmp.front();

                if( n->left != NULL )
                {
                    tmp.add(n->left);
                }

                if( n->right != NULL )
                {
                    tmp.add(n->right);
                }

                tmp.remove();
                queue.add(n);
            }
        }
    }
    void traversal(BTTraversal order, LinkQueue<BTreeNode<T>*>& queue)
    {
        switch (order)
        {
            case PreOrder:
                preOrderTraversal(root(), queue);
                break;
            case InOrder:
                inOrderTraversal(root(), queue);
                break;
            case PostOrder:
                postOrderTraversal(root(), queue);
                break;
            case LevelOrder:
                levelOrderTraversal(root(), queue);
                break;
            default:
                THROW_EXCEPTION(InvalidParameterException, "Parameter order is invalid ...");
                break;
        }
    }

    BTreeNode<T>* connect(LinkQueue<BTreeNode<T>*>& queue)
    {
        BTreeNode<T>* ret = NULL;

        if( queue.length() > 0 )
        {
            ret = queue.front();

            BTreeNode<T>* slider = queue.front();

            queue.remove();

            slider->left = NULL;

            while( queue.length() > 0 )
            {
                slider->right = queue.front();
                queue.front()->left = slider;
                slider = queue.front();
                queue.remove();
            }

            slider->right = NULL;
        }

        return ret;
    }
public:
    SharedPointer< Array<T> > traversal(BTTraversal order)
    {
        DynamicArray<T>* ret = NULL;
        LinkQueue<BTreeNode<T>*> queue;

        traversal(order, queue);

        ret = new DynamicArray<T>(queue.length());

        if( ret != NULL )
        {
            for(int i=0; i<ret->length(); i++, queue.remove())
            {
                ret->set(i, queue.front()->value);
            }
        }
        else
        {
            THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create return array ...");
        }

        return ret;
    }

    BTreeNode<T>* thread(BTTraversal order)
    {
        BTreeNode<T>* ret = NULL;
        LinkQueue<BTreeNode<T>*> queue;

        traversal(order, queue);

        ret = connect(queue);

        this->m_root = NULL;

        m_queue.clear();

        return ret;
    }

main.cpp測試:

#include <iostream>
#include "BTree.h"

using namespace std;
using namespace StLib;

int main()
{
    BTree<int> bt;
    BTreeNode<int>* n = NULL;

    bt.insert(1, NULL);

    n = bt.find(1);
    bt.insert(2, n);
    bt.insert(3, n);

    n = bt.find(2);
    bt.insert(4, n);
    bt.insert(5, n);

    n = bt.find(4);
    bt.insert(8, n);
    bt.insert(9, n);

    n = bt.find(5);
    bt.insert(10, n);

    n = bt.find(3);
    bt.insert(6, n);
    bt.insert(7, n);

    SharedPointer< Array<int> > tr = bt.traversal(LevelOrder);

    for(int i=0; i<(*tr).length(); i++)
    {
        cout << (*tr)[i] << " ";
    }
    cout << endl;

    BTreeNode<int>* head = bt.thread(LevelOrder);

    while( head != NULL )
    {
        cout << head->value << " ";
        head = head->right;
    }
    cout << endl;

    return 0;
}

執行結果為:

1 2 3 4 5 6 7 8 9 10 
1 2 3 4 5 6 7 8 9 10 

3.二叉樹的經典面試題分析

準備程式碼:

#include <iostream>
#include "BTree.h"

using namespace std;
using namespace StLib;

template < typename T >
BTreeNode<T>* createTree()
{
    static BTreeNode<int> ns[9];

    for(int i=0; i<9; i++)
    {
        ns[i].value = i;
        ns[i].parent = NULL;
        ns[i].left = NULL;
        ns[i].right = NULL;
    }

    ns[0].left = &ns[1];
    ns[0].right = &ns[2];
    ns[1].parent = &ns[0];
    ns[2].parent = &ns[0];

    ns[1].left = &ns[3];
    ns[1].right = NULL;
    ns[3].parent = &ns[1];

    ns[2].left = &ns[4];
    ns[2].right = &ns[5];
    ns[4].parent = &ns[2];
    ns[5].parent = &ns[2];

    ns[3].left = NULL;
    ns[3].right = &ns[6];
    ns[6].parent = &ns[3];

    ns[4].left = &ns[7];
    ns[4].right = NULL;
    ns[7].parent = &ns[4];

    ns[5].left = &ns[8];
    ns[5].right = NULL;
    ns[8].parent = &ns[5];

    return ns;
}

template < typename T >
void printInOrder(BTreeNode<T>* node)
{
    if( node != NULL )
    {
        printInOrder(node->left);

        cout << node->value <<" ";

        printInOrder(node->right);
    }
}

template < typename T >
void printDualList(BTreeNode<T>* node)
{
    BTreeNode<T>* g = node;

    cout << "head -> tail: " << endl;

    while( node != NULL )
    {
        cout << node->value << " ";

        g = node;

        node = node->right;
    }

    cout << endl;

    cout << "tail -> head: " << endl;

    while( g != NULL )
    {
        cout << g->value << " ";

        g = g->left;
    }

    cout << endl;
}

int main()
{
    BTreeNode<int>* ns = createTree<int>();

    printInOrder(ns);
    cout << endl;

    return 0;
}

執行結果為:

3 6 1 0 7 4 2 8 5 

3.1 單度結點刪除

面試題一:

  • 單度結點刪除
    1. 編寫一個函式用於刪除二叉樹中的所有單度結點
    2. 要求:結點刪除後,其唯一的子結點替代它的位置

結點中包含指向父結點的指標:

  • 定義功能:delOdd1(node)
    1. 刪除 node 為根結點的二叉樹中的單度結點

單度結點刪除(結點中包含指向父結點的指標):

template < typename T >
BTreeNode<T>* delOdd1(BTreeNode<T>* node)
{
    BTreeNode<T>* ret = NULL;

    if( node != NULL )
    {
        if(((node->left != NULL) && (node->right == NULL)) ||
           ((node->left == NULL) && (node->right != NULL)) )
        {
            BTreeNode<T>* parent = dynamic_cast<BTreeNode<T>*>(node->parent);
            BTreeNode<T>* node_child = (node->left != NULL) ? node->left : node->right;

            if( parent != NULL )
            {
                BTreeNode<T>*& parent_child = (parent->left == node) ? parent->left : parent->right;

                parent_child = node_child;
                node_child->parent = parent;
            }
            else
            {
                node_child->parent = NULL;
            }

            if( node->flag() )
            {
                delete node;
            }

            ret = delOdd1(node_child);
        }
        else
        {
            delOdd1(node->left);
            delOdd1(node->right);

            ret = node;
        }
    }

    return ret;
}

int main()
{
    BTreeNode<int>* ns = createTree<int>();

    printInOrder(ns);
    cout << endl;

    ns = delOdd1(ns);

    printInOrder(ns);
    cout << endl;

    int a[] = {6, 7, 8};
    for(int i=0; i<3; i++)
    {
        TreeNode<int>* n = ns + a[i];

        while( n != NULL )
        {
            cout << n->value << " ";

            n = n->parent;
        }
        cout << endl;
    }

    return 0;
}

執行結果為:

3 6 1 0 7 4 2 8 5 
6 0 7 2 8 
6 0 
7 2 0 
8 2 0 

結點中只包含左右孩子指標:

  • 定義功能:delOdd2(node) // node為結點指標的引用
    1. 刪除 node 為根結點的二叉樹中的單度結點

單度結點刪除(結點中只包含左右孩子指標):

template < typename T >
void delOdd2(BTreeNode<T>*& node)
{
    if( node != NULL )
    {
        if(((node->left != NULL) && (node->right == NULL)) ||
           ((node->left == NULL) && (node->right != NULL)) )
        {
            BTreeNode<T>* node_child = (node->left != NULL) ? node->left : node->right;

            if( node->flag() )
            {
                delete node;
            }

            node = node_child;

            delOdd2(node);
        }
        else
        {
            delOdd2(node->left);
            delOdd2(node->right);
        }
    }
}

int main()
{
    BTreeNode<int>* ns = createTree<int>();

    printInOrder(ns);
    cout << endl;

    delOdd2(ns);

    printInOrder(ns);
    cout << endl;

    return 0;
}

執行結果為:

3 6 1 0 7 4 2 8 5 
6 0 7 2 8 

3.2 中序線索化二叉樹

面試題二:

  • 中序線索化二叉樹
    1. 編寫一個函式用於中序線索化二叉樹
    2. 要求:不允許使用其它資料結構

解法一:在中序遍歷的同時進行線索化

  • 思路:
    1. 使用輔助指標,在中序遍歷時指向當前結點的前驅結點
    2. 訪問當前結點時,連線與前驅結點的先後次序

定義功能:inOrderThread(node, pre)

  • node根結點,也是中序訪問的結點
  • pre為中序遍歷時的前驅結點指標

中序線索化二叉樹:

template < typename T >
void inOrderThread(BTreeNode<T>* node, BTreeNode<T>*& pre)
{
    if( node != NULL )
    {
        inOrderThread(node->left, pre);

        node->left = pre;

        if( pre != NULL )
        {
            pre->right = node;
        }

        pre = node;

        inOrderThread(node->right, pre);
    }
}

template < typename T >
BTreeNode<T>* inOrderThread1(BTreeNode<T>* node)
{
    BTreeNode<T>* pre = NULL;

    inOrderThread(node, pre);

    while( (node != NULL) && (node->left != NULL) )
    {
        node = node->left;
    }

    return node;
}

int main()
{
    BTreeNode<int>* ns = createTree<int>();

    printInOrder(ns);
    cout << endl;

    delOdd2(ns);

    printInOrder(ns);
    cout << endl;

    ns = inOrderThread1(ns);

    printDualList(ns);

    return 0;
}

執行結果為:

3 6 1 0 7 4 2 8 5 
6 0 7 2 8 
head -> tail: 
6 0 7 2 8 
tail -> head: 
8 2 7 0 6 

解法二:中序遍歷的結點次序正好是結點的水平次序

  • 思路:
    1. 使用輔助指標,指向轉換後雙向連結串列的頭結點和尾結點
    2. 根結點與左右子樹轉換的雙向連結串列連線,成為完整雙向連結串列

定義功能:inOrderThread(node, head, tail)

  • node根結點,也是中序訪問的結點
  • head轉換成功後指向雙向連結串列的首結點
  • tail轉換成功後指向雙鏈表的尾結點

中序線索化二叉樹:

template < typename T >
void inOrderThread(BTreeNode<T>* node, BTreeNode<T>*& head, BTreeNode<T>*& tail)
{
    if( node != NULL )
    {
        BTreeNode<T>* h = NULL;
        BTreeNode<T>* t = NULL;

        inOrderThread(node->left, h, t);

        node->left = t;

        if( t != NULL )
        {
            t->right = node;
        }

        head = (h != NULL) ? h : node;

        h = NULL;
        t = NULL;

        inOrderThread(node->right, h, t);

        node->right = h;

        if( h != NULL )
        {
            h->left = node;
        }

        tail = (t != NULL) ? t : node;
    }
}

template < typename T >
BTreeNode<T>* inOrderThread2(BTreeNode<T>* node)
{
    BTreeNode<T>* head = NULL;
    BTreeNode<T>* tail = NULL;

    inOrderThread(node, head, tail);

    return head;
}

int main()
{
    BTreeNode<int>* ns = createTree<int>();

    printInOrder(ns);
    cout << endl;

    ns = inOrderThread2(ns);

    printDualList(ns);

    return 0;
}

執行結果為:

3 6 1 0 7 4 2 8 5 
head -> tail: 
3 6 1 0 7 4 2 8 5 
tail -> head: 
5 8 2 4 7 0 1 6 3 

4.小結

  • 比較操作判斷兩棵二叉樹中的資料元素是否對應相等
  • 克隆操作將當前二叉樹在堆空間中進行復制
  • 相加操作將兩棵二叉樹中的資料元素在對應位置處相加
  • 相加操作的結果儲存在堆空間的一棵二叉樹中

  • 線索化是將二叉樹轉換為雙向連結串列的過程
  • 線索化之後結點間的先後次序符合某種遍歷次序
  • 線索化操作將破壞原二叉樹結點間的父子關係
  • 線索化之後二叉樹將不再管理結點的生命期