1. 程式人生 > >從AVL樹的定義出發,一步步推匯出旋轉的方案。

從AVL樹的定義出發,一步步推匯出旋轉的方案。

本文從AVL樹的定義出發,一步步地推匯出AVL樹旋轉的方案,這個推導是在已經清楚地知道AVL樹的定義這個前提下進行的。文章注重思考的過程,並不會直接給出AVL樹是怎樣旋轉的,用來提醒自己以後在學習的時候要注重推導的過程。在此,我要特別感謝下我的資料結構老師,是他讓我意識到思考的重要性。

一、從AVL樹的定義開始

1. 二叉查詢樹的問題

二叉查詢樹的出現,雖然使查詢的平均時間降到了logN,但是,在多次刪除或者插入操作後,可能會出現根節點的左子樹比右子樹高很多,或者右子樹比左子樹高很多的情況。如圖所示:
失去平衡的二叉查詢樹
這樣的話,查詢的效率會相當低。所以現在的問題是:要想出一種解決方案,可以使得二叉查詢樹能保持平衡。這時,我們要定義一種新的二叉查詢樹,這種二叉查詢樹是一種帶有平衡條件的二叉查詢樹,也就是AVL樹。

2. AVL樹的定義

一棵AVL樹是其每個節點的左子樹和右子樹的高度最多差1的二叉查詢樹(空樹的高度定義為-1)。在下圖中,左邊的樹是AVL樹,但右邊的樹不是。
AVL樹
在右邊的樹中,根節點7的左子樹的高度為2,右子樹的高度為0,左右子樹的高度差為2,所以不是一棵AVL樹。
有了AVL樹的定義,下面就可以開始推導AVL樹是如何通過旋轉來達到平衡條件的了。

二、開始推導。

1. 分析需要注意的地方。

首先,我們用正向的思維來分析下。正常情況下,當進行插入操作時,我們需要更新通向根節點路徑上的那些節點的所有平衡資訊,而插入操作隱含著困難的原因在於,插入一個節點可能破壞AVL樹的特性。例如,將6插入到圖4-29中的AVL樹中將會破壞關鍵字為8的節點的平衡條件。如果發生這種情況,那麼就要把性質恢復(也就是恢復平衡)以後才認為這一步插入完成。

2.把影響降到最小。

作為一個程式設計師,我們總是希望在解決問題的過程中,把影響降到最小。

在上面的分析中,我們瞭解到,在插入一個節點後,很可能會破壞AVL樹的平衡性。而且,這種破壞平衡性的行為很可能是連鎖反應,比如,當我們在圖4-29中的AVL樹中的關鍵字為3的葉節點下面添加個關鍵字為2.5的葉節點時,就會破壞關鍵字為4的節點的平衡性,不僅如此,關鍵字為2的節點和關鍵字為5的根節點的平衡性都被打破了。

這種連鎖反應是十分可怕的,因此我們要把影響降至最小。如果我們能夠在第一個平衡性被破壞的節點上阻止連鎖反應的擴散,那麼就可以把影響降到最小了。在這裡,我們通過旋轉來對樹的區域性進行簡單的修正,從而把影響降到最小。

我們現在不用管是怎樣旋轉的,下面將進行一步步的推導,把旋轉的本質推匯出來,精彩的內容在下面!

3.假設某個節點為第一個平衡性被破壞的節點

現在開始,我們要分析所有可能出現的情況。

第一種情況:從第一個平衡性被破壞的節點的左邊插入,從而導致的失衡

先設下圖中的K2是第一個不滿足AVL平衡特性的節點,也就是說,K2在沒插入新節點前,是滿足平衡性的。然後,插入的地方為K2的左子樹:

這裡寫圖片描述

在這種情況下,K2的左子樹的高度就比它的右子樹高2了。設
a. 上圖中的樹的名字為STree,作為整棵AVL樹的一部分
b 插入節點前,以K2為根節點的樹的高度為h+2,即STree的高度為h+2;
c. 在這種情況下,插入節點前,K2的左子樹的高度為h+1,K2的右子樹Z的高度為h
d.插入節點後,以K2為根節點的樹的高度變成了h+3;
e.插入節點後,K2的左子樹的高度為h+2 ,K2的右子樹Z的高度仍然為h

