目錄
一、基本概念
鏈式存儲概念
?二、鏈式二叉樹的結構
鏈式二叉樹結構
構建鏈式二叉樹
二叉樹的遍歷
?二叉樹節點和高度等
?二叉樹銷毀
三、鏈式二叉樹的練習
相同的樹?
對稱二叉樹
?另外一顆子樹
二叉樹前序遍歷?
?二叉樹遍歷
四、完整代碼
Tree.h
Tree.c
五、總結
一、基本概念
鏈式二叉樹是一種常見的數據結構,它是用鏈表結點來存儲二叉樹中的每一個結點,結點結構通常包括數據域和若干個指針域。其中,指針域用于指向左孩子結點和右孩子結點。
對于那些非完全二叉樹,由于順序存儲結構的空間利用率低,因此二叉樹一般都采用鏈式存儲結構。在鏈式二叉樹中,根據指針的數量可以分為二叉鏈和三叉鏈兩種結構。二叉鏈的結點包含存儲數據的變量、存儲左孩子的指針以及存儲右孩子的指針;而三叉鏈的結點除了包含存儲數據的變量、存儲左孩子的指針以及存儲右孩子的指針,還包含存儲雙親的指針。
二叉樹的遍歷是指按某條搜索路徑訪問樹中每個結點,使得每個結點均被訪問一次,而且僅被訪問一次。根據訪問根結點的順序不同可以分為前序遍歷、中序遍歷和后序遍歷。
鏈式存儲概念
鏈式存儲二叉樹的鏈式存儲結構是指,用鏈表來表示一棵二叉樹,即用鏈來指示元素的邏輯關系。 通常的方法是 鏈表中每個結點由三個域組成,數據域和左右指針域,左右指針分別用來給出該結點左孩子和右孩子所 在的鏈結點的存儲地址 。鏈式結構又分為二叉鏈和三叉鏈,當前我們學習中一般都是二叉鏈。
?二、鏈式二叉樹的結構
鏈式二叉樹結構
typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;
構建鏈式二叉樹
int類型? 手撕構建方便前期調試
BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc err");return NULL;}newnode->_data = x;newnode->_left = NULL;newnode->_right = NULL;return newnode;
}//123nn4n5nn67nn8nn 通過前序遍歷構建
BTNode* BinaryTreeCreate()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);BTNode* node7 = BuyNode(7);BTNode* node8 = BuyNode(8);node1->_left = node2;node1->_right = node6;node2->_left = node3;node2->_right = node4;node4->_right = node5;node6->_left = node7;node6->_right = node8;return node1;
}
char數組類型
// 通過前序遍歷的數組"ABD##E#H##CF##G##"構建二叉樹
BTNode* BinaryTreeCreateChar(BTDataType* a, int* pi)
{if (a[(*pi)] == '#'){(*pi)++;return NULL;}BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc err");return NULL;}newnode->_data = a[(*pi)++];newnode->_left = BinaryTreeCreateChar(a, pi);newnode->_right = BinaryTreeCreateChar(a, pi);return newnode;
}
二叉樹的遍歷
前序:前序遍歷(Preorder Traversal 亦稱先序遍歷)——訪問根結點的操作發生在遍歷其左右子樹之前。
前序遍歷遞歸圖解:
// 二叉樹前序遍歷
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%d ", root->_data);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}
中序:中序遍歷(Inorder Traversal)——訪問根結點的操作發生在遍歷其左右子樹之中(間)
// 二叉樹中序遍歷
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}BinaryTreeInOrder(root->_left);printf("%d ",root->_data);BinaryTreeInOrder(root->_right);
}
后序:后序遍歷(Postorder Traversal)——訪問根結點的操作發生在遍歷其左右子樹之后
// 二叉樹后序遍歷
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%d ",root->_data);
}
?層序遍歷:除了先序遍歷、中序遍歷、后序遍歷外,還可以對二叉樹進行層序遍歷。設二叉樹的根節點所在 層數為1,層序遍歷就是從所在二叉樹的根節點出發,首先訪問第一層的樹根節點,然后從左到右訪問第2層 上的節點,接著是第三層的節點,以此類推,自上而下,自左至右逐層訪問樹的結點的過程就是層序遍歷。
// 層序遍歷
#include"Queue.h"
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);printf("%d ",Fout->_data);if (Fout->_left){QueuePush(&q,Fout->_left);}if (Fout->_right){QueuePush(&q, Fout->_right);}}QueueDestroy(&q);
}
判斷二叉樹是否是完全二叉樹
// 判斷二叉樹是否是完全二叉樹
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);if (Fout == NULL){break;}QueuePush(&q, Fout->_left);QueuePush(&q, Fout->_right);}while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);if (root){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}
?二叉樹節點和高度等
1.二叉樹節點個數?
// 二叉樹節點個數
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_left)+1;
}
2.二叉樹葉子節點個數
// 二叉樹葉子節點個數
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->_left==NULL && root->_right==NULL){return 1;}return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
3.二叉樹的最大高度
//二叉樹的最大高度
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int node_left=TreeHeight(root->_left); int node_right = TreeHeight(root->_right);return node_left > node_right ? node_left + 1 : node_right + 1;
}
4.二叉樹第k層節點個數
// 二叉樹第k層節點個數
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
5.二叉樹查找值為x的節點
// 二叉樹查找值為x的節點
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->_data == x)return root;BTNode* ret1 = BinaryTreeFind(root->_left, x);if (ret1)return ret1;BTNode* ret2 = BinaryTreeFind(root->_right, x);if (ret2)return ret2;return NULL;}
?二叉樹銷毀
// 二叉樹銷毀
void BinaryTreeDestory(BTNode** root)
{if (*(root) == NULL)return;BinaryTreeDestory(&(*root)->_left);BinaryTreeDestory(&(*root)->_right);free(*(root));
}
三、鏈式二叉樹的練習
相同的樹?
給你兩棵二叉樹的根節點?p
?和?q
?,編寫一個函數來檢驗這兩棵樹是否相同。
如果兩個樹在結構上相同,并且節點具有相同的值,則認為它們是相同的。
思路:? 可以通過遞歸的方式同時遍歷兩棵樹。
比較兩棵樹的根節點值是否相等,如果不相等則直接返回 false。
然后遞歸地對左子樹和右子樹分別進行相同的比較,只有當左右子樹在相應位置上都相同的時候才返回 true。
在遞歸過程中,只要有一處不滿足相同條件就可以立即返回 false,只有所有節點和結構都完全一致才能最終返回 true。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool isSameTree(struct TreeNode* p, struct TreeNode* q) {if (p == NULL && q == NULL)return true;if (p == NULL || q == NULL)return false;if (p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); }
對稱二叉樹
給你一個二叉樹的根節點?root
?, 檢查它是否軸對稱。?
思路:
采用遞歸的方法。
一個二叉樹是軸對稱的,意味著對于任意一個節點,它的左子樹和右子樹是鏡像對稱的。
具體來說,就是對于當前節點,它的左子節點和右子節點的值相等(如果都存在的話),并且左子節點的左子樹和右子節點的右子樹對稱,左子節點的右子樹和右子節點的左子樹也對稱。我們通過遞歸地比較左右子樹的對應節點來判斷整個二叉樹是否對稱。如果在遞歸過程中發現不滿足對稱條件的情況,就可以返回 false,如果遞歸完整棵樹都滿足,則返回 true。
* Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ bool _isSymmetric(struct TreeNode* p, struct TreeNode* q) {if (p == NULL && q == NULL)return true;if (p == NULL || q == NULL)return false;if (p->val != q->val)return false;return _isSymmetric(p->left, q->right)&&_isSymmetric(p->right, q->left); } bool isSymmetric(struct TreeNode* root) {return _isSymmetric(root->left, root->right); }
?另外一顆子樹
給你兩棵二叉樹?root
?和?subRoot
?。檢驗?root
?中是否包含和?subRoot
?具有相同結構和節點值的子樹。如果存在,返回?true
?;否則,返回?false
?。
二叉樹?tree
?的一棵子樹包括?tree
?的某個節點和這個節點的所有后代節點。tree
?也可以看做它自身的一棵子樹。
思路
我們可以從根節點開始,對主樹 root 進行先序遍歷。
在遍歷過程中,每當遇到一個節點,就判斷以這個節點為根的子樹是否與 subRoot 完全相同。
為了判斷子樹是否相同,可以編寫一個輔助函數來比較兩棵樹是否完全一致,這與前面判斷兩棵樹是否相同的思路類似,即同時遞歸地檢查節點值以及左右子樹的結構和節點值是否匹配。如果找到匹配的子樹,就返回 true,否則繼續遍歷主樹的其他節點。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/bool isSameTree(struct TreeNode* p, struct TreeNode* q) {if (p == NULL && q == NULL)return true;if (p == NULL || q == NULL)return false;if (p->val != q->val)return false;return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); } bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {if(root==NULL)return false;if( root->val==subRoot->val &&isSameTree(root,subRoot))return true;return isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot); }
二叉樹前序遍歷?
給你二叉樹的根節點?root
?,返回它節點值的?前序?遍歷。
思路:
首先訪問根節點,然后遞歸地對左子樹進行前序遍歷,最后遞歸地對右子樹進行前序遍歷。
具體來說,我們可以使用一個輔助函數,在這個函數中先輸出當前節點的值,然后如果左子樹存在就對左子樹調用該函數進行遞歸遍歷,接著如果右子樹存在就對右子樹調用該函數進行遞歸遍歷。這樣就可以按照前序遍歷的順序輸出所有節點的值。
/*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array must be malloced, assume caller calls free().*/ int TreeSize(struct TreeNode* root) {return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1; } void PrevOrder(struct TreeNode* root,int* a,int* i) {if(root==NULL)return;a[(*i)++]=root->val;PrevOrder(root->left,a,i);PrevOrder(root->right,a,i); } int* preorderTraversal(struct TreeNode* root, int* returnSize) {* returnSize=TreeSize(root);int* a=(int*)malloc(sizeof(int)*(*returnSize));int i = 0;PrevOrder(root,a,&i);return a; }
?二叉樹遍歷
編一個程序,讀入用戶輸入的一串先序遍歷字符串,根據此字符串建立一個二叉樹(以指針方式存儲)。 例如如下的先序遍歷字符串: ABC##DE#G##F### 其中“#”表示的是空格,空格字符代表空樹。建立起此二叉樹以后,再對二叉樹進行中序遍歷,輸出遍歷結果。
輸入描述:
輸入包括1行字符串,長度不超過100。
輸出描述:
可能有多組測試數據,對于每組數據, 輸出將輸入字符串建立二叉樹后中序遍歷的序列,每個字符后面都有一個空格。 每個輸出結果占一行。
思路:
1. 定義二叉樹節點結構體:包含節點值以及左右子節點指針。
2. 構建二叉樹函數:通過遍歷輸入的先序遍歷字符串來構建二叉樹。使用一個靜態索引來跟蹤當前處理的字符位置。如果遇到非'#'字符,就創建一個新節點,然后遞歸地構建左子樹和右子樹,遇到'#'則表示空節點。
3. 中序遍歷函數:按照中序遍歷的順序(先左子樹、當前節點、再右子樹)輸出節點值并添加空格。
4. 在主程序中,獲取用戶輸入的字符串,調用構建二叉樹函數得到二叉樹,然后調用中序遍歷函數輸出結果。
#include<stdio.h> #include<stdlib.h>typedef struct Tree {struct Tree* le;struct Tree* ri;int val; } BTNode;void PrevInor(BTNode* root) {if (root == NULL)return;PrevInor(root->le);printf("%c ", root->val);PrevInor(root->ri); }BTNode* BUTree(char* a,int* pi) {if(a[*pi] =='#'){(*pi)++;return NULL;}BTNode* newnode=(BTNode*)malloc(sizeof(BTNode));newnode->val=a[(*pi)++];newnode->le=BUTree(a, pi);newnode->ri=BUTree(a, pi);return newnode; } int main() {char a[100];scanf("%s",a);int i=0;BTNode* node=BUTree(a, &i);PrevInor(node); }
四、完整代碼
Tree.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef int BTDataType;
typedef struct BinaryTreeNode
{BTDataType _data;struct BinaryTreeNode* _left;struct BinaryTreeNode* _right;
}BTNode;// 通過前序遍歷的數組"ABD##E#H##CF##G##"構建二叉樹
BTNode* BinaryTreeCreate();//BTDataType* a, int n, int* pi);
// 二叉樹銷毀
void BinaryTreeDestory(BTNode** root);
// 二叉樹節點個數
int BinaryTreeSize(BTNode* root);
// 二叉樹葉子節點個數
int BinaryTreeLeafSize(BTNode* root);
// 二叉樹第k層節點個數
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉樹查找值為x的節點
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉樹前序遍歷
void BinaryTreePrevOrder(BTNode* root);
// 二叉樹中序遍歷
void BinaryTreeInOrder(BTNode* root);
// 二叉樹后序遍歷
void BinaryTreePostOrder(BTNode* root);
// 層序遍歷
void BinaryTreeLevelOrder(BTNode* root);
// 判斷二叉樹是否是完全二叉樹
bool BinaryTreeComplete(BTNode* root);
BTNode* BuyNode(BTDataType x);//求二叉樹的最長高度
int TreeHeight(BTNode* root);
Tree.c
#define _CRT_SECURE_NO_WARNINGS
#include"Tree.h"BTNode* BuyNode(BTDataType x)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc err");return NULL;}newnode->_data = x;newnode->_left = NULL;newnode->_right = NULL;return newnode;
}/*int n,*/
// 通過前序遍歷的數組"ABD##E#H##CF##G##"構建二叉樹
BTNode* BinaryTreeCreateChar(BTDataType* a, int* pi)
{if (a[(*pi)] == '#'){(*pi)++;return NULL;}BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc err");return NULL;}newnode->_data = a[(*pi)++];newnode->_left = BinaryTreeCreateChar(a, pi);newnode->_right = BinaryTreeCreateChar(a, pi);return newnode;
}//123nn4n5nn67nn8nn
BTNode* BinaryTreeCreate()
{BTNode* node1 = BuyNode(1);BTNode* node2 = BuyNode(2);BTNode* node3 = BuyNode(3);BTNode* node4 = BuyNode(4);BTNode* node5 = BuyNode(5);BTNode* node6 = BuyNode(6);BTNode* node7 = BuyNode(7);BTNode* node8 = BuyNode(8);node1->_left = node2;node1->_right = node6;node2->_left = node3;node2->_right = node4;node4->_right = node5;node6->_left = node7;node6->_right = node8;return node1;
}
// 二叉樹銷毀
void BinaryTreeDestory(BTNode** root)
{if (*(root) == NULL)return;BinaryTreeDestory(&(*root)->_left);BinaryTreeDestory(&(*root)->_right);free(*(root));
}
// 二叉樹節點個數
int BinaryTreeSize(BTNode* root)
{return root == NULL ? 0 : BinaryTreeSize(root->_left) + BinaryTreeSize(root->_left)+1;
}
// 二叉樹葉子節點個數
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL)return 0;if (root->_left==NULL && root->_right==NULL){return 1;}return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}
// 二叉樹第k層節點個數
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL)return 0;if (k == 1)return 1;return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
// 二叉樹查找值為x的節點
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->_data == x)return root;BTNode* ret1 = BinaryTreeFind(root->_left, x);if (ret1)return ret1;BTNode* ret2 = BinaryTreeFind(root->_right, x);if (ret2)return ret2;return NULL;}
// 二叉樹前序遍歷
void BinaryTreePrevOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}printf("%d ", root->_data);BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}
// 二叉樹中序遍歷
void BinaryTreeInOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}BinaryTreeInOrder(root->_left);printf("%d ",root->_data);BinaryTreeInOrder(root->_right);
}
// 二叉樹后序遍歷
void BinaryTreePostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);printf("%d ",root->_data);
}
// 層序遍歷
#include"Queue.h"
void BinaryTreeLevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);printf("%d ",Fout->_data);if (Fout->_left){QueuePush(&q,Fout->_left);}if (Fout->_right){QueuePush(&q, Fout->_right);}}QueueDestroy(&q);
}
// 判斷二叉樹是否是完全二叉樹
bool BinaryTreeComplete(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);if (Fout == NULL){break;}QueuePush(&q, Fout->_left);QueuePush(&q, Fout->_right);}while (!QueueEmpty(&q)){BTNode* Fout = QueueFront(&q);QueuePop(&q);if (root){QueueDestroy(&q);return false;}}QueueDestroy(&q);return true;
}//二叉樹的最大高度
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int node_left=TreeHeight(root->_left); int node_right = TreeHeight(root->_right);return node_left > node_right ? node_left + 1 : node_right + 1;
}
Queue.c(部分)
void QueueInit(Queue* pq)
{assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;
}
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->phead;while (cur){QNode* next = cur->next;free(cur);cur = next;}pq->phead = pq->ptail = NULL;pq->size = 0;
}
void QueuePush(Queue* pq, QDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc err");return;}newnode->data = x;newnode->next=NULL;if (pq->phead == NULL){assert(pq->ptail == NULL);pq->phead = pq->ptail= newnode;}else{pq->ptail->next = newnode;pq->ptail = newnode;}pq->size++;
}
void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));if (pq->phead->next == NULL){free(pq->phead);pq->phead = pq->ptail = NULL;}else {QNode* next = pq->phead->next;free(pq->phead);pq->phead = next;}pq->size--;
}
QDataType QueueFront(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));return pq->phead->data;
}
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->phead == NULL&& pq->ptail == NULL;
}
五、總結
二叉樹全面總結:
定義:
二叉樹是一種特殊的樹結構,每個節點最多有兩個子節點,分別稱為左子節點和右子節點。主要特點:
? 節點度最大為 2。
? 具有遞歸結構。
重要類型:
? 滿二叉樹:所有節點都有兩個子節點,且葉子節點都在同一層。
? 完全二叉樹:除了最后一層,其他層都是滿的,最后一層的葉子節點靠左排列。
遍歷方式:
? 前序遍歷:先訪問根節點,再遍歷左子樹,最后遍歷右子樹。
? 中序遍歷:先遍歷左子樹,再訪問根節點,最后遍歷右子樹。
? 后序遍歷:先遍歷左子樹,再遍歷右子樹,最后訪問根節點。
? 層次遍歷:按照層次依次訪問節點。
應用:
? 高效搜索和排序。
? 表達式樹。
? 二叉堆實現優先隊列等。
性質:
? 在完全二叉樹中,節點編號與層次存在特定關系。
? 葉子節點數與度為 2 的節點數存在一定關系。
存儲方式:
? 鏈式存儲:通過節點指針連接。
? 數組存儲:適用于完全二叉樹。
常見算法:
? 構建二叉樹。
? 求深度、高度。
? 查找節點。
? 平衡性判斷及調整(如 AVL 樹、紅黑樹等)。
二叉樹是數據結構中的基礎且重要部分,在計算機科學中有廣泛的應用和研究價值。