這個系列是我多年前找工做時對數據結構和算法總結,其中有基礎部分,也有各大公司的經典的面試題,最先發布在CSDN。現整理爲一個系列給須要的朋友參考,若有錯誤,歡迎指正。本系列完整代碼地址在 這裏。node
在說二叉樹前,先來看看什麼是樹。樹中基本單位是結點,結點之間的連接,稱爲分支。一棵樹最上面的結點稱之爲根節點,而下面的結點爲子結點。一個結點能夠有0個或多個子結點,沒有子結點的結點咱們稱之爲葉結點。git
二叉樹是指子結點數目不超過2個的樹,它是一種很經典的數據結構。而二叉搜索樹(BST)是有序的二叉樹,BST須要知足以下條件:github
本文接下來會從定義,二叉搜索樹的增刪查以及二叉樹的遞歸和非遞歸遍歷進行整理。 下一篇文章會對二叉樹相關的經典面試題進行全面解析,本文代碼在 這裏。面試
咱們先定義一個二叉樹的結點,以下:算法
typedef struct BTNode {
int value;
struct BTNode *left;
struct BTNode *right;
} BTNode;
複製代碼
其中 value
存儲值,left
和 right
指針分別指向左右子結點。二叉搜索樹跟二叉樹可使用同一個結構,只是在插入或者查找時會有不一樣。bash
接下來看看二叉樹和二叉查找樹的一些基本操做,包括BST插入結點,BST查找結點,BST最大值和最小值,二叉樹結點數目和高度等。二叉查找樹(BST)特有的操做都在函數前加了 bst
前綴區分,其餘函數則是二叉樹通用的。數據結構
分配內存,初始化值便可。數據結構和算法
/**
* 建立BTNode
*/
BTNode *newNode(int value)
{
BTNode *node = (BTNode *)malloc(sizeof(BTNode));
node->value = value;
node->left = node->right = NULL;
return node;
}
複製代碼
插入結點能夠用遞歸或者非遞歸實現,若是待插入值比根節點值大,則插入到右子樹中,不然插入到左子樹中。以下圖所示(圖來自參考資料1,2,3):函數
/**
* BST中插入值,遞歸方法
*/
/**
* BST中插入結點,遞歸方法
*/
BTNode *bstInsert(BTNode *root, int value)
{
if (!root)
return newNode(value);
if (root->value > value) {
root->left = bstInsert(root->left, value);
} else {
root->right = bstInsert(root->right, value);
}
return root;
}
/**
* BST中插入結點,非遞歸方法
*/
BTNode *bstInsertIter(BTNode *root, int value)
{
BTNode *node = newNode(value);
if (!root)
return node;
BTNode *current = root, *parent = NULL;
while (current) {
parent = current;
if (current->value > value)
current = current->left;
else
current = current->right;
}
if (parent->value >= value)
parent->left = node;
else
parent->right = node;
return root;
}
複製代碼
刪除結點稍微複雜一點,要考慮3種狀況:post
left
或者 right
指針置空便可。bstSearchIter
函數),並將其值替換到待刪除結點中,而後遞歸調用刪除函數刪除該結點左子樹最大結點便可。/**
* BST中刪除結點
*/
BTNode *bstDelete(BTNode *root, int value)
{
BTNode *parent = NULL, *current = root;
BTNode *node = bstSearchIter(root, &parent, value);
if (!node) {
printf("Value not found\n");
return root;
}
if (!node->left && !node->right) {
// 狀況1:待刪除結點是葉子結點
if (node != root) {
if (parent->left == node) {
parent->left = NULL;
} else {
parent->right = NULL;
}
} else {
root = NULL;
}
free(node);
} else if (node->left && node->right) {
// 狀況2:待刪除結點有兩個子結點
BTNode *predecessor = bstMax(node->left);
bstDelete(root, predecessor->value);
node->value = predecessor->value;
} else {
// 狀況3:待刪除結點只有一個子結點
BTNode *child = (node->left) ? node->left : node->right;
if (node != root) {
if (node == parent->left)
parent->left = child;
else
parent->right = child;
} else {
root = child;
}
free(node);
}
return root;
}
複製代碼
注意在非遞歸查找中會將父結點也記錄下來。
/**
* BST查找結點-遞歸
*/
BTNode *bstSearch(BTNode *root, int value)
{
if (!root) return NULL;
if (root->value == value) {
return root;
} else if (root->value > value) {
return bstSearch(root->left, value);
} else {
return bstSearch(root->left, value);
}
}
/**
* BST查找結點-非遞歸
*/
BTNode *bstSearchIter(BTNode *root, BTNode **parent, int value)
{
if (!root) return NULL;
BTNode *current = root;
while (current && current->value != value) {
*parent = current;
if (current->value > value)
current = current->left;
else
current = current->right;
}
return current;
}
複製代碼
最小值結點從左子樹遞歸查找,最大值結點從右子樹遞歸找。
/**
* BST最小值結點
*/
BTNode *bstMin(BTNode *root)
{
if (!root->left)
return root;
return bstMin(root->left);
}
/**
* BST最大值結點
*/
BTNode *bstMax(BTNode *root)
{
if (!root->right)
return root;
return bstMax(root->right);
}
複製代碼
/**
* 二叉樹結點數目
*/
int btSize(BTNode *root)
{
if (!root) return 0;
return btSize(root->left) + btSize(root->right) + 1;
}
/**
* 二叉樹高度
*/
int btHeight(BTNode *root)
{
if (!root) return 0;
int leftHeight = btHeight(root->left);
int rightHeight = btHeight(root->right);
int maxHeight = leftHeight > rightHeight ? leftHeight+1 : rightHeight+1;
return maxHeight;
}
複製代碼
二叉樹遍歷的遞歸實現比較簡單,直接給出代碼。這裏值得一提的是層序遍歷,先是計算了二叉樹的高度,而後調用的輔助函數依次遍歷每一層的結點,這種方式比較容易理解,雖然在時間複雜度上會高一些。
/**
* 二叉樹先序遍歷
*/
void preOrder(BTNode *root)
{
if (!root) return;
printf("%d ", root->value);
preOrder(root->left);
preOrder(root->right);
}
/**
* 二叉樹中序遍歷
*/
void inOrder(BTNode *root)
{
if (!root) return;
inOrder(root->left);
printf("%d ", root->value);
inOrder(root->right);
}
/**
* 二叉樹後序遍歷
*/
void postOrder(BTNode *root)
{
if (!root) return;
postOrder(root->left);
postOrder(root->right);
printf("%d ", root->value);
}
/**
* 二叉樹層序遍歷
*/
void levelOrder(BTNode *root)
{
int btHeight = height(root);
int level;
for (level = 1; level <= btHeight; level++) {
levelOrderInLevel(root, level);
}
}
/**
* 二叉樹層序遍歷輔助函數-打印第level層的結點
*/
void levelOrderInLevel(BTNode *root, int level)
{
if (!root) return;
if (level == 1) {
printf("%d ", root->value);
return;
}
levelOrderInLevel(root->left, level-1);
levelOrderInLevel(root->right, level-1);
}
複製代碼
postOrderIter()
會有點繞,也易錯。因此在面試時推薦用兩個棧的版本postOrderIterWith2Stack()
,容易理解,也比較好寫。BTNodeQueue
和棧 BTNodeStack
,用於二叉樹非遞歸遍歷。/*********************/
/** 二叉樹遍歷-非遞歸 **/
/*********************/
/**
* 先序遍歷-非遞歸
*/
void preOrderIter(BTNode *root)
{
if (!root) return;
int size = btSize(root);
BTNodeStack *stack = stackNew(size);
push(stack, root);
while (!IS_EMPTY(stack)) {
BTNode *node = pop(stack);
printf("%d ", node->value);
if (node->right)
push(stack, node->right);
if (node->left)
push(stack, node->left);
}
free(stack);
}
/**
* 中序遍歷-非遞歸
*/
void inOrderIter(BTNode *root)
{
if (!root) return;
BTNodeStack *stack = stackNew(btSize(root));
BTNode *current = root;
while (current || !IS_EMPTY(stack)) {
if (current) {
push(stack, current);
current = current->left;
} else {
BTNode *node = pop(stack);
printf("%d ", node->value);
current = node->right;
}
}
free(stack);
}
/**
* 後續遍歷-使用一個棧非遞歸
*/
void postOrderIter(BTNode *root)
{
BTNodeStack *stack = stackNew(btSize(root));
BTNode *current = root;
do {
// 移動至最左邊結點
while (current) {
// 將該結點右孩子和本身入棧
if (current->right)
push(stack, current->right);
push(stack, current);
// 往左子樹遍歷
current = current->left;
}
current = pop(stack);
if (current->right && peek(stack) == current->right) {
pop(stack);
push(stack, current);
current = current->right;
} else {
printf("%d ", current->value);
current = NULL;
}
} while (!IS_EMPTY(stack));
}
/**
* 後續遍歷-使用兩個棧,更好理解一點。
*/
void postOrderIterWith2Stack(BTNode *root)
{
if (!root) return;
BTNodeStack *stack = stackNew(btSize(root));
BTNodeStack *output = stackNew(btSize(root));
push(stack, root);
BTNode *node;
while (!IS_EMPTY(stack)) {
node = pop(stack);
push(output, node);
if (node->left)
push(stack, node->left);
if (node->right)
push(stack, node->right);
}
while (!IS_EMPTY(output)) {
node = pop(output);
printf("%d ", node->value);
}
}
/**
* 層序遍歷-非遞歸
*/
void levelOrderIter(BTNode *root)
{
if (!root) return;
BTNodeQueue *queue = queueNew(btSize(root));
enqueue(queue, root);
while (1) {
int nodeCount = QUEUE_SIZE(queue);
if (nodeCount == 0)
break;
btHeight
while (nodeCount > 0) {
BTNode *node = dequeue(queue);
printf("%d ", node->value);
if (node->left)
enqueue(queue, node->left);
if (node->right)
enqueue(queue, node->right);
nodeCount--;
}
printf("\n");
}
}
複製代碼