現在我們面臨的問題是:要使STree的高度在插入新節點後,仍然保持不變。也就是說,要使STree的高度由h+3變回到h+2。這樣,就不會發生連鎖反應,把影響降至最小,因為STree的高度仍然是插入節點前的高度。由這個問題,我們又會引出一連串的問題。(有問題就對了,畢竟結論不是一下子得出來的,要經過詳細的分析,不斷地問為什麼?)

問題1:要怎樣做才能使STree的高度變回到h+2呢?

我們可以把裡面的節點的位置改動下,以達到改變STree高度這個目的。但注意到,AVL樹首先是一顆二叉查詢樹,裡面的節點的位置是有規律的:對於樹中的每個節點X,它的左子樹中所有關鍵字值小於X的關鍵字值,而它的右子樹中所有關鍵字值大於X的關鍵字值。因此,我們在改動STree中節點的位置時,要不破壞上面的規律。

在眾多的限制下,我們只能把在中間的節點的位置調整下,顯然,K2的左子樹比右子樹高2,所以,我們應該把K2的左兒子K1提到K2的位置。這樣STree的高度就變成了K1的高度,即變回了h+2。這時,K1的右兒子是K2,K1的右子樹Y變成K2的左子樹,如下圖:
單旋轉
到這裡,已經分析出了怎樣去旋轉,而且這個做法好像真的能解決問題,但怎樣才能確定這樣做是正確的呢,仔細想想,這個情況還可以再細分:

情形1: 新節點是在K1的左子樹X中插入,從而導致K2失去平衡(注意,K1是平衡的,不要忘記了,K2才是第一個失衡的)。
情形2:新節點是在K1的右子樹Y中插入,從而導致K2失去平衡。

問題2:情形1下,插入新節點前,X、Y的高度是多少,在這樣的高度下,按照上圖這樣旋轉是不是正確的?
由題設,我們已經知道,Z的高度為h。插入節點前,以K1為根的樹的高度為h+1,因此X、Y的高度最多隻能是h。此時,X的高度是可以確定是h的,因為新節點是從X中插入,從而使K1的高度由h+1變成h+2,所以X在插入新節點前,高度為h。再來確定Y的高度:

如果Y的高度為h-1,則在X中插入新節點後,X的高度變成h+1,這樣的話,X、Y的高度差就變成了2,首先失去平衡的節點是K1而不是K2了,這與假設相矛盾,所以Y的高度不會是h-1.

到這裡已經可以確定,Y的高度也是h了,只有這樣,在X中插入新節點時,才不會導致K1失衡,畢竟有個大前提在那裡,K2才是第一 個失衡的節點
現在解決了X、Y的高度,在這樣的高度下,旋轉後的樹(圖4-31右邊的樹),X的高度是h+1,K1的高度為h+2,K2的高度為h+1,Y和Z的高度為h。這時每個節點的高度都滿足平衡性條件,所以,在情形1下,上圖的旋轉(這種旋轉叫單旋轉)是正確了,它有效地修正了STree的高度,使影響截斷在STree中,從而使整顆AVL樹的性質不變。

問題2:情形2下,插入新節點前,X、Y的高度是多少,在這樣的高度下,按照上圖這樣旋轉是不是正確的?
這裡,X、Y高度的分析和前面是一樣的,分析完後,插入前X、Y的高度也都是h。插入後,Y的高度變成了h+1,如下圖4-34左邊的樹所示
單旋轉不能修復情形2
在這種情形下,按照上面的旋轉是不行的,這樣,旋轉後Y成為了K2的左子樹。這時,K1仍然是不平衡的,所以這樣的旋轉是不正確的。我們要尋找另外的方法來使STree平衡。

問題3:怎樣才能解決情形2所產生的問題呢?
這裡的問題在於子樹Y太深,單旋轉沒有降低它的深度。在情形1中,我們通過在K2進行單旋轉來使比較高的子樹K1提上去,從而解決了問題。於是,我們就想,既然單旋轉可以把比較高的子樹提上去,那麼我們先在K1處進行一次單旋轉,把Y子樹提上去,這看起來是個不錯的主意。既然要進行單旋轉,我們就需要一個K1的右兒子K3,如下圖所示:
這裡寫圖片描述
這裡,我們並不用在意子樹A,B的高度了,他們的高度不會超過h,也不會少於h-1。
我們在K1處進行一次單旋轉,旋轉後的圖片如下:
這裡寫圖片描述
在這裡,可以看到,STree還是不平衡,也許,我們還要用同樣的原理來進行一次單旋轉。這次,我們把K3提到K2的位置來進行多一次單旋轉,結果如下圖右邊的樹所示:
這裡寫圖片描述
至此,旋轉完畢,容易驗證,在進行兩次旋轉後,得出的樹(圖4-35右邊的樹)是滿足AVL平衡特性的,這種旋轉叫雙旋轉

