精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

AVL 樹和普通的二叉查找樹的詳細區別分析

算法與數據結構 ? 2018-01-15 14:36 ? 次閱讀

背景

AVL 樹是一棵平衡的二叉查找樹,于 1962 年,G. M. Adelson-Velsky 和 E. M. Landis 在他們的論文《An algorithm for the organization of information》中發表。

所謂的平衡之意,就是樹中任意一個結點下左右兩個子樹的高度差不超過 1。(本文對于樹的高度約定為:空結點高度是 0,葉子結點高度是 1。)

那 AVL 樹和普通的二叉查找樹有何區別呢?如圖,如果我們插入的是一組有序上升或下降的數據,則一棵普通的二叉查找樹必然會退化成一個單鏈表,其查找效率就降為 O(n)。而 AVL 樹因其平衡的限制,可以始終保持 O(logn) 的時間復雜度。

具體實現與代碼分析

在我們進行完插入或刪除操作后,很可能會導致某個結點失去平衡,那么我們就需要把失衡結點旋轉一下,使其重新恢復平衡。

經過分析,不管是插入還是刪除,它們都會有四種失衡的情況:左左失衡,右右失衡,左右失衡,右左失衡。因此每次遇到失衡時,我們只需判斷一下是哪個失衡,再對其進行相對應的恢復平衡操作即可。

好,下面以插入操作為例,來看下這四種失衡的廬山真面目。(以下統一約定:紅色結點為新插入結點,y 結點為失衡結點)

(1)左左失衡

 AVL 樹和普通的二叉查找樹的詳細區別分析

所謂的左左,即 "失衡結點" 的左子樹比右子樹高 2,左孩子下的左子樹比右子樹高 1。

我們只需對 "以 y 為根的子樹" 進行 "左左旋轉 (ll_rotate)" 即可。一次旋轉后,恢復平衡。

Node * AVL::ll_rotate(Node * y)

{

Node * x = y->left;

y->left = x->right;

x->right = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

(2)右右失衡

所謂的右右,即 "失衡結點" 的右子樹比左子樹高 2,右孩子下的右子樹比左子樹高 1。

我們只需對 "以 y 為根的子樹" 進行 "右右旋轉 (rr_rotate)" 即可。一次旋轉后,恢復平衡。

Node * AVL::rr_rotate(Node * y)

{

Node * x = y->right;

y->right = x->left;

x->left = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

(3)左右失衡

 AVL 樹和普通的二叉查找樹的詳細區別分析

所謂的左右,即 "失衡結點" 的左子樹比右子樹高 2,左孩子下的右子樹比左子樹高 1。

觀察發現,若先對 "以 x 為根的子樹" 進行 "右右旋轉 (rr_rotate)",此時 "以 y 為根的子樹" 恰好符合 "左左失衡",所以再進行一次 "左左旋轉 (ll_rotate)"。兩次旋轉后,恢復平衡。

Node * AVL::lr_rotate(Node * y)

{

Node * x = y->left;

y->left = rr_rotate(x);

return ll_rotate(y);

}

(4)右左失衡

 AVL 樹和普通的二叉查找樹的詳細區別分析

所謂的右左,即 "失衡結點" 的右子樹比左子樹高 2,右孩子下的左子樹比右子樹高 1。

觀察發現,若先對 "以 x 為根的子樹" 進行 "左左旋轉 (ll_rotate)",此時 "以 y 為根的子樹" 恰好符合 "右右失衡",所以再進行一次 "右右旋轉 (rr_rotate)"。兩次旋轉后,恢復平衡。

Node * AVL::rl_rotate(Node * y)

{

Node * x = y->right;

y->right = ll_rotate(x);

return rr_rotate(y);

}

插入操作

插入成功后,在遞歸回溯時依次對經過的結點判斷是否失衡,若失衡就需要對其進行對應的旋轉操作使其恢復平衡,在這期間,原先作為一棵子樹的根結點就會因為旋轉被替換,因此設置insert_real( )返回的是新根結點,這樣就可以實時更新根結點。

插入操作實現代碼如下:

int AVL::get_height(Node * node)

{

if (node == nullptr)

return 0;

return node->height;

}

int AVL::get_balance(Node * node)

{

if (node == nullptr)

return 0;

return get_height(node->left) - get_height(node->right);

}

Node * AVL::insert_real(int key, Node * node)

{

if (node == nullptr)

return new Node(key);

if (key < node->key)

node->left = insert_real(key, node->left);

else if (key > node->key)

node->right = insert_real(key, node->right);

else

return node;

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) > 0)

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) < 0)

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::insert(int key)

