資料結構—二叉樹相關概念及經典面試題
二叉樹概念
一棵二叉樹是結點的有限集合,該集合或者為空, 或者是由根結點加上兩棵分別稱為左子樹和右子樹的二叉樹構成
二叉樹的特點:
- 每個結點最多有兩棵子樹,即二叉樹不存在度大於2的結點
- 二叉樹的子樹有左右之分,其子樹的次序不能顛倒
滿二叉樹、完全二叉樹
滿二叉樹:在一棵二叉樹中,如果所有分支結點都存在左子樹和右子樹,並且所有葉子節點都在同一層上
完全二叉樹: 如果一棵具有N個結點的二叉樹的結構與滿二叉樹的前N個 結點的結構相同,稱為完全二叉樹
二叉樹性質
- 若規定根節點的層數為1,則一棵非空二叉樹的第i層上最多有 (i>0)個結點
- 若規定只有根節點的二叉樹的深度為1,則深度為K的二叉樹的最大結點數是 (k>=0)
- 對任何一棵二叉樹, 如果其葉結點個數為 n0, 度為2的非葉結點個數 為 n2,則有n0=n2+1
- 具有n個結點的完全二叉樹的深度k為上取整
對於具有n個結點的完全二叉樹,如果按照從上至下從左至右的順序 對所有節點從0開始編號,則對於序號為i的結點有:
- 若i>0,雙親序號:(i-1)/2;
- i=0,i為根節點編號,無雙親結點
- 若2i+1<n,左孩子序號:2i+1,否則無左孩子
- 若2i+2<n,右孩子序號:2i+2,否則無右孩子
二叉樹儲存結構
二叉樹有順序儲存和鏈式儲存結構
順序儲存結構:
對於一棵完全二叉樹所有結點按照層序自頂向下,同一層自左向右順 序編號,就得到一個節點的順序序列
優點:儲存完全二叉樹,簡單省空間
缺點:儲存一般二叉樹尤其單支樹,儲存空間利用不高
鏈式儲存結構:
二叉鏈結構結點的定義:
typedef int DataType;
typedef struct BinaryTreeNode //二叉樹結構定義
{
struct BinaryTreeNode* _left; //指向當前結點左孩子
struct BinaryTreeNode* _right; //指向當前結點右孩子
DataType _data; //當前結點資料
}BTNode;
三叉鏈結構結點定義:
typedef int DataType;
typedef struct BinaryTreeNode //二叉樹結構定義
{
struct BinaryTreeNode* _pParent; //指向當前節點的雙親結點
struct BinaryTreeNode* _pLeft; //指向當前結點左孩子
struct BinaryTreeNode* _pRight; //指向當前結點右孩子
ataType _data; //當前結點資料
}BTNode;
二叉樹的操作 (面試題)
建立二叉樹:
對二叉樹進行其他操作的基礎是要創建出二叉樹,我們採用遞迴的方式建立二叉樹,從根結點開始先處理它的左子樹,再處理它的右子樹(對子樹也是同樣的),遞迴是比較抽象的,所以我先給出實現的程式碼,然後詳細說一下遞迴是如何實現二叉樹建立的
// 建立二叉樹
//a:陣列指標 pIndex:下標指標 invalib:給的非結點元素,用其表示一個結點無孩子(左、右)
BTNode* CreateBTree(DataType* a, size_t* pIndex, DataType invalid)
{
assert(a);
if (a[*pIndex] == invalid)
return NULL;
BTNode* root = BuyBTNode(a[*pIndex]);
++(*pIndex);
root->_left = CreateBTree(a,pIndex,invalid);
++(*pIndex);
root->_right = CreateBTree(a, pIndex, invalid);
return root;
}
思考:建立二叉樹是引數為什麼給的是指標變數 pIndex,而不是變數index?
我在走遞迴過程時以上圖中已經很好的體現了,相信對這塊的理解沒有問題
前序遍歷
- 遞迴實現
void BTreePrevOrder(BTNode* root) //前序遍歷
{
if (root == NULL)
return;
printf("%d ",root->_data);
BTreePrevOrder(root->_left);
BTreePrevOrder(root->_right);
}
- 非遞迴實現
void BTreePrevOrderNonR(BTNode* root) //非遞迴前序遍歷
{
assert(root);
Stack s;
StackInit(&s);
BTNode* cur = root;
while (cur != NULL || StackEmpty(&s) != 0) {
while (cur) {
printf("%d ",cur->_data);
StackPush(&s,cur);
cur = cur->_left;
}
BTNode* top = StackTop(&s);
cur = top->_right;
StackPop(&s);
}
}
中序遍歷
- 遞迴實現
void BTreeInOrder(BTNode* root) //中序遍歷
{
if (root == NULL)
return;
BTreeInOrder(root->_left);
printf("%d ",root->_data);
BTreeInOrder(root->_right);
}
- 非遞迴實現
void BTreeInOrderNonR(BTNode* root) //非遞迴中序遍歷
{
assert(root);
Stack s;
StackInit(&s);
BTNode* cur = root;
while (cur != NULL || StackEmpty(&s) != 0) {
while (cur) {
StackPush(&s, cur);
cur = cur->_left;
}
BTNode* top = StackTop(&s);
printf("%d ", top->_data);
cur = top->_right;
StackPop(&s);
}
}
後序遍歷
- 遞迴實現
void BTreePostOrder(BTNode* root) //後序遍歷
{
if (root == NULL)
return;
BTreePostOrder(root->_left);
BTreePostOrder(root->_right);
printf("%d ",root->_data);
}
- 非遞迴實現
void BTreePostOrderNonR(BTNode* root) //非遞迴後序遍歷
{
Stack s;
StackInit(&s);
BTNode* cur = root;
BTNode* prev = NULL; //記錄被遍歷的前一個結點
while (cur != NULL || StackEmpty(&s) != 0) {
while (cur) {
StackPush(&s, cur);
cur = cur->_left;
}
BTNode* top = StackTop(&s);
if (top->_right == NULL || top->_right == prev)
{
printf("%d ", top->_data);
StackPop(&s);
prev = top;
}
else
cur = top->_right;
}
}
層序遍歷
void BTreeLevelOrder(BTNode* root) //層序遍歷
{
assert(root);
Queue q;
QueueInit(&q);
QueuePush(&q,root);
while (QueueEmpty(&q) != 0) {
BTNode* head = QueueHead(&q);
printf("%d ",head->_data);
QueuePop(&q);
if (head->_left != NULL)
QueuePush(&q,head->_left);
if (head->_right != NULL)
QueuePush(&q, head->_right);
}
}
結點個數
size_t BTreeSize(BTNode* root) //結點個數
{
if (root == NULL)
return 0;
//左子樹的個數+右子樹的個數
return 1 + BTreeSize(root->_left) + BTreeSize(root->_right);
}
葉子結點個數
size_t BTreeLeafSize(BTNode* root) //葉子結點的個數
{
if (root == NULL) //這個返回條件不能不寫
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
//左子樹葉結點+右子樹葉結點
return BTreeLeafSize(root->_left) + BTreeLeafSize(root->_right);
}
第K層結點個數
size_t BTreeKLevelSize(BTNode* root, size_t k) //第k層的結點個數
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
//左子樹第k-1層的結點數+右子樹第k-1層的結點樹
return BTreeKLevelSize(root->_left,k-1) + BTreeKLevelSize(root->_right,k-1);
}
二叉樹的深度
size_t BTreeDepth(BTNode* root) //二叉樹的深度
{
if (root == NULL)
return 0;
//返回左子樹和右子樹中較深的(加1:根節點)
return (1 + BTreeDepth(root->_left)) >= (1 + BTreeDepth(root->_right)) ? (1+BTreeDepth(root->_left)) : (1+BTreeDepth(root->_right));
}
判斷一個節點是否在一棵二叉樹中
BTNode* BTreeFind(BTNode* root, BTNDataType x) //檢視某個結點是否存在
{
if (root == NULL)
return NULL;
if (root->_data == x)
return root;
BTNode* ret;
ret = BTreeFind(root->_left,x);
if (ret != NULL)
return ret;
ret = BTreeFind(root->_right,x);
return ret;
}
判斷一棵二叉樹是否是完全二叉樹
int IsCompleteBTree(BTNode* root) //判斷是否是完全二叉樹
{
assert(root);
Queue q;
QueueInit(&q);
QueuePush(&q,root);
while (QueueEmpty(&q) != 0) {
BTNode* head = QueueHead(&q);
QueuePop(&q);
if (head == NULL)
break;
if (head->_left != NULL || head->_right != NULL)
{
QueuePush(&q,head->_left);
QueuePush(&q,head->_right);
}
}
while (QueueEmpty(&q) != 0) {
BTNode* head = QueueHead(&q);
if (head == NULL)
{
if (QueueEmpty(&q) != 0)
return 0; //返回0表示不是完全二叉樹
else
return 1; //返回1表示是完全二叉樹
}
else
QueuePop(&q);
}
return 1;
}
求二叉樹中兩個結點的最近公共祖先結點
判斷一棵二叉樹是否是平衡二叉樹
求二叉樹中最遠的兩個結點之間的距離
有前序遍歷和中序遍歷重建二叉樹(前序遍歷結果:1,2,3,4,5,6 中序 遍歷結果:4,2,5,1,6,3)
求二叉樹的映象
將二叉搜尋樹轉換成一個排序的雙向連結串列。要求:不能建立任何新的 結點,只能調整樹種結點指標的指向