1. 程式人生 > 其它 >平衡二叉樹 C語言程式碼實現

平衡二叉樹 C語言程式碼實現

平衡二叉樹 C語言實現

平衡二叉樹挺有意思的,記錄一下。

1. 什麼是平衡二叉樹

平衡二叉樹,我們也稱【二叉平衡搜尋樹/AVL】,樹中任何節點的兩個子樹的高度最大差別為1,巴拉巴拉。。。(https://baike.baidu.com/item/AVL樹/10986648?fr=aladdin)

但是有個注意的點: 平衡二叉樹的前提是 二叉排序樹(https://baike.baidu.com/item/二叉搜尋樹/7077855?fr=aladdin)

這篇部落格主要總結平衡二叉樹,所以,二叉排序樹知識不會提及,但是會用到。

如果想要看 排序二叉樹調整為 平衡二叉樹 旋轉相關內容的,調整至 第5節。

平衡二叉樹

非平衡二叉樹

最小不平衡子樹節點為 130

左子樹深度為 1,右子樹深度為3 ,其差值大於1,所以不平衡

2. 如何判斷二叉樹最小不平衡子樹

最小不平衡子樹為 130 這顆子樹(黃色標註)

判定最小不平衡子樹的關鍵就在於,判斷 這棵樹的左子樹 和 右字數 深度之差是否大於1,若大於1 ,則證明該樹不平衡

檢查二叉樹是否平衡函式程式碼實現

typedef struct {
        int data; // 資料節點
        struct TreeNode *left; // 指向左子樹
        struct TreeNode *right; // 指向右子樹
} TreeNode , *PTreeNode;

// 記錄平衡二叉樹
bool BalanceTrue = false;
// 最小不平衡子樹地址
TreeNode *rjt = NULL;

// 檢查二叉樹是否平衡,若不平衡 BalanceTrue 為 true
int checkTreeBalance(TreeNode *root) {
        if (NULL == root) { return 0; }
        int x = checkTreeBalance(root->left);
        int y = checkTreeBalance(root->right);

        // 若檢測到最小不平衡二叉樹後,不進行後面的檢查
        if (BalanceTrue) return 0;
    
        int xx = abs(x-y);

        if (xx > 1) {
                // 左子樹 和 右子樹 相差大於1 , 二叉樹不平衡
                BalanceTrue = true;
                rjt = root;
        }
         
        return (x>y?x+1:y+1);
}

程式執行結果

# gcc -w -g -std=c11 BalanceTree.c 
# 
# ./a.out 
當前二叉樹遍歷
前序遍歷: 580    130     80      160     150     158     210     1590    900     2100    1900
中序遍歷: 80     130     150     158     160     210     580     900     1590    1900    2100
二叉樹不平衡,不平衡子樹根節點為: 130
# 

3. 二叉樹不平衡情況

在一顆平衡二叉樹的前提下,插入和刪除一個節點,都有可能會引起二叉樹不平衡,不平衡的情況主要有以下四種

左左更高

左右更高

右左更高

右右更高

4. 判斷不平衡二叉樹哪邊高

如上圖紅色所示,可以先根據最小不平衡二叉樹左子樹或者右子樹高,上圖所示,為右子樹高,則將最小不平衡二叉樹的右子樹作為樹根節點,繼續判斷子樹的左子樹或者右子樹高。
比如上圖的結果是右左較高,若進行調整的話,為 先讓不平衡子樹右節點的樹先向右旋轉,然後再向左旋轉

判斷不平衡二叉樹哪邊高程式碼實現

typedef struct {
        int data; // 資料節點
        struct TreeNode *left; // 指向左子樹
        struct TreeNode *right; // 指向右子樹
} TreeNode , *PTreeNode;

// 記錄平衡二叉樹
bool BalanceTrue = false;
// 最小不平衡子樹地址
TreeNode *rjt = NULL;

// 返回二叉樹樹高
int treeHeight(TreeNode *root) {
        if (NULL == root) return 0;
        int ll = treeHeight(root->left);
        int rr = treeHeight(root->right);
        return (ll>rr?ll+1:rr+1);
}

int main() {
    /*  構建二叉樹
    	判斷平衡,獲取最小不平衡子樹, 將資料存入 rjt 中
    	輸出二叉樹 前序/中序
    */
    if (BalanceTrue) {
        printf("二叉樹不平衡,不平衡子樹根節點為: %d\n",rjt->data);
    } else {
        return 0;
    };


    int ll = treeHeight(rjt->left);
    int rr = treeHeight(rjt->right);
    if (1 < ll - rr) {
        printf("左子樹高\n");
        TreeNode *rjt_ll = rjt->left;

        int child_ll = treeHeight(rjt_ll->left);
        int child_rr = treeHeight(rjt_ll->right);
        if (child_ll > child_rr) {
            printf("左子樹更高\n");
        } else if (child_rr > child_ll) {
            printf("右字數更高");
        }

    } else if (1 <  rr - ll) {
        printf("右子數高\n");
        TreeNode *rjt_rr = rjt->right;

        int child_ll = treeHeight(rjt_rr->left);
        int child_rr = treeHeight(rjt_rr->right);
        if (child_ll > child_rr) {
            printf("左子樹更高\n");
        } else if (child_rr > child_ll) {
            printf("右字數更高");
        }

    }

    return 0;
}

輸出

# gcc BalanceTree.c -w -g -std=c11
# 
# ./a.out 
當前二叉樹遍歷
前序遍歷:130    80      160     150     158     210
中序遍歷:80     130     150     158     160     210
二叉樹不平衡,不平衡子樹根節點為: 130
右子數高
左子樹更高
# 

5. 如何調整平衡二叉樹

所謂的旋轉,其實是修改指標指向的值,僅此而已。

二叉樹不平衡有四種情況

左左更高

原始二叉樹,若要調整為平衡二叉樹,需要整棵樹向右旋轉

調整1:整棵樹向右旋轉

左右更高

原始二叉樹,若要調整為平衡二叉樹,需要 先讓不平衡子樹左節點先向左旋轉,然後再向右旋轉

調整1: 先讓不平衡子樹左節點的樹先向左旋轉

調整2: 整棵樹向右

右左更高

原始二叉樹,若要調整為平衡二叉樹,需要 先讓不平衡子樹右節點的樹先向右旋轉,然後再向左旋轉

調整1: 先讓不平衡子樹右節點的樹先向右旋轉

調整2: 整棵樹向左

右右更高

原始二叉樹,若要調整為平衡二叉樹,需要 整棵樹向左旋轉

調整1: 整棵樹向左旋轉

全部程式碼

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

typedef struct {
	int data; // 資料節點
	struct TreeNode *left; // 指向左子樹
	struct TreeNode *right; // 指向右子樹
} TreeNode , *PTreeNode;

// 記錄平衡二叉樹
bool BalanceTrue = false;
// 最小不平衡子樹地址
TreeNode *rjt = NULL;

// 檢查二叉樹是否平衡,若不平衡 BalanceTrue 為 true
int checkTreeBalance(TreeNode *root) {
	if (NULL == root) { return 0; }
	int x = checkTreeBalance(root->left);
	int y = checkTreeBalance(root->right);
	
	// 若檢測到最小二叉樹後,不進行後面的檢查
	if (BalanceTrue) return 0;
	int xx = abs(x-y);

	if (xx > 1) {
		// 左子樹 和 右子樹 相差大於1 , 二叉樹不平衡
		BalanceTrue = true;
		rjt = root;
	}
	 
	return (x>y?x+1:y+1);
}

// 返回二叉樹樹高
int treeHeight(TreeNode *root) {
	if (NULL == root) return 0;
	int ll = treeHeight(root->left);
	int rr = treeHeight(root->right);
	return (ll>rr?ll+1:rr+1);
}

// 父節點查詢
TreeNode* queryTopData(TreeNode *root,int data) {
	// 空地址異常丟擲
	if (NULL == root) return NULL;
	
	// top: 父節點 ,如果為Null, 該節點為父節點
	// tmp: 遍歷查詢節點 
	TreeNode *top = NULL;
	TreeNode *tmp = root;

	while (tmp != NULL) {
		if (data == tmp->data) {
			// 節點為 返回Null
			if (NULL == top) return NULL;
			return top;
		}

		top = tmp;
		if (data > tmp->data) {
			tmp = tmp->right;
		} else if (data < tmp->data) {
			tmp = tmp->left;
		}
	}
	return NULL;
}

// 左左旋轉
//
// 不平衡二叉樹
//       70
//      /   \
//    50    80
//   /  \    
//  40  60
//  /
// 30
//
// 旋轉後平衡二叉樹(向右旋轉)
//
//    50
//  /   \
// 40    70
// /     /  \
//30   60    80
//
bool turnLL(TreeNode **root , TreeNode *notBalanceRoot) {
	
	if ((*root) != notBalanceRoot) {
		printf("左左旋轉,非根節點\n");
		// 非根節點
		TreeNode *lleft = notBalanceRoot->left;
		TreeNode *lright = lleft->right;
		
		// 查詢父節點
		TreeNode *fdata = queryTopData((*root),notBalanceRoot->data);
		if (NULL == fdata) return false;

		lleft->right = notBalanceRoot;
		notBalanceRoot->left = lright;
		
		if (notBalanceRoot == fdata->left) {
			fdata->left = lleft;
		} else if (notBalanceRoot == fdata->right) {
			fdata->right = lleft;
		}
		return true;

	} else {
		// 根節點
		printf("左左旋轉,是根節點\n");
		TreeNode *lleft = notBalanceRoot->left;
		TreeNode *absroot = lleft;
		TreeNode *lright = lleft->right;
		

		lleft->right = notBalanceRoot;
		notBalanceRoot->left = lright;

		(*root) = absroot;
		return true;
	}

}

// 左右旋轉
//不平衡二叉樹
//      70
//     /   \
//    50    80
//    / \    
//   40 60
//  /   
// 55
//
//左子樹向左
//      70
//     /   \
//    60    80
//    /
//   50
//  /  \    
// 40  55
//                                                           
//                                                                   
// 整棵樹向右
// 
//     60
//    /   \
//   50    70
//  /  \     \
// 40  55    80
//
bool turnLR(TreeNode **root , TreeNode *notBalanceRoot) {
	if ((*root) != notBalanceRoot) {
		printf("左右旋轉,非根節點");
		
		TreeNode *lleft = notBalanceRoot->left;
		TreeNode *leftRight = lleft->right;
		TreeNode *leftRightLeft = leftRight->left;

		// 第一次調整
		leftRight->left = lleft;
		lleft->right = leftRightLeft;
		notBalanceRoot->left = leftRight;


		// 查詢父節點
		TreeNode *fdata = queryTopData((*root),notBalanceRoot->data);
		//if (NULL != fdata) printf("fdata: %d\n",fdata->data);
		
		// 第二次調整
		lleft = notBalanceRoot->left;
		leftRight = lleft->right;
		
		lleft->right = notBalanceRoot;
		notBalanceRoot->left = leftRight;

		
		if (notBalanceRoot == fdata->left) {
			fdata->left = lleft;
		} else if (notBalanceRoot == fdata->right) {
			fdata->right = lleft;
		}
	} else {
		printf("左右旋轉,是根節點\n");

		TreeNode *lleft = notBalanceRoot->left;
		TreeNode *leftRight = lleft->right;
		TreeNode *leftRightLeft = leftRight->left;

		// 第一次調整
		leftRight->left = lleft;
		lleft->right = leftRightLeft;
		notBalanceRoot->left = leftRight;
		
		// 第二次調整
		lleft = notBalanceRoot->left;
		leftRight = lleft->right;
		
		lleft->right = notBalanceRoot;
		notBalanceRoot->left = leftRight;

		(*root) = lleft;
	}
}

// 右左旋轉
//不平衡二叉樹
//   70
//  /  \
// 50   80
//     /  \
//    75  88
//     \
//     77
//
//左子樹向右
//   70
//  /  \
// 50   75
//     /  \
//    77  80
//         \
//         88
//                                                                                                           
//                                                                                                                  
//                                                                                                                      
//整棵樹向左
//     75
//    /  \
//   70  80
//  /  \   \ 
// 50  77  88 
//
bool turnRL(TreeNode **root , TreeNode *notBalanceRoot) {
	TreeNode *rright = notBalanceRoot->right;
	TreeNode *rightLeft = rright->left;
	TreeNode *rightLeftRight = rightLeft->right;

	// 第一次調整
	rightLeft->right = rright;
	rright->left = rightLeftRight;
	notBalanceRoot->right = rightLeft;

	// 查詢父節點
	TreeNode *fdata = queryTopData((*root),notBalanceRoot->data);
	//if (NULL != fdata) printf("fdata: %d\n",fdata->data);

	// 第二次調整
	rright = notBalanceRoot->right;
	rightLeft = rright->left;

	rright->left = notBalanceRoot;
	notBalanceRoot->right = rightLeft;

	if ((*root) != notBalanceRoot) {
		printf("右左旋轉,非根節點\n");
		if (notBalanceRoot == fdata->left) {
			fdata->left = rright;
		} else if (notBalanceRoot == fdata->right) {
			fdata->right = rright;
		}
	
	} else {
		printf("右左旋轉,是根節點\n");
		(*root) = rright;
	}
}

// 右右旋轉
// 
// 不平衡二叉樹
//  70
// /  \
//50   80
//    /  \
//   75  88
//      /
//     85
//
//
//
//向左旋轉
//    80
//   /  \
//  70   88
// /  \   /  
//50  75 85  
bool turnRR(TreeNode **root , TreeNode *notBalanceRoot) {
	if ((*root) != notBalanceRoot) {
		printf("右右旋轉,非根節點");
		TreeNode *rright = notBalanceRoot->right;
		TreeNode *rleft = rright->left;

		// 查詢父節點
		TreeNode *fdata = queryTopData((*root),notBalanceRoot->data);
		if (NULL != fdata) printf("fdata: %d\n",fdata->data);

                rright->left = notBalanceRoot;
                notBalanceRoot->right = rleft;

                if (notBalanceRoot == fdata->left) {
                        fdata->left = rright;
                } else if (notBalanceRoot == fdata->right) {
                        fdata->right = rright;
                }				

	} else {
		// 右右旋轉,是根節點
		printf("右右旋轉,是根節點\n");
		TreeNode *rright = notBalanceRoot->right;
		TreeNode *absroot = rright;
		TreeNode *rleft = rright->left;

		rright->left = notBalanceRoot;
		notBalanceRoot->right = rleft;

		(*root) = absroot;

	}
}

// 二叉樹前序遍歷
void Print1(TreeNode *root) {
	if (NULL == root) return;
	printf("%d\t",root->data);
	Print1(root->left);
	Print1(root->right);
}

// 二叉樹中序遍歷
void Print2(TreeNode *root) {
	if (NULL == root) return;
	Print2(root->left);
	printf("%d\t",root->data);
	Print2(root->right);
}

// 二叉樹後續遍歷
void Print3(TreeNode *root) {
	if (NULL == root) return;
	Print3(root->left);
	Print3(root->right);
	printf("%d\t",root->data);
}

// 插入二叉樹節點
TreeNode* addNode(TreeNode *root,int data) {
	if (NULL == root) {
		// 頭節點插入
		TreeNode *Node = (TreeNode *)malloc(sizeof(TreeNode));
		if (NULL == Node) {
			printf("新節點申請記憶體失敗\n");
			return NULL;
		}
		Node->data = data;

		return Node;
	}

	TreeNode *tmp = root;
	TreeNode *top = NULL;

	// 找到合適的最尾巴節點
	while (NULL != tmp) {
		top = tmp;
		if (tmp->data == data) {
			printf("已經存在該節點,節點地址: %p\n",tmp);
			return root;
		}
		if (tmp->data < data) {
			tmp = tmp->right;
		} else if (tmp->data > data) {
			tmp = tmp->left;
		}
	}

	TreeNode *Node = (TreeNode *)malloc(sizeof(TreeNode));
	Node->data = data;
	if (NULL == Node) {
		printf("申請新節點記憶體失敗\n");
		return root;
	}

	// 連結節點
	if (data > top->data) {
		top->right = Node;
	} else if (data < top->data) {
		top->left = Node;
	}

	return root;
}


// 刪除二叉排序樹節點
bool DeleteTreeNode(TreeNode **TreeRoot,int data) {
	if (NULL == (*TreeRoot)) return false;

	printf("刪除節點: %d\n",data);

	TreeNode *tmp = (*TreeRoot);
	TreeNode *top = NULL;
	
	while (tmp != NULL) {
		if (tmp->data == data) {
			// 葉子節點
			if ((NULL == tmp->left) && (NULL == tmp->right)) {
				// 葉子節點
				if (NULL == top) {
					// 僅有根節點的葉子節點
					free(tmp);
					return true;
				} else {
					// 其他的葉子節點
					TreeNode *lastNode = top;
					if (tmp == lastNode->left) {
						lastNode->left = NULL;
					} else if (tmp == lastNode->right) {
						lastNode->right = NULL;
					}
					free(tmp);
					return true;
				}
			} else {
				// 非葉子節點
				// 演算法為: 
				// 預設演算法為: 1.  當刪除該節點時,獲取該樹右子樹最左子節點
				//             2.  當右子樹為空時,此時應該獲取左子樹最右端子節點

				if (NULL != tmp->right) {
					// 方案 1
					TreeNode *tmp2 = tmp->right;
					TreeNode *top2 = NULL;

					// 找到最後一個節點
					while (tmp2->left != NULL) {
						top2 = tmp2;
						tmp2 = tmp2->left;
					}

					// 刪除老的節點
					tmp->data = tmp2->data;
					// 只有右子樹節點 沒有左子樹節點
					if (NULL == top2) {
						tmp->right = NULL;

					} else {
						top2->left = NULL;
					}
					free(tmp2);
				} else {
					// 方案 2
					TreeNode *tmp2 = tmp->left;
					TreeNode *top2 = NULL;

					// 找到最後一個節點
					while (tmp2->right != NULL) {
						tmp2 = tmp2->right;
					}

					// 刪除老的節點
					tmp->data = tmp2->data;
					if (NULL == top2) {
						tmp->left = NULL;
					} else {
						top2->right = NULL;
					}
					free(tmp2);
				}
				
			}
		} else {
			top = tmp;
			if (data > tmp->data) {
				tmp = tmp->right;
			} else {
				tmp = tmp->left;
			}
		}
	}
	return false;
}

// 二叉樹平衡調整
bool treeBalance(TreeNode **root) {
	checkTreeBalance((*root));
	while (BalanceTrue) {
		printf("二叉樹不平衡,最小不平衡子樹資料結點: %d\n",rjt->data);
		TreeNode *tmp;

		if (1 < treeHeight(rjt->left) - treeHeight(rjt->right)) {
			// 對於不平衡二叉樹而言,左子樹比右子樹高
			//
			//printf("左\n");
			if (rjt->left != NULL) {
				tmp = rjt->left;
				int ll = treeHeight(tmp->left);
				int rr = treeHeight(tmp->right);
				
				if (ll > rr) {
					// 對於不平衡子樹 左子樹 而言, 左子樹比右子樹高
					// 左左旋轉

					turnLL(root,rjt);

				} else {
					// 對於不平衡子樹 左子樹 而言, 右子樹比左子樹高
					// 左右旋轉
					//
					turnLR(root ,rjt);
				}
			} 
		} else if (1 < treeHeight(rjt->right) - treeHeight(rjt->left)) {
			// 對於不平衡二叉樹而言,右子樹比左子樹高
			//
			//printf("右\n");
			if (rjt->right != NULL) {
				tmp = rjt->right;
				int ll = treeHeight(tmp->left);
				int rr = treeHeight(tmp->right);

				if (ll > rr) {
					//右左旋轉
					turnRL(root,rjt);
				} else {
					//右右旋轉
					turnRR(root,rjt);
				}
			}		
		}
		
		BalanceTrue = false;
		checkTreeBalance((*root));
		printf("二叉樹調整平衡後資料結點:\n");
		printf("前序遍歷:");
		Print1(*root);
		printf("\n");
		printf("中序遍歷:");
		Print2(*root);
		printf("\n");
		printf("\n");
	}

}

int main() {
	TreeNode *root = NULL;

	printf("平衡二叉樹插入測試\n");
	int nums[] = {65,60,70,55,40,63,69,66,68,77};
	int i;
	for (i=0;i<sizeof(nums)/sizeof(int);i++) {
		printf("插入資料: %d\n",nums[i]);

		root = addNode(root,nums[i]);
		if (NULL == root) {
			printf("首節點申請失敗"); 
			return -1;
		}

		treeBalance(&root);
		sleep(1);

	}
	printf("\n當前二叉樹遍歷\n");
	printf("前序遍歷:");
	Print1(root);
	printf("\n");
	printf("中序遍歷:");
	Print2(root);
	printf("\n");
	//return 0;

	printf("\n\n平衡二叉樹刪除測試\n");

	for (i=2;i<5;i++) {
		DeleteTreeNode(&root,nums[i]);

		treeBalance(&root);
		sleep(1);
	}

	printf("\n當前二叉樹遍歷\n");
	printf("前序遍歷:");
	Print1(root);
	printf("\n");
	printf("中序遍歷:");
	Print2(root);
	printf("\n");
		
	return 0;
}

程式執行結果

# gcc BalanceTree.c -w -g -std=c11
# 
# ./a.out 
平衡二叉樹插入測試
插入資料: 65
插入資料: 60
插入資料: 70
插入資料: 55
插入資料: 40
二叉樹不平衡,最小不平衡子樹資料結點: 60
左左旋轉,非根節點
二叉樹調整平衡後資料結點:
前序遍歷:65     55      40      60      70
中序遍歷:40     55      60      65      70

插入資料: 63
二叉樹不平衡,最小不平衡子樹資料結點: 65
左右旋轉,是根節點
二叉樹調整平衡後資料結點:
前序遍歷:60     55      40      65      63      70
中序遍歷:40     55      60      63      65      70

插入資料: 69
插入資料: 66
二叉樹不平衡,最小不平衡子樹資料結點: 70
左左旋轉,非根節點
二叉樹調整平衡後資料結點:
前序遍歷:60     55      40      65      63      69      66      70
中序遍歷:40     55      60      63      65      66      69      70

插入資料: 68
二叉樹不平衡,最小不平衡子樹資料結點: 65
右左旋轉,非根節點
二叉樹調整平衡後資料結點:
前序遍歷:60     55      40      66      65      63      69      68      70
中序遍歷:40     55      60      63      65      66      68      69      70

插入資料: 77
二叉樹不平衡,最小不平衡子樹資料結點: 60
右右旋轉,是根節點
二叉樹調整平衡後資料結點:
前序遍歷:66     60      55      40      65      63      69      68      70      77
中序遍歷:40     55      60      63      65      66      68      69      70      77


當前二叉樹遍歷
前序遍歷:66     60      55      40      65      63      69      68      70      77
中序遍歷:40     55      60      63      65      66      68      69      70      77


平衡二叉樹刪除測試
刪除節點: 70
刪除節點: 55
刪除節點: 40
二叉樹不平衡,最小不平衡子樹資料結點: 60
右左旋轉,非根節點
二叉樹調整平衡後資料結點:
前序遍歷:66     63      60      65      69      68      77
中序遍歷:60     63      65      66      68      69      77


當前二叉樹遍歷
前序遍歷:66     63      60      65      69      68      77
中序遍歷:60     63      65      66      68      69      77
# 
歡迎轉發! 請保留源地址: https://www.cnblogs.com/NoneID