{

header->left = insert_real(key, header->left);

}

查找操作

Node * AVL::find_real(int key, Node * node)

{

if (node == nullptr)

return nullptr;

if (key < node->key)

return find_real(key, node->left);

else if (key > node->key)

return find_real(key, node->right);

else

return node;

}

Node * AVL::find(int key)

{

return find_real(key, header->left);

}

刪除操作

刪除操作的四種失衡情況和插入操作一樣,讀者可以參考前文。下面是刪除操作的實現代碼:

Node * AVL::erase_real(int key, Node * node)

{

if (node == nullptr)

return node;

if (key < node->key)

node->left = erase_real(key, node->left);

else if (key > node->key)

node->right = erase_real(key, node->right);

else

{

if (node->left && node->right)

{

// 找到后繼結點

Node * x = node->right;

while (x->left)

x = x->left;

// 后繼直接復制

node->key = x->key;

// 轉化為刪除后繼

node->right = erase_real(x->key, node->right);

}

else

{

Node * t = node;

node = node->left ? node->left : node->right;

delete t;

if (node == nullptr)

return nullptr;

}

}

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) >= 0) // 需要加等號

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) <= 0) // 需要加等號

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::erase(int key)

{

header->left = erase_real(key, header->left);

}

完整代碼

/**

*

* author : 劉毅(Limer)

* date : 2017-08-17

* mode : C++

*/

#include

#include

using namespace std;

struct Node

{

int key;

int height;

Node * left;

Node * right;

Node(int key = 0)

{

this->key = key;

this->height = 1;

this->left = this->right = nullptr;

}

};

class AVL

{

private:

Node * header;

private:

Node * ll_rotate(Node * y);

Node * rr_rotate(Node * y);

Node * lr_rotate(Node * y);

Node * rl_rotate(Node * y);

void destroy(Node * node);

int get_height(Node * node);

int get_balance(Node * node);

Node * insert_real(int key, Node * node);

Node * find_real(int key, Node * node);

Node * erase_real(int key, Node * node);

void in_order(Node * node);

public:

AVL();

~AVL();

void insert(int key);

Node * find(int key);

void erase(int key);

void print();

};

Node * AVL::ll_rotate(Node * y)

{

Node * x = y->left;

y->left = x->right;

x->right = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

Node * AVL::rr_rotate(Node * y)

{

Node * x = y->right;

y->right = x->left;

x->left = y;

y->height = max(get_height(y->left), get_height(y->right)) + 1;

x->height = max(get_height(x->left), get_height(x->right)) + 1;

return x;

}

Node * AVL::lr_rotate(Node * y)

{

Node * x = y->left;

y->left = rr_rotate(x);

return ll_rotate(y);

}

Node * AVL::rl_rotate(Node * y)

{

Node * x = y->right;

y->right = ll_rotate(x);

return rr_rotate(y);

}

void AVL::destroy(Node * node)

{

if (node == nullptr)

return;

destroy(node->left);

destroy(node->right);

delete node;

}

int AVL::get_height(Node * node)

{

if (node == nullptr)

return 0;

return node->height;

}

int AVL::get_balance(Node * node)

{

if (node == nullptr)

return 0;

return get_height(node->left) - get_height(node->right);

}

Node * AVL::insert_real(int key, Node * node)

{

if (node == nullptr)

return new Node(key);

if (key < node->key)

node->left = insert_real(key, node->left);

else if (key > node->key)

node->right = insert_real(key, node->right);

else

return node;

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) > 0)

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) < 0)

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

Node * AVL::find_real(int key, Node * node)

{

if (node == nullptr)

return nullptr;

if (key < node->key)

return find_real(key, node->left);

else if (key > node->key)

return find_real(key, node->right);

else

return node;

}

Node * AVL::erase_real(int key, Node * node)

