二叉樹平衡(DSW算法)

一、前言

《二叉查找樹全面詳細介紹》中講解了二叉樹操作:搜索(查找)、遍歷、插入、刪除。《二叉樹遍歷詳解(遞歸遍歷、非遞歸棧遍歷,Morris遍歷)》詳細講解了二叉樹遍歷的幾種方法。《二叉樹平衡(有序數組創建)》通過了一種構建平衡二叉樹的方法

《二叉樹平衡(有序數組創建)》討論的算法的效率有點低,因爲在創建完全平衡的樹之前,需要使用一個額外的有序數組。爲了避免排序,這一算法需要破壞樹並用中序遍歷把元素放在數組中,然後重建該樹,這樣做效率並不高,除非樹很小。然而,存在幾乎不需要存儲中間變量也不需要排序過程的算法。Colin Day提出了非常簡潔的DSW算法,Quentin F. Stout以及Bette L. Waren 對此算法進行了改進,本文講解DSW算法構建平衡二叉樹(完全平衡二叉樹)。

 

二、知識點回顧

2.1 二叉樹的旋轉

有兩種旋轉方法:

(1)左旋轉:將根節點旋轉到(根節點的)右孩子的左孩子位置

(2)右旋轉:將根節點旋轉到(根節點的)左孩子的右孩子位置

下圖爲左、右旋轉圖,黃色節點爲旋轉的根節點,灰色節點是可以有也可以沒有的節點。

 

三、DSW算法(Day–Stout–Warren algorithm)

一般來說,DSW算法首先將任意的二叉查找樹轉換爲類似於鏈表的樹,稱爲主鏈或主幹。然後圍繞主鏈中第二個節點的父節點,反覆將其旋轉,將這棵被拉伸的樹在系列步驟中轉換成完全平衡的樹。DSW算法可以分爲兩個大的步驟。

步驟一:將二叉樹右旋轉形成主鏈;

步驟二:左旋轉轉換爲平衡樹;

其中步驟二可以分爲兩個階段:

階段一:左旋n-m次

階段二:進入循環根據每一層節點數進行旋轉。

3.1 步驟一

createBackbone (root)
    tmp = root;
    while (tmp !=0)
        if tmp有左子節點
        	圍繞tmp旋轉該子節點: //這樣該左子節點將成爲tmp的父節點
    	tmp設置爲剛剛成爲父節點的子節點;
     else 將tmp設置爲它的右子節點:

步驟一實例演示:

 

3.2 步驟二

createPerfectTree ()
    n=節點數;
    m = (1 << (int)log2(n)) - 1; //計算當前節點數n與最接近完全平衡二叉樹中節點數之間的差,多出的節點將單獨處理
    // 第一階段:進行左旋m-n次
    
    // 第二階段:進入循環根據每一層節點數進行左旋轉
    while (m> 1)
        m=m/2;
        從主鏈的頂部開始做m次旋轉;

這裏不要好理解的是,這兩個階段都是進行左旋轉,爲什麼要分開進行?

個人理解:第一階段需要先進行n-m次左旋轉,是爲了保證最後一排階段是在樹的最左側。第二階段,可以理解爲根據樹的層次中節點數量進行旋轉次數。有點抽象不容易理解,看圖,更直觀些,容易加深理解。

1)第一階段第一次旋轉,對節點1進行向左旋轉

2)第一階段第二次旋轉,旋轉節點是上一次旋轉節點的後面第2個節點也就是節點3作爲本次旋轉節點。

3)第一階段的兩次旋轉完成

4)第二階段第一層第一次旋轉,以第一階段旋轉完的樹根節點作爲本次旋轉節點。

第一層旋轉次數3次, 3 = 7>>1。

5)第二階段第一層第二次旋轉,旋轉節點是上一次旋轉節點的後面第2個節點也就是節點5作爲本次旋轉節點。

6)第二階段第一層第三次旋轉,旋轉節點是上一次旋轉節點的後面第2個節點也就是節點8作爲本次旋轉節點。

