1. 程式人生 > >二叉平衡樹(AVL樹)插入、刪除的C語言實現

二叉平衡樹(AVL樹)插入、刪除的C語言實現

對於AVL樹的定義,在教科書和網上的資料都已經十分詳細,在這裡直接上程式碼,不做過多贅述。

一、AVL樹的結構體

typedef struct AVLTREE {
	int data;
	int height;
	struct AVLTREE* leftChlid;
	struct AVLTREE* rightChild;
}AVLTREE;

再貼出兩個工具函式的程式碼:

max():返回兩個數的最大值

geiHeight():取得節點的高度

int max(int data1, int data2) {
	return ((data1 > data2) ? data1 : data2);
}

int getHeight(AVLTREE* root) {
	if(root == NULL) 
		return -1;
	else 
		return root->height;
}

其中data為節點的值,而height為節點的高度,而AVL實現起來的難點就在於旋轉和對高度這個引數的把控,先來看看AVL旋轉的四種情況

二、AVL樹的旋轉

1.LL型


此時二叉平衡樹是不平衡的,我們假設葉子節點的高度都為0

那麼高度為0的節點有1、6、12,高度為1的節點有2,高度為2的節點有4,高度為3的節點有8

此時,根節點的左孩子高度為2,而右孩子的高度為0,此時樹是不平衡的,需要對其進行旋轉,貼出LL型的旋轉程式碼:

AVLTREE* left_Left_Rotation(AVLTREE* root) {
AVLTREE* newRoot = NULL;

newRoot = root->leftChlid;
root->leftChlid = newRoot->rightChild;
newRoot->rightChild = root;

root->height = max(getHeight(root->leftChlid),getHeight(root->rightChild)) + 1;
newRoot->height = max(getHeight(newRoot->leftChlid), root->height) + 1;

return newRoot;
}

此時,將根節點8傳入,即可完成旋轉,下面再給出旋轉後的樹


對於指標熟悉的朋友來說,旋轉時指標的改變是非常容易理解的,而在旋轉後重新形成了平衡二叉樹,這時每個節點的高度也發生了變化,這時一個令人疑惑的地方,來變數跟蹤研究一下:

在程式碼完成旋轉後,會開始執行這兩行程式碼:

        root->height = max(getHeight(root->leftChlid),getHeight(root->rightChild)) + 1;
	newRoot->height = max(getHeight(newRoot->leftChlid), root->height) + 1;

在執行之前,root為節點8,它的高度是3,在執行第一行程式碼時,取得它左右孩子節點6和節點12的高度,它們高度都為0,那麼會將root的高度更新為1。而newRoot為節點4,此時它的高度為2,它的左右孩子分別是節點2和節點8,節點高度都為1,那麼此時newRoot節點的高度為2。通過下面的圖,檢視節點的更新


旋轉後,高度為0的節點有1、6、12,高度為1的節點有2、8,而高度為2的節點只有4,不存在不平衡的情況了。

2.RR型

參考圖上紅點,紅點為節點的高度。如果你已經看懂了上面LL型的例子,那麼理所當然的能明白,此時節點8的左右孩子高度差變為了2,二叉樹需要旋轉,給出旋轉的程式碼:

AVLTREE* right_Right_Rotation(AVLTREE* root) {
	AVLTREE* newRoot = NULL;

	newRoot = root->rightChild;
	root->rightChild = newRoot->leftChlid;
	newRoot->leftChlid = root;

	root->height = max(getHeight(root->leftChlid),getHeight(root->rightChild)) + 1;
	newRoot->height = max(getHeight(newRoot->rightChild), root->height) + 1;

	return newRoot;
}

進行旋轉過後的樹和修正後的高度為如下圖:


3.LR型

LR型是一種特殊情況,前面兩次LL和RR都是單旋轉,而LR型和RL型是雙旋轉,通過下面的圖來看看這種情況:


這個時候不能進行單旋轉,需要先對根節點的左子樹進行一次RR單旋轉,然後再對整體進行一次LL單旋轉,先給出程式碼:

AVLTREE* left_Right_Rotation(AVLTREE* root) {
	root->leftChlid = right_Right_Rotation(root->leftChlid);

	return left_Left_Rotation(root);
}

可以看出,雙旋轉是對單旋轉的呼叫,再讀懂上面程式碼的基礎上,理解雙旋轉也不是什麼難事。首先對根節點的左子樹進行旋轉:


根節點的左子樹旋轉後:


這個時候樹還是不平衡的,但是我們這個時候可以將整個樹進行一次LL單旋轉,旋轉並且修正過後如下圖:


4.RL型

如果看懂了LR型,那麼RL型的也就非常容易了,先貼出程式碼:

AVLTREE* right_Left_Rotation(AVLTREE* root) {
	root->rightChild = left_Left_Rotation(root->rightChild);

	return right_Right_Rotation(root);
}

下圖即為旋轉後的圖:


三、AVL樹的插入

如果節點為空,那麼建立一個節點
下面也分別給出了當要插入的節點大於或小於或等於當前節點的情況
當一個節點的左右孩子高度差為2時,說明樹需要旋轉
至於是單旋轉還是雙旋轉,得看插入的位置是左子樹還是右子樹

然後根據相應結構,選擇單旋轉或者雙旋轉

建議手動過一遍這裡的程式碼,在插入過程需要旋轉時,不妨畫出樹的結構

然後就很容易的看出端倪了

AVLTREE* insertPoint(int data, AVLTREE* root) {
	
	if(root == NULL) {
		root = (AVLTREE *)malloc(sizeof(AVLTREE));
		root->data = data;
		root->height = 0;
		root->leftChlid = root->rightChild = NULL;
	} else if(data < root->data) {
		root->leftChlid = insertPoint(data, root->leftChlid);
		
		if(getHeight(root->leftChlid) - getHeight(root->rightChild) == 2) {	
			if(data < root->leftChlid->data)
				root = left_Left_Rotation(root);
			else
				root = left_Right_Rotation(root);
		}
	} else if(data > root->data) {
		root->rightChild = insertPoint(data, root->rightChild);
		if(getHeight(root->rightChild) - getHeight(root->leftChlid) == 2) {
			if(data > root->rightChild->data)
				root = right_Right_Rotation(root);
			else
				root = right_Left_Rotation(root);
		}
	} else if(data == root->data) {
		return NULL;
	}

	root->height = max(getHeight(root->leftChlid), getHeight(root->rightChild)) + 1;
	return root;
}

四、AVL樹的刪除

AVL樹的刪除分為以下三種情況:

1.刪除的節點沒有左右孩子,即刪除葉子節點

2.刪除的節點有左右孩子(包括根節點)

3.刪除的節點只有一個孩子(只有左孩子或者右孩子)

分析好這幾種情況,可以手工過一遍程式碼,但是要特別注意被讓遞迴搞壞了思路,得一步步來

AVLTREE* deletePoint(int data, AVLTREE* root) {
	// 根為空 或者 沒有要刪除的節點,直接返回NULL
	if(root == NULL || data == NULL) {
		return NULL;
	}

	// 待刪除的節點在root的左子樹
	if(data < root->data) {
		root->leftChlid = deletePoint(data, root->leftChlid);
		// 刪除節點後,若AVL樹失去平衡,則進行相應的調節
		if(abs(getHeight(root->rightChild) - getHeight(root->leftChlid)) == 2) {
			AVLTREE* p = root->rightChild;
		
			if(getHeight(p->leftChlid) > getHeight(p->rightChild)) {
				root = right_Left_Rotation(root);
			} else {
				root = right_Right_Rotation(root);
			}
		}
	// 待刪除的節點在root的右子樹
	} else if(data > root->data) {
		root->rightChild = deletePoint(data, root->rightChild);
		// 刪除節點後,若AVL樹失去平衡,則進行相應的調節
		if(abs(getHeight(root->leftChlid) - getHeight(root->rightChild)) == 2) {
			AVLTREE* p = root->leftChlid;
			if(getHeight(p->rightChild) > getHeight(p->leftChlid)) {
				root = left_Right_Rotation(root);
			} else {
				root = left_Left_Rotation(root);
			}
		}
	// root就是要刪除的節點
	} else if(data == root->data) {
		//左右孩子非空
		if(root->leftChlid && root->rightChild) {

			if(getHeight(root->leftChlid) > getHeight(root->rightChild)) {
				// 如果root的左子樹比右子樹高;
                // 則找出root的左子樹中的最大節點
                //   將該最大節點的值賦值給root
                //   刪除該最大節點
                // 這類似於用root的左子樹中最大節點做root的替身
                // 刪除root的左子樹中最大節點之後,AVL樹仍然是平衡的
				AVLTREE* max = getMaxNum(root->leftChlid);
				root->data = max->data;
				root->leftChlid = deletePoint(max->data, root->leftChlid);
			} else {
				// 如果root的左子樹比右子樹低;
                // 則找出root的左子樹中的最小節點
                //   將該最小節點的值賦值給root
                //   刪除該最小節點
                // 這類似於用root的右子樹中最小節點做root的替身
                // 刪除root的左子樹中最小節點之後,AVL樹仍然是平衡的
				AVLTREE* min = getMinNum(root->rightChild);
				root->data = min->data;
				root->rightChild = deletePoint(min->data, root->rightChild);
			}
		}

		else {
			//這種情況為左孩子為空右孩子不為空、或者右孩子為空左孩子不為空、左右孩子都為空時的處理方法
			//直接通過一個三目運算,即可完美解決
			AVLTREE* temp = root;
	
			root = root->leftChlid ? root->leftChlid : root->rightChild;
		
			destroy(temp);
		}
	}

	return root;
}