我們現在解決了情形1和情形2這兩種情況,下面給出這兩種情形的準確定義,讓我們把必須重新平衡的節點叫做a
情形1:對a的左兒子的左子樹進行一次插入。
情形2:對a的左兒子的右子樹進行一次插入。

到這裡可能會有點疑問,這些情形是否已經包括齊所有的情形了?比如情形1中,還需要對X進行拆分,繼續產生情形3,4…..n嗎?其實不用了,我們只要把X看成一個整體就行,我們只需要知道插入後X子樹的高度為h + 1,並不用去關心插入在什麼位置了。

第二種情況:從第一個平衡性被破壞的節點的右邊插入,從而導致的失衡

其實第二種情況就是第一種情況的對稱,只是換了一邊插入而已,其推導過程和第一種情況是一樣的。同樣的,具有兩種情形:
設a是必須重新平衡的節點,
情形3:對a的右兒子的左子樹進行一次插入。
情形4:對a的右兒子的右子樹進行一次插入。

這裡寫圖片描述

這裡寫圖片描述

在這裡對4種情形進行下總結:
情形1和情形4是關於a點的映象對稱,而2和3是關於a點的映象對稱。因此,理論上只有兩種情況,當然從程式設計的角度來看還是四種情形。
第一種情況是插入發生在“外邊”的情況(即左-左的情況或右-右的情況),該情況通過對樹的一次單旋轉而完成調整。第二種情況是插入發生在“內部”的情形(即左-右的情況或右-左的情況),該情況通過稍微複雜些的雙旋轉(兩次單旋轉)來處理。

3.實際的例子

接來下來演示一個例子。假設從初始的空AVL樹開始插入關鍵字3、2和1,然後依序插入4到7。在插入關鍵字 1時第一個問題出現了,AVL特性在根處被破壞。我們在根與其左兒子之間實施單旋轉修正這個問題。下面是旋轉之前和之後的兩棵樹:
這裡寫圖片描述
虛線連線要旋轉的兩個節點,它們是旋轉的主體。下面我們插入關鍵字為4的節點,這沒有問題,但插入5破壞了在節點3處的AVL特性,而通過單旋轉又將其修正。
這裡寫圖片描述
這裡需要注意的是,在程式設計的時候,要注意讓2的右兒子變成4,否則會導致4是不可訪問的。下面我們插入6。這在根節點產生一個平衡問題,因為它的左子樹高度是0而右子樹高度為2。因此我們在根處在2和4之間實施一次單旋轉。
這裡寫圖片描述
旋轉的結果使得2是4的一個兒子而4原來的左子樹變成節點2的新的右子樹。我們插入的下一個關鍵字是7,它導致另外的旋轉:
這裡寫圖片描述
下面演示雙旋轉的情況:
我們現在以倒序插入關鍵字10到16,接著插入8,然後再插入9。插入16容易,因為它並不破壞平衡特性,但插入15就會引起節點在7處的高度不平衡。這屬於情形3,需要通過一次右-左雙旋轉來解決,這個右-左雙旋轉將涉及7,16和15。
這裡寫圖片描述
下面我們插入14,它也需要一個雙旋轉。此時修復該樹的雙旋轉還是右-左雙旋轉,它將涉及到6、15和7。
這裡寫圖片描述
現在插入13,那麼在根處就會產生一個不平衡。由於13不在4和7之間,因此我們知道一次單旋轉就能完成修正的工作。
這裡寫圖片描述
插入12也要一個單旋轉:
這裡寫圖片描述
為了插入11,還需要進行一個單旋轉,對於其後的10的插入也需要這樣的旋轉。我們插入8不進行旋轉,這樣就建立了一棵近乎理想的平衡樹了。
這裡寫圖片描述
最後,我們插入9以演示雙旋轉的對稱情形。注意,9引起含有關鍵字10的節點產生不平衡。由於9在10和8之間(8是通向9的路徑上的節點10的兒子),因此需要進行一個雙旋轉。
這裡寫圖片描述

