1. 树
1.1 树的概念与结构
树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树,也就是说它是根朝上,⽽叶朝下的。
- 有⼀个特殊的结点,称为根结点,根结点没有前驱结点。
- 除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、……、Tm ,其中每⼀个集合Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以有 0 个或多个后继。因此,树是递归定义的。
非树形结构:
• ⼦树是不相交的(如果存在相交就是图了)
• 除了根结点外,每个结点有且仅有⼀个⽗结点
• ⼀棵N个结点的树有N-1条边
注意子树是指找一个结点为根结点和它的所有的子孙结点的总和
1.2 树相关术语
1.3 树的表⽰
孩⼦兄弟表⽰法:
树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦兄弟表⽰法等。我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法
struct TreeNode
{
struct Node* child; // 左边开始的第⼀个孩⼦结点
struct Node* brother; // 指向其右边的下⼀个兄弟结点
int data; // 结点中的数据域
};
1.4 树形结构实际运⽤场景
⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。
2. ⼆叉树
2.1 概念与结构
在树形结构中,我们最常⽤的就是⼆叉树,⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空。
从上图可以看出⼆叉树具备以下特点:
2.2 特殊的⼆叉树
2.2.1 满⼆叉树
⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为 K ,且结点总数是2k-1 ,则它就是满⼆叉树。
2.2.2 完全⼆叉树
完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度为 K 的,有 n 个结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从 1 ⾄ n 的结点⼀⼀对应时称之为完全⼆叉树。要注意的是满⼆叉树是⼀种特殊的完全⼆叉树。
2.3 ⼆叉树存储结构
⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构(底层是数组),⼀种链式结构(底层是链表)。
2.3.1 顺序结构
顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。
现实中我们通常把堆(⼀种⼆叉树)使⽤顺序结构的数组来存储,需要注意的是这⾥的堆和操作系统虚拟进程地址空间中的堆是两回事,⼀个是数据结构,⼀个是操作系统中管理内存的⼀块区域分段。
2.3.2 链式结构
⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 。链式结构⼜分为⼆叉链和三叉链,当前我们学习中⼀般都是⼆叉链。后⾯课程学到⾼阶数据结构如红⿊树等会⽤到三叉链。
3. 实现顺序结构⼆叉树
⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的⼆叉树,具有⼆叉树的特性的同时,还具备
其他的特性。
3.1 堆的概念与结构
如果有⼀个关键码的集合 ,把它的所有元素按完全⼆叉树的顺序存储⽅
式存储,在⼀个⼀维数组中,并满⾜:Ki <=K2*i+1(或Ki >=K2*i+1)则称为⼩堆(或⼤堆)。将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆叫做最⼩堆或⼩根堆。
3.2 堆的实现
堆底层结构为数组,因此定义堆的结构为:
编写代码
typedef int HPDataType;
//定义堆的结构--数组
typedef struct Heap {
HPDataType* arr;
int size;
int capacity;
}HP;
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HPInit(HP* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
void HPDestrory(HP* php)
{
assert(php);
if(php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
void swap(HPDataType* x1, HPDataType* x2)
{
HPDataType tmp = *x1;
*x1 = *x2;
*x2 = tmp;
}
void AdjustUp(HPDataType* arr, int child)
{
int parent=(child - 1) / 2;
while (parent>=0)//也可以用child,自己尝试一下
{
if (arr[child] < arr[parent])
{
swap(&arr[parent], &arr[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int NewCapacity = php->capacity == 0 ? 4 : php->capacity;
HPDataType* tmp = realloc(php->arr, sizeof(HPDataType) * NewCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
php->arr = tmp;
}
php->arr[php->size] = x;
AdjustUp(php->arr, php->size);
php->size++;
}
void AdjustDown(HPDataType* arr, int parent, int n)
{
int child = parent * 2 + 1;
while (child<n)
{
//找左右孩子中最小的
if (child+1<n && arr[child] > arr[child + 1])
{
child++;
}
if (arr[child] < arr[parent])
{
swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HPPop(HP* php)
{
assert(php && php->size);
swap(&php->arr[0], &php->arr[php->size-1]);
php->size--;
AdjustDown(php->arr, 0, php->size);
}
HPDataType HPTop(HP* php)
{
assert(php && php->size);
return php->arr[0];
}
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
3.3 堆的应⽤
3.3.1 堆排序
版本⼀:基于已有数组建堆、取堆顶元素完成排序版本
HP hp;
HPInit(&hp);
int arr[] = { 17,20,10,13,19,15 };
for (int i = 0; i < 6; i++)
{
HPPush(&hp, arr[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
arr[i++] = HPTop(&hp);
HPPop(&hp);
}
版本二:数组建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据
void HeapSort(int* arr, int n)
{
//向上调整算法建堆
//for (int i = 0; i < n; i++)
//{
// AdjustUp(arr, i);
//}
//向下调整算法建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, i, n);//保证最后以一棵子树是堆,对于一个二叉树(最多)
}
//循环将堆顶数据跟最后位置(会变化,每次减少一个数据)的数据进行交换
int end = n - 1;
while (end > 0)
{
Swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
3.4时间复杂度
3.4.1 向下调整算法复杂度
3.4.2 向上调整算法复杂度
3.4.3 堆排序算法复杂度
4. 实现链式结构⼆叉树
⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。 通常的⽅法是链表中每个结点由三个域组
成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址 ,
其结构如下:
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
⼆叉树的创建⽅式⽐较复杂,我们往往手动创建一棵二叉树
#include"Tree.h"
BTNode* buyNode(BTDataType x)
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->left = newnode->right = NULL;
return newnode;
}
void test01()
{
BTNode* node1 = buyNode(1);
BTNode* node2 = buyNode(2);
BTNode* node3 = buyNode(3);
BTNode* node4 = buyNode(4);
BTNode* node5 = buyNode(5);
BTNode* node6 = buyNode(6);
node1->left = node2;
node1->right = node3;
node2->left = node4;
node2->right = node5;
node3->left = node6;
}
回顾⼆叉树的概念,⼆叉树分为空树和⾮空⼆叉树,⾮空⼆叉树由根结点、根结点的左⼦树、根结点的右⼦树组成的
根结点的左⼦树和右⼦树分别⼜是由⼦树结点、⼦树结点的左⼦树、⼦树结点的右⼦树组成的,因此⼆叉树定义是递归式的,后序链式⼆叉树的操作中基本都是按照该概念实现的。
4.1 前中后序遍历
⼆叉树的操作通过树的遍历来实现
4.1.1 遍历规则
按照规则,⼆叉树的遍历有:前序/中序/后序的递归结构遍历:
编写代码
void PreOrder(BTNode*root)
{
if (root = NULL)
{
return;
}
printf("%d", root->data);
PreOrder(root->left);
PreOrder(root->right);
}
//中序遍历-左根右
void InOrder(BTNode* root)
{
if (root = NULL)
{
return;
}
InOrder(root->left);
printf("%d", root->data);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root = NULL)
{
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d", root->data);
}
4.2 结点个数以及⾼度等
编写代码
int BinaryTreesize(BTNode* root)
{
if (root = NULL)
{
return 0;
}
return 1+BinaryTreesize (root->left)+BinaryTreesize(root->right);
}
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);
}
int BinaryTreeLevelKsize(BTNode* root,int k)
{
if (root = NULL)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BinaryTreeLeefsize(root->left, k - 1) + BinaryTreeLeefsize(root->right, k - 1);
}
int BinaryTreeDepth(BTNode* root)
{
if (root = NULL)
{
return 0;
}
int leftDep = BinaryTreeDepth(root->left);
int rightDep = BinaryTreeDepth(root->right);
return leftDep > rightDep ? leftDep + 1 : rightDep + 1;
}
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
if (root = NULL)
{
return NULL;
}
if (root->data == x)
{
return root;
}
BTNode* leftFind = BinaryTreeFind(root->left, x);
if (leftFind)
{
return leftFind;
}
BTNode* rightFind = BinaryTreeFind(root->right, x);
if(rightFind)
{
return rightFind;
}
return NULL;
}
void BinaryTreeDestrory(BTNode** root)
{
if (*root==NULL)
{
return;
}
BinaryTreeDestrory(&(*root)->left);
BinaryTreeDestrory(&(*root)->right);
free(*root);
*root = NULL;
}
4.3 层序遍历
除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。
设⼆叉树的根结点所在层数为1,层序遍历就是从所在⼆叉树的根结点出发,⾸先访问第⼀层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历
实现层序遍历需要额外借助数据结构:队列
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
int size;
}Queue;
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInitialise(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->phead == NULL && pq->ptail == NULL)
{
return false;
}
return true;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NULL;
if (pq->phead == 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));
QueueNode* Next = pq->phead->next;
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
free(pq->phead);
pq->phead = NULL;
pq->phead = Next;
pq->size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
/*size_t size = 0;
QueueNode* pcur = pq->phead;
while (pcur)
{
size++;
pcur = pcur->next;
}*/
return pq->size;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
int size;
}Queue;
void QueueInitialise(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->phead == NULL && pq->ptail == NULL)
{
return false;
}
return true;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NULL;
if (pq->phead == 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));
QueueNode* Next = pq->phead->next;
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
free(pq->phead);
pq->phead = NULL;
pq->phead = Next;
pq->size--;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->phead->data;
}
void LevelOrder(BTNode* root)
{
Queue q;
QueueInitialise(&q);
QueuePush(&q, root);
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
printf("%d", front->data);
QueuePop(&q);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
QueuePush(&q, front->right);
}
QueueDestroy(&q);
}
4.4判断是是否为完全二叉树
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
int size;
}Queue;
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
void QueueInitialise(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->phead == NULL && pq->ptail == NULL)
{
return false;
}
return true;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NULL;
if (pq->phead == 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));
QueueNode* Next = pq->phead->next;
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
free(pq->phead);
pq->phead = NULL;
pq->phead = Next;
pq->size--;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->ptail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
/*size_t size = 0;
QueueNode* pcur = pq->phead;
while (pcur)
{
size++;
pcur = pcur->next;
}*/
return pq->size;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* phead;
QueueNode* ptail;
int size;
}Queue;
void QueueInitialise(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
if (pq->phead == NULL && pq->ptail == NULL)
{
return false;
}
return true;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* NewNode = (QueueNode*)malloc(sizeof(QueueNode));
if (NewNode == NULL)
{
perror("malloc fail!");
exit(1);
}
NewNode->data = x;
NewNode->next = NULL;
if (pq->phead == 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));
QueueNode* Next = pq->phead->next;
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
free(pq->phead);
pq->phead = NULL;
pq->phead = Next;
pq->size--;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* pcur = pq->phead;
while (pcur)
{
QueueNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(QueueEmpty(pq));
return pq->phead->data;
}
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInitialise(&q);
QueuePush(&q, root);
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
//队列不一定为空
while (QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}