{

if (node == nullptr)

return node;

if (key < node->key)

node->left = erase_real(key, node->left);

else if (key > node->key)

node->right = erase_real(key, node->right);

else

{

if (node->left && node->right)

{

// 找到后繼結點

Node * x = node->right;

while (x->left)

x = x->left;

// 后繼直接復制

node->key = x->key;

// 轉化為刪除后繼

node->right = erase_real(x->key, node->right);

}

else

{

Node * t = node;

node = node->left ? node->left : node->right;

delete t;

if (node == nullptr)

return nullptr;

}

}

node->height = max(get_height(node->left), get_height(node->right)) + 1;

int balance = get_balance(node);

// 左左失衡

if (balance > 1 && get_balance(node->left) >= 0) // 需要加等號

return ll_rotate(node);

// 右右失衡

if (balance < -1 && get_balance(node->right) <= 0) // 需要加等號

return rr_rotate(node);

// 左右失衡

if (balance > 1 && get_balance(node->left) < 0)

return lr_rotate(node);

// 右左失衡

if (balance < -1 && get_balance(node->right) > 0)

return rl_rotate(node);

return node;

}

void AVL::in_order(Node * node)

{

if (node == nullptr)

return;

in_order(node->left);

cout << node->key << " ";

in_order(node->right);

}

AVL::AVL()

{

header = new Node(0);

}

AVL::~AVL()

{

destroy(header->left);

delete header;

header = nullptr;

}

void AVL::insert(int key)

{

header->left = insert_real(key, header->left);

}

Node * AVL::find(int key)

{

return find_real(key, header->left);

}

void AVL::erase(int key)

{

header->left = erase_real(key, header->left);

}

void AVL::print()

{

in_order(header->left);

cout << endl;

}

int main()

{

AVL avl;

// test "insert"

avl.insert(7);

avl.insert(2);

avl.insert(1); avl.insert(1);

avl.insert(5);

avl.insert(3);

avl.insert(6);

avl.insert(4);

avl.insert(9);

avl.insert(8);

avl.insert(11); avl.insert(11);

avl.insert(10);

avl.insert(12);

avl.print(); // 1 2 3 4 5 6 7 8 9 10 11 12

// test "find"

Node * p = nullptr;

cout << ((p = avl.find(2)) ? p->key : -1) << endl;? ?//? 2

cout << ((p = avl.find(100)) ? p->key : -1) << endl; // -1

// test "erase"

avl.erase(1);

avl.print(); // 2 3 4 5 6 7 8 9 10 11 12

avl.erase(9);

avl.print(); // 2 3 4 5 6 7 8 10 11 12

avl.erase(11);

avl.print(); // 2 3 4 5 6 7 8 10 12

return 0;

}

起初構造的 AVL 樹為下圖:

總結

和二叉查找樹相比,AVL 樹的特點是時間復雜度更穩定,但缺點也是很明顯的。

插入操作中,至多需要一次恢復平衡操作,遞歸回溯的量級為 O(logn)。有一點需要我們注意,在對第一個失衡結點進行恢復平衡后,遞歸回溯就應該立即停止(因為失衡結點的父親及其祖先們肯定都是處于平衡狀態的)。

但讓 "遞歸的回溯" 中途停止,不好實現,所以我上面的編碼程序都不可避免的會繼續回溯,直到整棵樹的根結點,而這些回溯都是沒有必要的。(謝謝 LLL 的提醒,若在結點中增設父親結點,就可以解決遞歸回溯的問題)

刪除操作中,若存在失衡,則至少需要一次恢復平衡操作,遞歸回溯的量級亦為 O(logn)。與插入操作不同,當對第一個失衡結點恢復平衡后,它的父親或者是它的祖先們也可能是非平衡的(見下圖,刪除 1),所以刪除操作的回溯很有必要。

沒有參照物對比的探討是沒有意義的,所以此文就止于此吧,有興趣的朋友可以看下我后面《紅黑樹》及《AVL 樹與紅黑樹的對比》的文章。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • AVL
    AVL
    +關注

    關注

    0

    文章

    14

    瀏覽量

    10025
  • 二叉樹
    +關注

    關注

    0

    文章

    74

    瀏覽量

    12283

原文標題:一文讀懂 AVL 樹