五、原始碼與總結

在正確理解思路後,其實AVL樹並不難,在編寫時要注意遞迴函式編寫的嚴謹性即可,以下是原始碼:

#include<stdio.h>
#include<malloc.h>
#include<math.h>

typedef struct AVLTREE {
	int data;
	int height;
	struct AVLTREE* leftChlid;
	struct AVLTREE* rightChild;
}AVLTREE;

typedef unsigned char boolean;  
#define TRUE            1  
#define FALSE           0  

static int i = 1;

void showBtreeByLeft(AVLTREE* head);  
void showBtreeByMid(AVLTREE* head);
AVLTREE* left_Left_Rotation(AVLTREE* root);
AVLTREE* right_Right_Rotation(AVLTREE* root);
AVLTREE* left_Right_Rotation(AVLTREE* root);
AVLTREE* right_Left_Rotation(AVLTREE* root);
AVLTREE* insertPoint(int data, AVLTREE* root);
AVLTREE* deletePoint(int data, AVLTREE* root);
int getHeight(AVLTREE* root);
int max(int data1, int data2);
AVLTREE* getMaxNum(AVLTREE* root);
AVLTREE* getMinNum(AVLTREE* root);
void destroy(AVLTREE* root);

void destroy(AVLTREE* root) {
	if(root == NULL) {
		return;
	}

	root->leftChlid = NULL;
	root->rightChild = NULL;
	root = NULL;
}

AVLTREE* getMinNum(AVLTREE* root) {
	if(root == NULL) {
		return NULL;
	}

	while(root->leftChlid != NULL) {
		root = root->leftChlid;
	}
	return root;
}

AVLTREE* getMaxNum(AVLTREE* root) {
	if(root == NULL) {
		return NULL;
	}

	while(root->rightChild != NULL) {
		root = root->rightChild;
	}
	return root;
}

int max(int data1, int data2) {
	return ((data1 > data2) ? data1 : data2);
}

int getHeight(AVLTREE* root) {
	if(root == NULL) 
		return -1;
	else 
		return root->height;
}

AVLTREE* deletePoint(int data, AVLTREE* root) {
	// 根為空 或者 沒有要刪除的節點,直接返回NULL
	if(root == NULL || data == NULL) {
		return NULL;
	}

	// 待刪除的節點在root的左子樹
	if(data < root->data) {
		root->leftChlid = deletePoint(data, root->leftChlid);
		// 刪除節點後,若AVL樹失去平衡,則進行相應的調節
		if(abs(getHeight(root->rightChild) - getHeight(root->leftChlid)) == 2) {
			AVLTREE* p = root->rightChild;
		
			if(getHeight(p->leftChlid) > getHeight(p->rightChild)) {
				root = right_Left_Rotation(root);
			} else {
				root = right_Right_Rotation(root);
			}
		}
	// 待刪除的節點在root的右子樹
	} else if(data > root->data) {
		root->rightChild = deletePoint(data, root->rightChild);
		// 刪除節點後,若AVL樹失去平衡,則進行相應的調節
		if(abs(getHeight(root->leftChlid) - getHeight(root->rightChild)) == 2) {
			AVLTREE* p = root->leftChlid;
			if(getHeight(p->rightChild) > getHeight(p->leftChlid)) {
				root = left_Right_Rotation(root);
			} else {
				root = left_Left_Rotation(root);
			}
		}
	// root就是要刪除的節點
	} else if(data == root->data) {
		//左右孩子非空
		if(root->leftChlid && root->rightChild) {

			if(getHeight(root->leftChlid) > getHeight(root->rightChild)) {
				// 如果root的左子樹比右子樹高;
                // 則找出root的左子樹中的最大節點
                //   將該最大節點的值賦值給root
                //   刪除該最大節點
                // 這類似於用root的左子樹中最大節點做root的替身
                // 刪除root的左子樹中最大節點之後,AVL樹仍然是平衡的
				AVLTREE* max = getMaxNum(root->leftChlid);
				root->data = max->data;
				root->leftChlid = deletePoint(max->data, root->leftChlid);
			} else {
				// 如果root的左子樹比右子樹低;
                // 則找出root的左子樹中的最小節點
                //   將該最小節點的值賦值給root
                //   刪除該最小節點
                // 這類似於用root的右子樹中最小節點做root的替身
                // 刪除root的左子樹中最小節點之後,AVL樹仍然是平衡的
				AVLTREE* min = getMinNum(root->rightChild);
				root->data = min->data;
				root->rightChild = deletePoint(min->data, root->rightChild);
			}
		}

		else {
			//這種情況為左孩子為空右孩子不為空、或者右孩子為空左孩子不為空、左右孩子都為空時的處理方法
			//直接通過一個三目運算,即可完美解決
			AVLTREE* temp = root;
	
			root = root->leftChlid ? root->leftChlid : root->rightChild;
		
			destroy(temp);
		}
	}

	return root;
}
/*
		如果節點為空,那麼建立一個節點
		下面也分別給出了當要插入的節點大於或小於或等於當前節點的情況
		當一個節點的左右孩子高度差為2時,說明樹需要旋轉
		至於是單旋轉還是雙旋轉,得看插入的位置是左子樹還是右子樹
		然後根據相應結構,選擇單旋轉或者雙旋轉

		建議手動過一遍這裡的程式碼,在插入過程需要旋轉時,不妨畫出樹的結構
		然後就很容易的看出端倪了
*/
AVLTREE* insertPoint(int data, AVLTREE* root) {
	
	if(root == NULL) {
		root = (AVLTREE *)malloc(sizeof(AVLTREE));
		root->data = data;
		root->height = 0;
		root->leftChlid = root->rightChild = NULL;
	} else if(data < root->data) {
		root->leftChlid = insertPoint(data, root->leftChlid);
		
		if(getHeight(root->leftChlid) - getHeight(root->rightChild) == 2) {	
			if(data < root->leftChlid->data)
				root = left_Left_Rotation(root);
			else
				root = left_Right_Rotation(root);
		}
	} else if(data > root->data) {
		root->rightChild = insertPoint(data, root->rightChild);
		if(getHeight(root->rightChild) - getHeight(root->leftChlid) == 2) {
			if(data > root->rightChild->data)
				root = right_Right_Rotation(root);
			else
				root = right_Left_Rotation(root);
		}
	} else if(data == root->data) {
		return NULL;
	}

	root->height = max(getHeight(root->leftChlid), getHeight(root->rightChild)) + 1;
	return root;
}