例子到此結束。

4.程式碼實現

下面是程式碼實現,就不詳細介紹了,畢竟本文著重的是推導過程:

#include <stdio.h>
#include <stdlib.h>

struct AvlNode;//樹的結點
typedef struct AvlNode *AvlPostion;//樹的指標
typedef struct AvlNode *AvlTree;//樹的指標
typedef int AvlElementType;//樹結點中的元素

#define Max(X,Y) ((X) > (Y) ? (X) : (Y))

struct AvlNode {
    AvlElementType element;
    AvlTree left;//左子樹
    AvlTree right;//右子樹
    int height;//樹的高度
};

void makeAvlEmpty(AvlTree tree) {
    if(tree != NULL){
        makeAvlEmpty(tree->left);
        makeAvlEmpty(tree->right);
        free(tree);
    }
}

//查詢關鍵字為x的相應的樹結點,並返回
AvlPostion findAvl(AvlElementType x,AvlTree tree) {
    if(tree == NULL){
        return NULL;
    }

    if(x < tree->element) {
        return findAvl(x,tree->left);
    }else if(x > tree->element) {
        return findAvl(x,tree->right);
    }else {
        return tree;
    }
}
//查詢最小的結點
AvlPostion findMinAvl(AvlTree tree) {
    if(tree != NULL) {
        while(tree->left) {
            tree = tree->left;
        }
    }
    return tree;
}
//查詢最大的結點
AvlPostion findMaxAvl(AvlTree tree) {
    if(tree != NULL) {
        while(tree->right) {
            tree = tree->right;
        }
    }
    return tree;
}
//返回樹的高度
static int height(AvlPostion p) {
    if(p == NULL) {
        return -1;
    }else {
        return p->height;
    }
}
/*如果k2有一個左兒子k1,則將k1作為根返回,k2變成k1的右兒子,並更新他們的高度*/
static AvlPostion singleRotateWithLeft(AvlPostion k2) {
    AvlPostion k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;

    k2->height = Max(height(k2->left),height(k2->right)) + 1;
    k1->height = Max(height(k1->left),k2->height) + 1;

    return k1;
}
/*如果k2有一個右兒子k1,則將k1作為根返回,k2變成k1的左兒子,並更新他們的高度*/
static AvlPostion singleRotateWithRight(AvlPostion k2) {
    AvlPostion k1 = k2->right;
    k2->right = k1->left;
    k1->left = k2;

    k2->height = Max(height(k2->left),height(k2->right)) + 1;
    k1->height = Max(k2->height,height(k1->right)) + 1;

    return k1;
}

static AvlPostion doubleRotateWithLeft(AvlPostion k3) {
    k3->left = singleRotateWithRight(k3->left);
    return singleRotateWithLeft(k3);
}

static AvlPostion doubleRotateWithRight(AvlPostion k3) {
    k3->right = singleRotateWithLeft(k3->right);
    return singleRotateWithRight(k3);
}

//最關鍵的插入方法
AvlTree insertAvl(AvlElementType x,AvlTree tree) {
    if(tree == NULL) {
        tree = (AvlPostion)malloc(sizeof(struct AvlNode));
        if(tree == NULL) {
            printf("create tree fail!\n");
        }else {
            tree->element = x;
            tree->left = NULL;
            tree->right = NULL;
            tree->height = 0;
        }
    }else if(x < tree->element) {
        //如果出來平衡性被破壞的情況,那麼一定是左子樹的高度更高
        tree->left = insertAvl(x,tree->left);//讓下次呼叫來更新tree->left;
        if(height(tree->left) - height(tree->right) == 2) {
            if(x < tree->left->element) {//是在tree的左兒子的左子樹中插入的
                tree = singleRotateWithLeft(tree);
            } else {//在tree的左兒子的右子樹中插入的
                tree = doubleRotateWithLeft(tree);
            }
        }
    }else if(x > tree->element) {
        tree->right = insertAvl(x,tree->right);
        if(height(tree->right) - height(tree->left) == 2) {
            if(x > tree->right->element) {
                tree = singleRotateWithRight(tree);
            }else {
                tree = doubleRotateWithRight(tree);
            }
        }
    }//如果有相等的節點,就什麼也不做
    //更新節點的高度資訊
    tree->height = Max(height(tree->left),height(tree->right)) + 1;
    return tree;//實現把上一次呼叫的節點更新的目的
}
//中序遍歷
void inorderTraversal(AvlTree tree) {
    if(tree != NULL) {
        inorderTraversal(tree->left);
        printf("%d ",tree->element);
        inorderTraversal(tree->right);
    }
}


