AVL搜索二叉樹C語言實現

什麼是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旋轉:

在根節點的左子樹的左節點下面插入一個節點:會導致失衡
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旋轉

在根節點的右子樹的右節點後面插入一個節點會導致失衡
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旋轉即可。
LR旋轉
代碼實現

static AVLNode* lr_rotate(AVLTree tree){
	tree->left = rr_rotate(tree->left);
	return ll_rotate(tree);
}

(4)RL旋轉

在根節點的右子樹的左節點的後面插入一個節點,導致失衡,我們發現其實只需要對根節點的右子樹進行一次LL旋轉,再對根進行一次RR旋轉即可。
RL旋轉
代碼實現

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.左邊高(被刪除的節點的左子樹高度大於右子樹高度)

情況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.右邊高(被刪除的節點的右子樹高度大於左子樹高度)

情況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;
	}
}

最後,一個簡單地AVL的一些基本操作實現函數就寫好了

全部源代碼

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