文章出處:【微信號:TheAlgorithm,微信公眾號:算法與數據結構】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    二叉查找(GIF動圖講解)

    二叉查找(Binary Search Tree),也稱二叉搜索,是指一棵空或者具有下列性質
    發表于 07-29 15:24

    二叉樹層次遍歷算法的驗證

    實現二叉樹的層次遍歷算法,并對用”A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))”創建的二叉樹進行測試。
    發表于 11-28 01:05 ?2023次閱讀
    <b class='flag-5'>二叉樹</b>層次遍歷算法的驗證

    詳解電源二叉樹到底是什么

    作為數據結構的基礎,分很多種,像 AVL 、紅黑二叉搜索....今天我想分享的是關于
    的頭像 發表于 06-06 15:05 ?9780次閱讀
    詳解電源<b class='flag-5'>二叉樹</b>到底是什么

    PCB板設計的電源二叉樹分析詳細資料說明

    本文檔的主要內容詳細介紹的是PCB板設計的電源二叉樹分析詳細資料說明。
    發表于 07-29 08:00 ?0次下載
    PCB板設計的電源<b class='flag-5'>二叉樹</b><b class='flag-5'>分析</b><b class='flag-5'>詳細</b>資料說明

    C語言二叉樹代碼免費下載

    本文檔的主要內容詳細介紹的是C語言二叉樹代碼免費下載。
    發表于 08-27 08:00 ?1次下載

    紅黑(Red Black Tree)是一種自平衡的二叉搜索

    平衡(Balance):就是當結點數量固定時,左右子樹的高度越接近,這棵二叉樹越平衡(高度越低)。而最理想的平衡就是完全二叉樹/滿二叉樹,高度最小的二叉樹
    的頭像 發表于 07-01 15:05 ?5457次閱讀
    紅黑<b class='flag-5'>樹</b>(Red Black Tree)是一種自平衡的<b class='flag-5'>二叉</b>搜索<b class='flag-5'>樹</b>

    二叉樹操作的相關知識和代碼詳解

    是數據結構中的重中之重,尤其以各類二叉樹為學習的難點。在面試環節中,二叉樹也是必考的模塊。本文主要講二叉樹操作的相關知識,梳理面試常考的內容。請大家跟隨小編一起來復習吧。 本篇針對面
    的頭像 發表于 12-12 11:04 ?1948次閱讀
    <b class='flag-5'>二叉樹</b>操作的相關知識和代碼詳解

    二叉樹的前序遍歷非遞歸實現

    我們之前說了二叉樹基礎及二叉的幾種遍歷方式及練習題,今天我們來看一下二叉樹的前序遍歷非遞歸實現。 前序遍歷的順序是, 對于中的某節點,先遍歷該節點,然后再遍歷其左子樹,最后遍歷其右子
    的頭像 發表于 05-28 13:59 ?1857次閱讀

    如何修剪二叉搜索

    ? 如果不對遞歸有深刻的理解,本題有點難。單純移除一個節點那還不夠,要修剪! 669. 修剪二叉搜索 ? 給定一個二叉搜索,同時給定最小邊界L 和最大邊界 R。通過修剪
    的頭像 發表于 10-11 14:16 ?1304次閱讀

    二叉排序樹AVL如何實現動態平衡

    熟悉的二叉樹種類有二叉搜索(排序、查找)二叉平衡、伸展
    的頭像 發表于 10-28 17:02 ?1656次閱讀
    <b class='flag-5'>二叉排序樹</b><b class='flag-5'>AVL</b>如何實現動態平衡

    C語言數據結構:什么是二叉樹

    完全二叉樹:完全二叉樹是效率很高的數據結構。對于深度為K,有n個節點的二叉樹,當且僅當每一個節點都與深度為K的滿二叉樹中編號從1至n的節點一一對應時,稱為完全
    的頭像 發表于 04-21 16:20 ?2325次閱讀

    怎么就能構造成二叉樹呢?

    一直跟著公眾號學算法的錄友 應該知道,我在二叉樹:構造二叉樹登場!,已經講過,只有 中序與后序 和 中序和前序 可以確定一顆唯一的二叉樹。前序和后序是不能確定唯一的二叉樹的。
    的頭像 發表于 07-14 11:20 ?1382次閱讀

    使用C語言代碼實現平衡二叉樹

    這篇博客主要總結平衡二叉樹,所以,二叉排序樹知識不會提及,但是會用到。
    的頭像 發表于 09-21 11:00 ?965次閱讀

    二叉樹的代碼實現

    二叉樹的主要操作有遍歷,例如有先序遍歷、中序遍歷、后序遍歷。在遍歷之前,就是創建一棵二叉樹,當然,還需要有刪除二叉樹的算法。
    的頭像 發表于 01-18 10:41 ?1118次閱讀
    <b class='flag-5'>二叉樹</b>的代碼實現

    C++自定義二叉樹并輸出二叉樹圖形

    使用C++構建一個二叉樹并輸出。
    的頭像 發表于 01-10 16:29 ?1535次閱讀
    C++自定義<b class='flag-5'>二叉樹</b>并輸出<b class='flag-5'>二叉樹</b>圖形