樹概念及結構
定義
樹是一種非線性的數據結構,它是由 n(n>=0)個有限結點組成一個具有層次關係的集合。它具有以下的特點:每個結點有零個或多個子結點;沒有父結點的結點稱爲根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分爲多個不相交的子樹
其他概念
節點的度:一個節點含有的子樹的個數稱爲該節點的度; 如圖:A的爲6
葉節點或終端節點:度爲0的節點稱爲葉節點; 如圖:B、C、H、I…等節點爲葉節點
非終端節點或分支節點:度不爲0的節點; 如圖:D、E、F、G…等節點爲分支節點
雙親節點或父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點; 如圖:A是B的父節點
孩子節點或子節點:一個節點含有的子樹的根節點稱爲該節點的子節點; 如圖:B是A的孩子節點
兄弟節點:具有相同父節點的節點互稱爲兄弟節點; 如圖:B、C是兄弟節點
樹的度:一棵樹中,最大的節點的度稱爲樹的度; 如圖:樹的度爲6
節點的層次:從根開始定義起,根爲第1層,根的子節點爲第2層,以此類推;
樹的高度或深度:樹中節點的最大層次; 如圖:樹的高度爲4
堂兄弟節點:雙親在同一層的節點互爲堂兄弟;如圖:H、I互爲兄弟節點
節點的祖先:從根到該節點所經分支上的所有節點;如圖:A是所有節點的祖先
子孫:以某節點爲根的子樹中任一節點都稱爲該節點的子孫。如圖:所有節點都是A的子孫
森林:由m(m>=0)棵互不相交的樹的集合稱爲森林;
區分樹
- 子樹不相交
- 除了根節點,每個節點有且僅有一個父節點
- 一棵有 N 個節點的樹有 n - 1 條邊
樹的表示
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一個孩子結點
struct Node* _pNextBrother; // 指向其下一個兄弟結點
DataType _data; // 結點中的數據域
};
舉例如下:
二叉樹概念及結構
定義
一棵二叉樹是結點的一個有限集合,該集合或者爲空,或者是由一個根節點加上兩棵別稱爲左子樹和右子樹的二叉樹組成
特點
- 每個結點最多有兩棵子樹,即二叉樹不存在度大於 2 的結點
- 二叉樹的子樹有左右之分,其子樹的次序不能顛倒
特殊的二叉樹
- 滿二叉樹:一個二叉樹,如果每一個層的結點數都達到最大值,則這個二叉樹就是滿二叉樹。即層數爲 K,且結點總數是 ( 2 ^ k ) -1
- 完全二叉樹:完全二叉樹是效率很高的數據結構,完全二叉樹是由滿二叉樹而引出來的。對於深度爲 K 的,有 n 個結點的二叉樹,當且僅當其每一個結點都與深度爲K的滿二叉樹中編號從1至n的結點一一對應時稱之爲完全二叉樹。 滿二叉樹是一種特殊的完全二叉樹
二叉樹的存儲結構
二叉樹一般可以使用兩種結構存儲,一種順序結構,一種鏈式結構
順序結構
順序結構存儲就是使用數組來存儲,一般使用數組只適合表示完全二叉樹,因爲不是完全二叉樹會有空間的浪費。只有堆纔會使用數組來存儲,二叉樹順序存儲在物理上是一個數組,在邏輯上是一顆二叉樹
鏈式存儲
鏈式存儲結構是,用鏈表來表示一棵二叉樹,即用鏈來指示元素的邏輯關係。 通常的方法是鏈表中每個結點由三個域組成,數據域和左右指針域,左右指針分別用來給出該結點左孩子和右孩子所在的鏈結
點的存儲地址 。鏈式結構又分爲二叉鏈和三叉鏈,高階數據結構如紅黑樹等會用到三叉鏈
鏈式結構代碼表示
// 二叉鏈
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向當前節點左孩子
struct BinTreeNode* _pRight; // 指向當前節點右孩子
BTDataType _data; // 當前節點值域
}
// 三叉鏈
struct BinaryTreeNode
{
struct BinTreeNode* _pParent; // 指向當前節點的雙親
struct BinTreeNode* _pLeft; // 指向當前節點左孩子
struct BinTreeNode* _pRight; // 指向當前節點右孩子
BTDa
二叉樹的順序結構實現
我們通常把堆(一種二叉樹)使用順序結構的數組來存儲
堆的概念及結構
定義
如果有一個關鍵碼的集合K = { k0,k1, k2,…,kn-1 },把它的所有元素按完全二叉樹的順序存儲方式存儲在一個一維數組中,並滿足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,則稱爲小堆(或大堆)。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆
性質
堆中某個節點的值總是不大於或不小於其父節點的值
堆總是一棵完全二叉樹
堆的實現
堆向下調整算法
從根節點開始的向下調整算法可以把它調整成一個小堆。向下調整算法有一個前提:左右子樹必須是一個堆,才能調整
堆的創建
從倒數的第一個非葉子節點的子樹開始調整,一直調整到根節點的樹,就可以調整成堆
- 初始化堆
- 從葉子節點向上,將更大的子節點與父親節點比較後,若子節點更大,則與父親節點交換,一直向上,直到根節點,這樣根節點的數爲最大的數
- 再依次處理根節點的子節點,以此類推,最終完成大堆的構造
堆的插入
先插入一個80到數組的尾上,再進行向上調整算法,直到滿足堆
堆的刪除
刪除堆是刪除堆頂的數據,將堆頂的數據根最後一個數據一換,然後刪除數組最後一個數據,再進行向下調整算法
堆排序
將大頂堆調整爲小頂堆
- 將最後一個節點與根結點交換位置,換下來的根節點不進行排序操作,位置固定,從堆頂往下調整爲大頂堆
- 繼續將最後一個節點與根結點交換位置,新換下來的根節點依然不進行排序操作,位置固定,並從堆頂往下調整爲大頂堆
- 重複以上步驟,直至只剩根節點即獲得小根堆
堆的代碼
在進行代碼編寫前,我們需要明確以下幾個問題:
堆:
邏輯結構:完全二叉樹
物理結構:順序表
父子節點:
子節點:2*父節點+1,2*父節點+2
父節點:(子節點-1)/2
堆中最後一個非葉子節點:(n-2)/2
向下調整:建堆,Pop
向上調整:Push
Pop:Swap(0,size-1)--->尾刪size-1--->向下調整
Push:尾插(size)--->向上調整
建棧:for:最後一個非葉子節點~0 向下調整
應用場景:排序,Top K
堆排序:Swap(0,size-1)--->向下調整 N * logN + N ~ N
升序:大堆--->向下調整
降序:小堆
頭文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<assert.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
void HeapInit(Heap* hp, HPDataType* a, int n);
void HeapInit(Heap* hp);
void ShiftDown(HPDataType* a, int n, int root);
void HeapDestory(Heap* hp);
void HeapPush(Heap* hp, HPDataType x);
void HeapPop(Heap* hp);
HPDataType HeapTop(Heap* hp);
int HeapSize(Heap* hp);
int HeapEmpty(Heap* hp);
// 堆排序
void HeapSort(int* a, int n);
void TestHeap();
源文件
#include "Heap.h"
//大堆
void HeapInit(Heap* hp, HPDataType* a, int n)
{
assert(hp && a);
int i;
hp->_a = (HPDataType*)malloc(sizeof(HPDataType)* n);
for (i = 0; i < n; ++i)
{
hp->_a[i] = a[i];
}
for (i = (n - 2) / 2; i >= 0; --i)
{
ShiftDown(hp->_a, n, i);
}
hp->_size = hp->_capacity = n;
}
void HeapEmptyInit(Heap* hp)
{
assert(hp);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
void Swap(HPDataType* a, HPDataType* b)
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
//向上調整
void ShiftUp(HPDataType* a, int n, int child)
{
assert(a);
int parent = (child - 1) / 2;
while (parent >= 0)
{
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
break;
}
}
//向下調整
void ShiftDown(HPDataType* a, int n, int root)
//最後一個非葉子結點索引:(n-2)/2
{
assert(a);
int parent = root;
int child = 2 * parent + 1;
//當前節點是否有孩子
while (child < n)
{
//是否有右孩子,有則進行比較
if (child + 1 < n && a[child + 1] > a[child])
++child;
//孩子是否大於父親,大則進行交換
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
//更新下一次調整的位置
parent = child;
child = 2 * parent + 1;
}
else
{
//以父親爲根的子樹已經是一個大堆,結束調整
break;
}
}
}
void HeapDestory(Heap* hp)
{
assert(hp);
if (hp->_a)
{
free(hp->_a);
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
}
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
if (hp->_size == hp->_capacity)
{
int newC = hp->_capacity == 0 ? 10 : 2 * hp->_capacity;
hp->_a = (HPDataType*)realloc(hp->_a, newC * sizeof(HPDataType));
hp->_capacity = newC;
}
hp->_a[hp->_size] = x;
++hp->_size;
ShiftUp(hp->_a, hp->_size, hp->_size - 1);
}
int HeapEmpty(Heap* hp)
{
assert(hp);
if (hp->_size == 0)
return 1;
else
return 0;
}
void HeapPop(Heap* hp)
{
assert(hp);
if (HeapEmpty(hp) == 0)
{
Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
--hp->_size;
ShiftDown(hp->_a, hp->_size, 0);
}
}
HPDataType HeapTop(Heap* hp)
{
assert(hp && hp->_size);
return hp->_a[0];
}
int HeapSize(Heap* hp)
{
assert(hp);
return hp->_size;
}
// 堆排序
void HeapSort(int* a, int n)
{
//改成小堆
assert(a);
int i;
for (i = (n - 2) / 2; i >= 0 ; i--)
{
ShiftDown(a, n , i);
}
while (n > 1)
{
Swap(&a[0], &a[n - 1]);
ShiftDown(a, n - 1, 0);
n--;
}
}
void HeapPrint(Heap* hp)
{
assert(hp);
for (int i = 0; i < hp->_size; ++i)
{
printf("%d ", hp->_a[i]);
}
printf("\n");
}
void TestHeap()
{
Heap hp;
HeapEmptyInit(&hp);
HeapPush(&hp, 1);
HeapPush(&hp, 8);
HeapPush(&hp, 5);
HeapPush(&hp, 7);
HeapPush(&hp, 6);
HeapPush(&hp, 3);
HeapPush(&hp, 9);
HeapPush(&hp, 5);
HeapPrint(&hp);
HeapPop(&hp);
HeapPrint(&hp);
HeapPop(&hp);
HeapPrint(&hp);
HeapPop(&hp);
HeapPrint(&hp);
HeapPop(&hp);
HeapPrint(&hp);
}
void TestHeapSort()
{
int a[] = { 1, 5, 3, 8, 7, 6 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
printf("\n");
HeapSort(a, sizeof(a) / sizeof(a[0]));
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
int main()
{
//TestHeap();
TestHeapSort();
return 0;
}
二叉樹鏈式結構實現
二叉樹鏈式結構的遍歷
沿着某條搜索路線,依次對樹中每個結點均做一次且僅做一次訪問。訪問結點所做的操作依賴於具體的應用問題。 遍歷是二叉樹上最重要的運算之一,是二叉樹上進行其它運算之基礎
遍歷方式
前序/中序/後序的遞歸結構遍歷
- NLR:前序遍歷(Preorder Traversal 亦稱先序遍歷)——訪問根結點的操作發生在遍歷其左右子樹之前
- LNR:中序遍歷(Inorder Traversal)——訪問根結點的操作發生在遍歷其左右子樹之中(間)
- LRN:後序遍歷(Postorder Traversal)——訪問根結點的操作發生在遍歷其左右子樹之後
層序遍歷:設二叉樹的根節點所在層數爲1,層序遍歷就是從所在二叉樹的根節點出發,首先訪問第一層的樹根節點,然後從左到右訪問第2層上的節點,以此類推,自上而下,自左至右逐層訪問樹的結點的過程就是層序遍歷
二叉樹結構及遍歷代碼的實現
非遞歸遍歷與遞歸遍歷的實現
非遞歸遍歷需要藉助隊列和棧的結構特點實現
這裏我們不再提供棧和隊列的接口,想用參考這兩篇博客:
隊列的實現與運用
棧的實現與運用
另外:
頭文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
typedef char 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);
int BinaryTreeLevelKSize(BTNode* root, int k);
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 遍歷
void BinaryTreePrevOrder(BTNode* root);
void BinaryTreeInOrder(BTNode* root);
void BinaryTreePostOrder(BTNode* root);
// 非遞歸遍歷
// 層序遍歷
void BinaryTreeLevelOrder(BTNode* root);
void BinaryTreePrevOrderNonR(BTNode* root);
void BinaryTreeInOrderNonR(BTNode* root);
void BinaryTreePostOrderNonR(BTNode* root);
// 判斷二叉樹是否是完全二叉樹
int BinaryTreeComplete(BTNode* root);
void TestBinaryTree();
源文件
#include "BinaryTree.h"
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
if (a[*pi] != '#')
{
BTNode* root = (BTNode*)malloc(sizeof(BTNode));
root->_data = a[*pi];
++(*pi);
root->_left = BinaryTreeCreate(a, pi);
++(*pi);
root->_right = BinaryTreeCreate(a, pi);
}
else
return NULL;
}
void BinaryTreeDestory(BTNode** root)
{
BTNode* curRoot = *root;
if (curRoot)
{
BinaryTreeDestory(&curRoot->_left);
BinaryTreeDestory(&curRoot->_right);
free(curRoot);
* root = NULL;
}
}
int BinaryTreeSize(BTNode* root)
{
if (root == NULL)
return 0;
return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right) + 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->_left);
}
//第K層的節點數
int BinaryTreeLevelKSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (root->_left == NULL && root->_right == NULL)
return 1;
return BinaryTreeLevelKSize(root->_left, k - 1) + BinaryTreeLevelKSize(root->_right, k - 1);
}
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
BTNode* ret;
if (root->_data == x)
return root;
ret = BinaryTreeFind(root->_left, x);
if (ret)
return ret;
ret = BinaryTreeFind(root->_right, x);
if (ret)
return ret;
return NULL;
}
// 先序遍歷遞歸
void BinaryTreePrevOrder(BTNode* root)
{
if (root)
{
printf("%c ", root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
else
printf("# ");
}
// 中序遍歷遞歸
void BinaryTreeInOrder(BTNode* root)
{
if (root)
{
BinaryTreeInOrder(root->_left);
printf("%c ", root->_data);
BinaryTreeInOrder(root->_right);
}
else
printf("# ");
}
// 後序遍歷遞歸
void BinaryTreePostOrder(BTNode* root)
{
if (root)
{
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
printf("%c ", root->_data);
}
else
printf("# ");
}
// 非遞歸遍歷
// 層序遍歷
void BinaryTreeLevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (QueueEmpty(&q) == 0)
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%d ", front->_data);
if (front->_left)
QueuePush(&q, front->_left);
if (front->_right)
QueuePush(&q, front->_right);
}
printf("\n");
}
// 判斷二叉樹是否是完全二叉樹
int BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (QueueEmpty(&q) == 0)
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front)
{
QueuePush(&q, front->_left);
QueuePush(&q, front->_right);
}
else
break;
}
while (QueueEmpty(&q) == 0)
{
BTNode* front = QueueFront(&q);
if (front)
{
QueueDestroy(&q);
return 0;
}
QueuePop(&q);
}
return 1;
}
// 先序遍歷非遞歸
void BinaryTreePrevOrderNonR(BTNode* root)
{
BTNode* cur = root;
BTNode* top;
Stack st;
StackInit(&st);
while (cur || StackEmpty(&st) == 0)
{
while (cur)
{
printf("%c ", cur->_data);
StackPush(&st, cur);
cur = cur->_left;
}
top = StackTop(&st);
StackPop(&st);
cur = top->_right;
}
printf("\n");
}
// 中序遍歷非遞歸
void BinaryTreeInOrderNonR(BTNode* root)
{
BTNode* cur = root;
BTNode* top,* prev;
Stack st;
StackInit(&st);
while (cur || StackEmpty(&st) == 0)
{
while (cur)
{
StackPush(&st, cur);
cur = cur->_left;
}
top = StackTop(&st);
printf("%c ", top->_data);
StackPop(&st);
cur = top->_right;
}
printf("\n");
}
// 後序遍歷非遞歸
void BinaryTreePostOrderNonR(BTNode* root)
{
BTNode* cur = root;
BTNode* top,* prev;
Stack st;
StackInit(&st);
while (cur || StackEmpty(&st) == 0)
{
if (cur)
{
StackPush(&st, cur);
cur = cur->_left;
}
top = StackTop(&st);
if (top->_right == NULL || top->_right == prev)
{
printf("%c ", top->_data);
StackPop(&st);
prev = top;
}
else
cur = top->_right;
}
printf("\n");
}
void TestBinaryTree()
{
int i = 0;
char a[] = { 'A', 'B', 'D', '#', '#', 'E', '#', 'H', '#', '#', 'C', 'F', '#', '#', 'G', '#', '#' };
BTNode* root = BinaryTreeCreate(a, &i);
BinaryTreePrevOrder(root);
printf("\n");
BinaryTreePrevOrder(root);
printf("\n");
BinaryTreePrevOrder(root);
printf("\n");
BinaryTreeDestory(&root);
i = BinaryTreeLeafSize(root);
i = BinaryTreeLevelKSize(root, 1);
}
int main()
{
TestBinaryTree();
return 0;
}