什麼是AVL搜索二叉樹?
AVL樹本質上還是一棵二叉搜索樹,它的特點是:
1.本身首先是一棵二叉搜索樹。
2.帶有平衡條件:每個結點的左右子樹的高度之差的絕對值(平衡因子)最多爲1。
也就是說,AVL樹,本質上是帶了平衡功能的二叉查找樹(二叉排序樹,二叉搜索樹)。
AVL搜索二叉樹的類型定義及頭文件:
typedef struct AVLNode{
int key; //用來存儲節點所保存的值
int height; //用來記錄節點的高度
struct AVLNode *left; //左兒子節點
struct AVLNode *right; //右兒子節點
}AVLNode;
//定義兩個宏:一個用MAX來求左右兒子高度的最大者
// 一個HEIGHT用來求樹的高度
#define MAX(a,b) ((a)>(b)?(a):(b))
#define HEIGHT(p) ((p==NULL)?0:(((AVLNode *)(p))->height))
//定義樹的類型
typedef struct AVLNode* AVLTree; //AVL樹的定義
AVL搜索二叉樹操作函數聲明
// 判斷這個AVL是否是一棵空樹
bool avltree_is_empty(AVLTree tree);
// AVL樹的節點個數
size_t avltree_size(AVLTree tree);
//AVL搜索二叉樹的高度
size_t avltree_height(AVLTree tree);
// 查找數據 查找成功返回true
bool avltree_find(AVLTree tree,int key);
// 中序遍歷(從小到大)
void avltree_mid_foreach(AVLTree tree,void(*func)(AVLNode*));
//層序遍歷(需要用到隊列)
void avltree_level_foreach(AVLTree tree,void(*func)(AVLNode*));
//二叉搜索樹的插入一個節點
int avltree_insert(AVLTree *ptree,int key);
//二叉搜索樹刪除節點
int avltree_delete(AVLTree *ptree,int key);
//銷燬這棵AVL搜索二叉樹
void avltree_destroy(AVLTree tree);
函數代碼實現
接下來我們把這些函數一個一個的實現
1.判斷這個AVL搜索二叉樹是否爲空?
bool avltree_is_empty(AVLTree tree){ //判斷是否爲空
return tree == NULL; //只需要判斷根節點是否爲NULL即可
}
2.獲得AVL搜索二叉樹的節點個數
我們選擇用遞歸的方式來實現:節點個數=本身+左子樹的節點個數+右子樹的節點個數
size_t avltree_size(AVLTree tree){
if(!tree){
return 0;
}
return avltree_size(tree->left)+avltree_size(tree->right) + 1;
3.獲得AVL搜索二叉樹的高度
AVL樹的高度即等於根節點的高度
size_t avltree_height(AVLTree tree){
if(!tree){
return 0;
}
return tree->height;
}
4.查找AVL樹中是否有某個元素,有返回ture,沒有返回NULL
選擇用遞歸的方法:如果根的值等於搜索的值,成功找到,如果搜索的元素小於根的值去左子樹找,如果大於就去右子樹找
bool avltree_find(AVLTree tree,int key){
if(!tree){ //沒有找到返回false
return false;
}
if(key == tree->key){
return true;
}
if(key < tree->key){
return avltree_find(tree->left,key);
}
if(key > tree->key){
return avltree_find(tree->right,key);
}
}
5.中序遍歷
先遍歷左子樹,再遍歷根節點,最後遍歷右子樹,並通過func這個函數指針返回樹的節點以供用戶使用
void avltree_mid_foreach(AVLTree tree,void(*func)(AVLNode*)){
if(tree){
avltree_mid_foreach(tree->left,func);
func(tree);
avltree_mid_foreach(tree->right,func);
}
}
6.層序遍歷
先遍歷第一層,再一層一層的往下遍歷
我們需要使用到隊列這個數據結構,我先提供隊列的函數,並不多做講解:
#ifndef _QUEUE_H__
#define _QUEUE_H__
#include "AVLtree.h"
typedef struct Queue{
void **vect; //萬能指針,可以存儲任意類型的數據
size_t size; //隊列大小
size_t cnt; //當前數據個數
size_t fir; //用來保存隊首的位置 隊尾的位置可以用(fir+cnt)%size來獲得
}Queue;
void queue_init(Queue *que,size_t size){ //初始化隊列
que->vect = malloc(sizeof(AVLNode*)*size);
que->size = size;
que->cnt = 0;
que->fir = 0;
}
bool queue_is_empty(Queue *que){ //判斷隊列是否爲空
return que->cnt == 0;
}
void queue_push(Queue *que,void *data){ //入隊
que->vect[(que->cnt +que->fir)%que->size] = data;
que->cnt++;
}
void *queue_pop(Queue *que){ //出隊
void *data = que->vect[que->fir];
que->fir = (que->fir+1)%que->size;
que->cnt--;
return data;
}
void queue_destroy(Queue *que){ //銷燬隊列,回收內存
free(que->vect);
que->vect = NULL;
}
#endif //queue.h
層序遍歷的實現
如果根節點不爲空,就把根入隊,用一個節點來接受出隊的節點,再判斷這個出隊節點的左右子節點是否爲空,如果不爲空就一一入隊,詳細的可以看註釋(注意:入隊的是節點,不是節點的值,這是關鍵點,這樣我們就可以通過這個出隊的節點來訪問到它的左右節點)
void avltree_level_foreach(AVLTree tree,void(*func)(AVLNode *)){
Queue que;
queue_init(&que,avltree_size(tree)); //對列初始化
if(tree){ //如果根不爲空,就把根節點入隊
queue_push(&que,tree);
}
while(!queue_is_empty(&que)){ //如果隊列不爲空就一直重複入隊出隊操作
tree = queue_pop(&que); //用tree來接收出隊的節點
func(tree); //供用戶使用
if(tree->left){ //如果這個出隊的節點tree的左節點存在,
queue_push(&que,tree->left); //那就把左節點入隊
}
if(tree->right){ //如果右節點存在,那就右節點入隊
queue_push(&que,tree->right);
}
}
}
7.銷燬AVL搜索二叉樹,回收內存
先遞歸的銷燬左子樹和右子樹,再銷燬根節點
void avltree_destroy(AVLTree tree){
if(!tree){
avltree_destroy(tree->left);
avltree_destroy(tree->right);
free(tree);
}
tree = NULL;
}
8.AVL搜索二叉樹的節點插入
由於AVL搜索二叉樹的條件是要求平衡,即左右子樹高度差不超過1,所以我們在插入一個節點後,該樹的平衡性可能會遭到破壞,此時爲了同時保持平衡性質和搜索樹的性質,我們需要對插入節點的局部樹進行旋轉。
(1)LL旋轉:
在根節點的左子樹的左節點下面插入一個節點:會導致失衡
代碼實現
static AVLNode* ll_rotate(AVLTree tree){
AVLNode *node = tree->left; //記錄根節點的左兒子,用於返回
tree->left = node->right; // 左兒子的右節點接到根節點的左節點
node->right = tree; //根節點接到左兒子的右節點處
tree->height = MAX(HEIGHT(tree->left),HEIGHT(tree->right))+1; //更新根節點的高度(根節點的左右子樹的高度的較大者)加上自身的高度
node->height = MAX(HEIGHT(node->left),tree->height)+1; //更新高度,同上
return node;
}
(PS:有了上面的LL旋轉 下面的三個旋轉大家都可以參照LL旋轉來理解這些代碼的含義,意思都相同,就是方向不同而已,所以不多做累述)
(2)RR旋轉
在根節點的右子樹的右節點後面插入一個節點會導致失衡
代碼實現
static AVLNode* rr_rotate(AVLTree tree){
AVLNode *node = tree->right;
tree->right = node->left;
node->left = tree;
tree->height = MAX(HEIGHT(tree->left),HEIGHT(tree->right))+1;
node->height = MAX(HEIGHT(node->right),tree->height)+1;
return node;
}
(3)LR旋轉
在根節點的左子樹的右節點的後面插入一個節點,導致失衡,我們發現其實只需要對根節點的左子樹進行一次RR旋轉,再對根進行一次LL旋轉即可。
代碼實現
static AVLNode* lr_rotate(AVLTree tree){
tree->left = rr_rotate(tree->left);
return ll_rotate(tree);
}
(4)RL旋轉
在根節點的右子樹的左節點的後面插入一個節點,導致失衡,我們發現其實只需要對根節點的右子樹進行一次LL旋轉,再對根進行一次RR旋轉即可。
代碼實現
static AVLNode* rl_rotate(AVLTree tree){
tree->right = ll_rotate(tree->right);
return rr_rotate(tree);
}
(5)我們再寫一個插入節點的函數(初始化插入的節點)
static AVLNode *create_avlnode(int key){
AVLNode *node;
if((node = malloc(sizeof(AVLNode)))==NULL){
return NULL;
}
node->key = key;
node->height = 1;
node->right = NULL;
node->left = NULL;
return node;
}
插入節點的代碼實現
我們選擇用遞歸實現插入操作,可能比較難理解,大家請仔細看註釋
int avltree_insert(AVLTree *ptree,int key){ //傳入二級指針來保存樹的地址(即節點的地址),形參是地址,我們可以對實參的值進行修改
if(!(*ptree)){ //如果當前樹爲NULL,那這就是我們需要插入的位置
*ptree = create_avlnode(key); //在*ptree這個位置插入節點
if(*ptree == NULL){ //如果動態內存申請失敗,就返回-1
return -1;
}
return 0; //成功插入返回0
}
int ret = 0; //定義一個變量來記錄遞歸調用的返回值並且返回到上一層函數中去,這樣上一層的函數才知道遞歸調用的下一層是否插入成功
if(key < (*ptree)->key){ //如果我們插入的值小於根節點的值
ret = avltree_insert(&(*ptree)->left,key); //那我們就去根節點的左邊插入該節點,遞歸調用插入函數
if(ret == 0){ //如果返回值告訴我們已經插入成功了
if(HEIGHT((*ptree)->left)-HEIGHT((*ptree)->right) == 2){ //我們需要判斷該AVL樹是否失衡
//如果失衡,因爲我們是在左邊進行插入操作,所以肯定是左子樹的高度大於右子樹的高度
int hl = HEIGHT((*ptree)->left->left); //記錄左子樹的左子樹的高度
int hr = HEIGHT((*ptree)->left->right); //記錄左子樹的右子樹的高度
//這樣我們可以判斷是需要進行哪種旋轉
if(hl>hr){ //如果左邊高,那就是LL旋轉
*ptree = ll_rotate(*ptree);
}else{ //如果是右邊高,那就是LR旋轉
*ptree = lr_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新該節點的高度
}
return ret; //將插入的結果返回遞歸調用的上一層函數中,並且上一層的函數會根據這個返回值來進行下一步的操作
}else if(key > (*ptree)->key){ //如果插入的值大於根節點的值
ret = avltree_insert(&(*ptree)->right,key); //記錄返回值
if(ret == 0){ //插入成功
if(HEIGHT((*ptree)->right)-HEIGHT((*ptree)->left) == 2){ //失衡
int hl = HEIGHT((*ptree)->right->left);
int hr = HEIGHT((*ptree)->right->right);
if(hl < hr){ //右子樹的右邊高
*ptree = rr_rotate(*ptree); //RR旋轉
}else{ //左邊高
*ptree = rl_rotate(*ptree); //RL旋轉
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
}
return ret; //返回插入結果
}else{ //如果樹種已經存在該值
return -1; //那就返回-1
}
}
9.AVL搜索二叉樹的刪除節點
在我們刪除一個節點的時候,如何操作才能讓我們的AVL搜索二叉樹儘量少失衡或者不失衡呢?
首先我們分以下幾種情況來討論(假設我們已經找到需要刪除的節點)。
(1)該節點的左右節點都存在
1.左邊高(被刪除的節點的左子樹高度大於右子樹高度)
比如我們需要刪除8這個節點,此時節點8的左子樹高度高度右子樹的高度,我們需要把要被被刪除的節點的左子樹的最大值節點覆蓋掉(即值覆蓋)被刪除節點,如上圖的把節點8的值賦值爲7,然後再把7這個節點刪除掉(遞歸調用刪除節點函數)
代碼實現
if(HEIGHT(node->left)>HEIGHT(node->right)){//左邊高
AVLNode *max = node->left; //記錄左子樹
while(max->right){ //找到左子樹中最大值的節點
max = max->right;
}
node->key = max->key; //覆蓋
ret = avltree_delete(&(node->left),max->key); //遞歸刪除這個最大值的節點
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
2.右邊高(被刪除的節點的右子樹高度大於左子樹高度)
如上圖,我們需要刪除6這個節點,此時被刪除的節點6它的右子樹高度高於左子樹的高度,我們可以用右子樹中的最小值的節點來覆蓋掉被刪除的節點,然後遞歸調用刪除函數用來刪除這個最小值的節點。即用7這個節點覆蓋掉6這個節點(即值覆蓋),然後遞歸刪除7這個節點。
代碼實現
}else{
AVLNode *min = node->right;
while(min->left){
min = min->left;
}
node->key = min->key;
ret = avltree_delete(&(node->right),min->key);
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
(2)該節點的左右子樹都不存在
那就直接刪除這個節點
if(!node->left && !node->right){
free(*ptree);
*ptree = NULL;
}
(3)該節點的左右子樹存在一個
把被刪除節點的存在的子節點上提,然後刪除該節點
AVLNode *denode = *ptree;
*ptree = (*ptree)->left!=NULL?(*ptree)->left:(*ptree)->right;
free(denode);
(4)如果被刪除的值小於根節點的值,去左子樹中刪除
(5)如果被刪除的值大於根節點的值,去右子樹中刪除
整體代碼實現
int avltree_delete(AVLTree *ptree,int key){
if(!(*ptree)){ //如果被刪除的節點不存在,返回-1
return -1;
}
int ret = 0; //記錄遞歸調用的返回值
if((*ptree)->key == key){ //找到要被刪除的節點
AVLNode *node = *ptree;
if(node->left && node->right){ //左右節點都存在
if(HEIGHT(node->left)>HEIGHT(node->right)){//左邊高
AVLNode *max = node->left;
while(max->right){
max = max->right;
}
node->key = max->key;
ret = avltree_delete(&(node->left),max->key); //遞歸刪除該最大值節點
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1; //更新高度
}else{
AVLNode *min = node->right;
while(min->left){
min = min->left;
}
node->key = min->key;
ret = avltree_delete(&(node->right),min->key);
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
}else if(!node->left && !node->right){ //如果左右節點都不存在
free(*ptree);
*ptree = NULL;
}else{ //如果只存在左節點或者右節點
AVLNode *denode = *ptree;
*ptree = (*ptree)->left!=NULL?(*ptree)->left:(*ptree)->right;
free(denode);
}
return 0;
}else if(key < (*ptree)->key){ //被刪除的值小於根節點的值
ret = avltree_delete(&(*ptree)->left,key);
if(ret == 0){
if(HEIGHT((*ptree)->right)-HEIGHT((*ptree)->left) == 2){
int hl = HEIGHT((*ptree)->right->left);
int hr = HEIGHT((*ptree)->right->right);
if(hl<hr){
*ptree = rr_rotate(*ptree);
}else{
*ptree = rl_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
return ret;
}else{ //被刪除的值大於被刪除的節點的值
ret = avltree_delete(&(*ptree)->right,key);
if(ret == 0){
if(HEIGHT((*ptree)->left)-HEIGHT((*ptree)->right) == 2){
int hl = HEIGHT((*ptree)->left->left);
int hr = HEIGHT((*ptree)->left->right);
if(hl > hr){
*ptree = ll_rotate(*ptree);
}else{
*ptree = lr_rotate(*ptree);
}
}
(*ptree)->height = MAX(HEIGHT((*ptree)->left),HEIGHT((*ptree)->right))+1;
}
return ret;
}
}