1.树
1.1树的概念与结构
树是一种非线性的数据结构,它是由 n(n >= 0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一颗倒挂的树,也就是说它是根朝上,而叶朝下的。
- 有一个特殊的结点,称之为根结点(只能有一个),根结点没有前驱结点。
- 每颗字树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。
- 子树是不相交的(如果存在相交,那就变成图了)
- 除了根结点外,每个结点有且仅有一个父结点
- 一颗N个结点的树有N-1个边
1.2相关术语
- 父结点/双亲结点:若一个结点含义子节点,则这个结点称为其子结点的父结点。A是B的父结点,A是C的父结点。
- 子结点/孩子结点:一个结点含有的子树的根结点称为该结点的子结点。B是A的子结点。
- 结点的度:一个结点有几个孩子,它的度就是多少。A的度为2,B的度为3。
- 树的度:一棵树中,最大的结点的度。上图:树的度为3。
- 叶子结点/终端结点:度为0的结点称为叶结点。D、H、I、F、G。
- 分支结点/非终端结点:度不为0的结点。B、C、E。
- 兄弟结点:具有相同父结点的结点互称为兄弟结点(亲兄弟)。B和C就是兄弟结点。
- 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推。
- 树的高度或深度:树中结点的最大层次。上图为4。
- 结点的祖先:从根到该结点所经分支上的所有结点。A是所有结点的祖先。
- 路径:一条从树中任意结点出发,沿父结点—子结点连接,达到任意结点的序列。比如A到H:A—B—E—H。
- 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。所有结点都是A的子孙。
- 森林:有m(m > 0)棵互不相交的树集合称为森林。
1.3树的表示
孩子兄弟表示法:
树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦,既要保存值域,也要保存结点和结点之间的关系,实际中树有很多种表示方法如:双亲表示法,孩子表示法,孩子双亲表示法以及孩子兄弟教师法等。但此刻就介绍最常用的:孩子兄弟表示法。
struct TreeNode
{
struct TreeNode* child; //左边开始的第一个孩子结点
struct TreeNode* brother; //指向其右边的下一个兄弟结点
int data; //结点中的数据域
}
1.4树形结构实际运用场景
文件系统是计算机存储和管理文件的一种方式,它利用树形结构来组织和管理文件和文件夹。在文件系统中,树的结构被广泛应用,它通过父结点和子结点之间的关系来表示不同层级的文件和文件夹之间的关联。
2.二叉树
2.1概念与结构
在树形结构中,我们最常用的就是二叉树,一颗二叉树是结点的一个有限集合,该集合由一个根结点加上两颗别称为左子树和右子树的二叉数组组成或者为空。
2.2特殊的二叉树
2.2.1满二叉树
一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为k,且结点总数是2的k次方-1,则它就是满二叉树。
2.2.2完全二叉树
完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来。对于深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树。(假设有K层,最后一层从左到右放,但是K-1包括K-1之前的层数都达到了最大)
2.3二叉树存储结构
二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
2.3.1顺序结构
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费,完全二叉树更适合使用顺序结构存储。
2.3.2链式结构
二叉树的链式结构是指,用链表来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址。
3.实现顺序结构的二叉树
一般堆使用顺序结构的数组来存储数据,堆是一种特殊的二叉树,具有二叉树的特性的同时,还具备其他的特性。
3.1堆
- 若 i > 0,i 位置结点的双亲序号:(i - 1)/ 2;i = 0,i 为根结点编号,无双亲结点
- 若 2i +1 < n,左孩子序号:2i+1 右孩子序号:2i+2
4.源代码
Heap.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>
typedef int DataType;
//定义堆的结构——数组
typedef struct Heap
{
DataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}HP;
void HPInit(HP* php);//初始化
void HPDestroy(HP* php);//销毁
void HPPush(HP* php,DataType x);//插入
void HPPop(HP* php);//删除
DataType HPTop(HP* PHP);//出堆顶元素
bool HPEmpty(HP* php);//true为空,false为不空
void ADJustDown(DataType* arr, int parent, int n);//向下调整算法
void ADJustUp(DataType* arr, int child);//向上调整算法
Heap.c
#include "Heap_Tree.h"
void HPInit(HP* php)
{
assert(php);
php->arr = NULL;
php->size = php->capacity = 0;
}
void HPDestroy(HP* php)
{
assert(php);
if(php->arr)
free(php->arr);
php->arr = NULL;
php->size = php->capacity = 0;
}
void Swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
void ADJustUp(DataType* arr,int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//大堆:>
//小堆:<
if (arr[child] < arr[parent])
{
Swap(&arr[child], &arr[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HPPush(HP* php, DataType x)
{
assert(php);
//判断空间是否足够
if (php->size == php->capacity)
{
//扩容
int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
DataType* tmp = (DataType*)realloc(php->arr, newcapacity*sizeof(DataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
php->arr = tmp;
php->capacity = newcapacity;
}
php->arr[php->size] = x;
ADJustUp(php->arr, php->size);
php->size++;
}
void ADJustDown(DataType* 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);
}
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
DataType HPTop(HP* php)
{
assert(php && php->size);
return php->arr[0];
}
//堆排序
void HeapSort(int* arr,int n)
{
//向上调整算法建堆(小堆)
for (int i = 0; i < n; i++)
{
ADJustUp(arr,i);
}
//向下调整算法建堆(大堆)
for (int i=(n-2)/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--;
}
}
4.1 TOPK问题
#include "Heap_Tree.h"
void test01()
{
HP hp;
HPInit(&hp);
int arr[] = {17,20,10,13,19,15 };
for (int i = 0; i < 6; i++)
{
HPPush(&hp,arr[i]);
}
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
HPPop(&hp);
}
HPDestroy(&hp);
}
void CreateNDate()
{
//造数据
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file,"w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i=0;i<n;i++)
{
int x = (rand() + i) % 100000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void TOPK()
{
int k = 0;
printf("请输入K:");
scanf("%d",&k);
//从文件中读取前K个数据,建堆
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fepen fail");
exit(1);
}
int* minHeap = (int*)malloc(k * sizeof(int));
if (minHeap == NULL)
{
perror("malloc fail");
exit(2);
}
//从文件读取k个数据,开始建堆
for (int i=0;i<k;i++)
{
fscanf(fout,"%d",&minHeap[i]);
}
//建小堆
for (int i = (k-1-1)/2; i >=0 ; i--)
{
ADJustDown(minHeap,i,k);
}
int x = 0;
while (fscanf(fout,"%d",&x)!=EOF)
{
//读取到的数据跟堆顶的数据进行比较
//比堆顶大,就交换入堆
if (x > minHeap[0])
{
minHeap[0] = x;
ADJustDown(minHeap, 0, k);
}
}
for (int i = 0; i < k; i++)
{
printf("%d ", minHeap[i]);
}
fclose(fout);
}
int main()
{
//test01();
//CreateNDate();
TOPK();
return 0;
}