7)第二階段第一層第三次旋轉完成。我們發現,當前生成的樹是平衡二叉樹,但不是完全平衡二叉樹,其中有兩個節點是在最後一層的右邊。最後一層節點數量和步驟一中旋轉次數是對應的。現在需要將其轉換成完平衡二叉樹,需要繼續旋轉。

8)第二階段第二層第一次旋轉,以上一層旋轉完的樹根節點作爲本次旋轉節點。

第二層旋轉次數1次, 1 = 3>>1。

9)旋轉完成,完全平衡二叉樹構建成功。

 

四、總結

整體來說這種思路有些抽象,需要結合圖多多領悟。時間複雜度和空間複雜度相比《二叉樹平衡(有序數組創建)》好很多。

 

五、編碼實現

//==========================================================================
/**
* @file    : DswBST.h
* @blogs   : https://blog.csdn.net/nie2314550441/article/details/107095634
* @author  : niebingyu
* @title   : DSW算法構建平衡二叉樹
* @purpose : DSW算法構建平衡二叉樹
*/
//==========================================================================
#pragma once
#include "GenBST.h"

template<class T>
class DswBST : private BST<T> 
{
public:
    DswBST(T* a, int len);    //根據數組中的數據構造樹,調試測試用

    // 平衡二叉樹
    void dswBalance();

    // 前序遍歷二叉樹
    void preorder(){ BST<T>::preorder(); }

    // 中序遍歷二叉樹
	void inorder() { BST<T>::inorder(); }

protected:
    // 步驟一:將二叉樹右旋轉形成主鏈
    void createBackbone();
    // 步驟二:左旋轉轉換爲平衡樹
    void creatPerfectTree();

    // 右旋轉
    void rotateRight(BSTNode<T>* Gr, BSTNode<T>* Par, BSTNode<T>* Ch);

    // 左旋轉
    void rotateLeft(BSTNode<T>* Gr, BSTNode<T>* Par, BSTNode<T>* Ch);

private:
    int m_count;
};

template<class T>
DswBST<T>::DswBST(T* a, int len) 
{
	m_count = len;
    for (int i = 0; i < len; i++) 
    {
        this->insert(a[i]);
    }
}

template<class T>
void DswBST<T>::dswBalance() 
{
    createBackbone();
    creatPerfectTree();
}

// 二叉查找樹轉化成主鏈的過程分析
/**********************************************************************************************
*  5 <-tmp         5               5               5              5
*   \               \               \               \               \
*    10             10              10              10              10
*      \              \               \               \               \
*       20            15              15              15              15
*      /  \             \               \               \               \
*     15  30            20              20              20              20
*         / \             \              \                \               \
*        25 40            30 <-tmp       25 <-tmp         23               23
*       /  \             /  \           /  \               \                \
*     23    28          25   40        23   30              25              25
*                      /  \                /  \              \                \
*                     23   28             28   40            30 <-tmp         28
*                                                           /  \               \
*                                                          28  40               30
*                                                                                \
*                                                                                 40 <-tmp
***********************************************************************************************/
// 步驟一:將二叉樹右旋轉形成主鏈
template<class T>
void DswBST<T>::createBackbone() 
{
    BSTNode<T>* Gr = 0, *Par = this->m_root, *Ch = 0;
    while (Par != 0) 
    {
        Ch = Par->m_left;
        if (Ch != 0) 
        {
            rotateRight(Gr, Par, Ch);
            Par = Ch;
        }
        else 
        {
            Gr = Par;
            Par = Par->m_right;
        }

        // 旋轉過程中,如果是繞根節點的右節點旋轉時要將根節點置爲原根節點的右節點
        if (Gr == 0)
            this->m_root = Ch;
    }
}

/************************************************************************
 *  子節點Ch圍繞父節點Par的右旋轉
 *   Before      After
 *    Gr          Gr
 *     \           \
 *     Par         Ch
 *    /  \        /  \
 *   Ch   Z      X   Par
 *  /  \            /  \
 * X    Y          Y    Z
 ***********************************************************************/