AVLTREE* right_Left_Rotation(AVLTREE* root) {
	root->rightChild = left_Left_Rotation(root->rightChild);

	return right_Right_Rotation(root);
}

AVLTREE* left_Right_Rotation(AVLTREE* root) {
	root->leftChlid = right_Right_Rotation(root->leftChlid);

	return left_Left_Rotation(root);
}

AVLTREE* right_Right_Rotation(AVLTREE* root) {
	AVLTREE* newRoot = NULL;

	newRoot = root->rightChild;
	root->rightChild = newRoot->leftChlid;
	newRoot->leftChlid = root;

	root->height = max(getHeight(root->leftChlid),getHeight(root->rightChild)) + 1;
	newRoot->height = max(getHeight(newRoot->rightChild), root->height) + 1;

	return newRoot;
}

AVLTREE* left_Left_Rotation(AVLTREE* root) {
	AVLTREE* newRoot = NULL;

	newRoot = root->leftChlid;
	root->leftChlid = newRoot->rightChild;
	newRoot->rightChild = root;

	root->height = max(getHeight(root->leftChlid),getHeight(root->rightChild)) + 1;
	newRoot->height = max(getHeight(newRoot->leftChlid), root->height) + 1;

	return newRoot;
}

void showBtreeByMid(AVLTREE* head) {  
    if(head == NULL) {  
        return;  
    }  
    showBtreeByMid(head->leftChlid);  
    printf("%d ", head->data);  
    showBtreeByMid(head->rightChild);  
}  
  
void showBtreeByLeft(AVLTREE* head) {  
    if(head == NULL) {  
        return;  
    }  

    printf("%d ", head->data);  
    showBtreeByLeft(head->leftChlid);  
    showBtreeByLeft(head->rightChild);  
}

void main(void) {
	AVLTREE* root = NULL;
	root = insertPoint(20,root);
	root = insertPoint(10,root);
	root = insertPoint(25,root);
	root = insertPoint(8,root);
    root = insertPoint(24,root);
	root = insertPoint(30,root);
	root = insertPoint(29,root);
	printf("\n");
	printf("按先序輸出可得:\n");
	showBtreeByLeft(root);
	printf("\n");
	printf("按中序輸出可得:\n");
	showBtreeByMid(root);
	printf("\n");
	root = deletePoint(24,root);
	printf("\n");
	printf("按先序輸出可得:\n");
	showBtreeByLeft(root);
	printf("\n");
	printf("按中序輸出可得:\n");
	showBtreeByMid(root);
	printf("\n");
}