int main() {
    int n;
    printf("請輸入結點數:\n");
    if(scanf("%d",&n) == 1) {
        int x;
        AvlTree tree = NULL;
        for(int i = 0; i < n; i++) {
            if(scanf("%d",&x) == 1) {
                tree = insertAvl(x,tree);
            }
        }
        inorderTraversal(tree);
    }
    printf("\n");
    return 0;
}

至此,整篇文章就完了!

相關推薦

AVL定義出發步步匯出旋轉方案

本文從AVL樹的定義出發,一步步地推匯出AVL樹旋轉的方案,這個推導是在已經清楚地知道AVL樹的定義這個前提下進行的。文章注重思考的過程,並不會直接給出AVL樹是怎樣旋轉的,用來提醒自己以後在學習的時候要注重推導的過程。在此,我要特別感謝下我的資料結構老師,是他

定義出發理解二叉搜尋

[leetcode Validate Binary Search Tree]從定義出發,理解二叉搜尋樹  書呆子的復仇 關注 2015.12.13 12:53* 字數 855 閱讀 181評論 0喜歡 2 附上原題: 一棵二叉樹如果屬於二

漸進式編碼規範步步面向過程到面向對象

span 編碼 element 保留 col spa style -1 方式 學習js這麽久了,一步步見證著自己的成長,看到別的大牛的代碼,一步步去完善自己,今天,我就來通過一個簡單的實例來記錄自己的進步。 通過輸入框輸入字符串,來顯示輸入的字符數量。 1.函數式編程(初

使用者互動場景出發歐瑞博MixPad要系統化定義智慧居住空間

  近兩年,智慧家居市場十分火爆,國內消費者對智慧家居產品的接受程度越來越高,不過,消費熱潮還集中在浪潮一般出現的各類智慧家居單品上。很難有一款產品能夠系統化地為消費者描繪全屋智慧家居解決方案,從而改變人們的居住空間。不過,歐瑞博近日推出的新產品MixPad,或許能夠從另一個角度

現代前端開發路線圖:零開始步步成為前端工程師

編者按:很多人都想學程式設計。但是苦於沒有具體的步驟和指導。比如想找份前端開發的工作,卻不知道應該先學習什麼再學習什麼,也不知道該選擇什麼樣的工具。因為經常被人問到類似的問題,全棧開發者Kamran Ahmed索性在github上制訂了一份現代前端開發的路線圖,並且用一篇文章

我是怎麼客服專員步步成為高階資料分析師

三年前,我還是在某媽網的客服專員。每天做著簡單重複的事情:打字,吃下使用者吐槽,打字,再吃一遍使用者的吐槽。機械式的重複,讓我週末跟朋友在一起都不能放開了玩。因為我知道週一我又要去面臨那低工資、機械式的工作。直到有一天,我通過觀察一個數據,幫公司確定了潛在的業務問題並且提出了

去哪兒網怎麽淪為騙子的平臺了步步揭開去哪兒網欺騙消費者的把戲

客服 讓我 支付 技術 發現 都江堰 去哪兒網 接機 stat 先讓我大哭一會兒 現在的去哪兒網真是牛擺哄哄,明目張膽誆騙老用戶啊。 好傷心。好難過,被騙了,被坑了。 之前一直在去哪兒訂機票,還沒發現有什麽不正確的地方 知道今天。我才悔恨不已啊, 此事還得從頭

全能自定義環境鍵快速安裝PHP7.2版本32/64位任選

normal phpwamp 下載地址 ott 相對 新版 round -o href 想要在windows環境下快速搭建最新的PHP版本,可以使用全能自定義PHP集成環境PHPWAMP_IN2全能自定義:PHPWAMP_IN2支持一鍵自定義Apache、nginx、PHP

科普貼 | 以太坊代幣錢包MyEtherWallet使用教程步步教你玩轉MEW

按鈕 wid isp 查詢 到你 pan fail VC oam MyEtherWallet 是一個以太坊的網頁錢包,使用非常簡單,打開網頁就可以使用,源代碼開源,不會在服務器上存儲用戶的錢包信息如私鑰和密碼。支持 Ledger Wallet、TREZOR 等硬件錢包

各種排序演算法步步更新(桶排序氣泡排序選擇排序快速排序

部分方法來自我關注的博主  J_小浩子  謝謝 1 桶排序  bucketsort 1 桶排序 #include <stdio.h>//桶排序基本說明 int main(){     int data1[11]={0},tem

#圖文詳解:實際和理論出發帶你瞭解Java中的多執行緒

這裡並沒有講什麼新東西,只是把多執行緒一些知識來個總結。大家懂得可以複習複習,還有些童鞋對多執行緒朦朧的可以拿這個做為入門~ 舉個栗子說明啥是多執行緒:玩遊戲,前面一堆怪,每個怪都是一個執行緒,你射了一槍,子彈飛出去了,這顆子彈也是一個執行緒。你開啟你的程序管理,看到你遊戲的後臺程序,這就是程序

unittest框架學習步步生成html格式測試報告

主要參考文章: https://blog.csdn.net/xiaoquantouer/article/details/75089200 unittest中最核心的四部分是: 1.test fixture(測試韌體) 2.test case(測試用例) 3.te

setContentView方法原始碼出發弄懂Activity的檢視是怎麼附屬在Window上的

前言 最近正在看Android開發藝術探索,看到了Window的建立過程中的Activity的建立過程,講到Activity的檢視是怎麼附屬在Window上的時候,書中分析了setContentView方法,但是說得比較簡略,一些方法也跟以前有了些改變,所以我

10小時到10分鐘步步優化巨量關鍵詞的匹配

問題由來 前些天工作中遇到一個問題: 有 60萬 條短訊息記錄日誌,每條約 50 字,5萬 關鍵詞,長度 2-8 字,絕大部分為中文。要求將這 60萬 條記錄中包含的關鍵詞全部提取出來並統計各關鍵詞的命中次數。 本文完整介紹了我的實現方式,看我如何將需要執行十小時的任務優化到十分鐘以內。雖然實現語言是 PH

裝置的引入能去分析的一些事

Linux3.x後設備樹的引入(背後是Linus大神一聲吼:fucking code,詳細可以參考下http://www.wowotech.net/device_model/why-dt.html),大家可以多看看www.wowotech.net/中的文章,很犀利。 Lin

概念到底層技術文看懂區塊鏈架構設計

原文地址 前言 區塊鏈作為一種架構設計的實現,與基礎語言或平臺等差別較大。區塊鏈是加密貨幣背後的技術,是當下與VR虛擬現實等比肩的熱門技術之一,本身不是新技術,類似Ajax,可以說它是一種技術架構,所以我們從架構設計的角度談談區塊鏈的技術實現。 無論你擅長

Python|三個例子步步教你學會爬蟲

網路爬蟲簡介 網路爬蟲,也叫網路蜘蛛(Web Spider)。它根據網頁地址(URL)爬取網頁內容,而網頁地址(URL)就是我們在瀏覽器中輸入的網站連結。比如:https://www.baidu.com/,它就是一個URL。   更多Python視訊、原始碼、資料加群9

線上安裝 Docker源自官網步步來就能成功試過redhat

在 CentOS/RHEL 中安裝 Docker 在終端中執行下面的命令安裝 Docker。 sudo yum install -y yum-utils sudo yum-config-manager \ --add-repo \ https://download.dao

客戶端到後臺文通吃

前記 最近一段時間,因為公司需求,需要轉向Java Web方向發展,Android得放下一段時間(不過還是會利用空餘時間堅持寫文章~)。 推送功能是app很常用的一個功能,目前能夠實現推送的第三方平臺也有不少,比如友盟、極光、信鴿等等,總之只要百度一下android推送關鍵字,就能看到很多的廠家。 這

Android執行時出發打造我們的脫殼神器

0x00 前言 之前對Android的兩個執行時的原始碼做了一些研究,又加上如火如荼的Android加固服務的興起,便產生了打造一個用於脫殼的執行時,於是便有了DexHunter的誕生(原始碼:https://github.com/zyq8709/DexHunter/)。