// 右旋轉
template<class T>
void DswBST<T>::rotateRight(BSTNode<T>* Gr, BSTNode<T>* Par, BSTNode<T>* Ch) 
{
    if (Gr != 0)
        Gr->m_right = Ch;

    Par->m_left = Ch->m_right;
    Ch->m_right = Par;
}

// 左旋轉
template<class T>
void DswBST<T>::rotateLeft(BSTNode<T>* Gr, BSTNode<T>* Par, BSTNode<T>* Ch) 
{
    if (Gr != 0)
        Gr->m_right = Ch;

    Par->m_right = Ch->m_left;
    Ch->m_left = Par;
}

// 步驟二:左旋轉轉換爲平衡樹
template<class T>
void DswBST<T>::creatPerfectTree() 
{
    int n = m_count;
    if (n < 3)
        return; //節點數目小於3不用平衡

    int m = (1 << (int)log2(n)) - 1;
    BSTNode<T>* Gr = 0;
    BSTNode<T>* Par = this->m_root;
    BSTNode<T>* Ch = this->m_root->m_right;

    this->m_root = this->m_root->m_right; //修改root指針
    // 第一階段: 左旋n-m次
    for (int i = 0; i < n - m; i++) 
    {
        rotateLeft(Gr, Par, Ch);
        Gr = Ch;
        Par = Gr->m_right;
        if (0 != Par) 
            Ch = Par->m_right;
        else 
            break;
    }

    // 第二階段,進入while循環
    while (m > 1) 
    {
        m = m >> 1;
        BSTNode<T>* Gr = 0;
        BSTNode<T>* Par = this->m_root;
        BSTNode<T>* Ch = this->m_root->m_right;

        this->m_root = this->m_root->m_right;
        for (int i = 0; i < m; i++) 
        {
            rotateLeft(Gr, Par, Ch);
            Gr = Ch;
            Par = Gr->m_right;
            if (0 != Par) 
                Ch = Par->m_right;
            else 
                break;
        }
    }
}

測試代碼

//==========================================================================
/**
* @file    : DswBSTTest.h
* @blogs   : 
* @author  : niebingyu
* @title   : 測試DSW算法構建平衡二叉樹
* @purpose : 測試DSW算法構建平衡二叉樹
*
*/
//==========================================================================
#pragma once
#include "DswBST.h"
using namespace std;

#define NAMESPACE_DSWBSTTEST namespace NAME_DSWBSTTEST {
#define NAMESPACE_DSWBSTTESTEND }
NAMESPACE_DSWBSTTEST

// 測試用例
void Test1()
{
    vector<int> data = { 1,2,3,4,5,6,7,8,9};
    DswBST<int> tree(data.data(), data.size());
    tree.dswBalance();

    cout << "Test1 前序遍歷: ";
    tree.preorder();
    cout << "\nTest1 中序遍歷: ";
    tree.inorder();
    cout << endl;
}

void Test2()
{
    vector<int> data = { 5, 10, 20, 15, 30, 25, 40, 23, 28 };
    DswBST<int> tree(data.data(), data.size());
    tree.dswBalance();

    cout << "Test2 前序遍歷: ";
    tree.preorder();
    cout << "\nTest2 中序遍歷: ";
    tree.inorder();
    cout << endl;
}

// 測試用例
void Test3()
{
    vector<int> data;
    for (int i = 1; i <= 33; ++i)
        data.push_back(i);

    DswBST<int> tree(data.data(), data.size());
    tree.dswBalance();

    cout << "Test1 前序遍歷: ";
    tree.preorder();
    cout << "\nTest1 中序遍歷: ";
    tree.inorder();
    cout << endl;
}
NAMESPACE_DSWBSTTESTEND

void DswBSTTest_Test()
{
    NAME_DSWBSTTEST::Test1();
    NAME_DSWBSTTEST::Test2();
    NAME_DSWBSTTEST::Test3();
}

執行結果:

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章