目录
1.二叉树定义:
二叉树的每一个节点最多有两个子树
2. 二叉树的特殊性质:
1. 不存在度大于2的结点
2. 子树有左右之分,是有序树
3.二叉树的数学关系:
1.第k层最多有2^(k-1)个节点
2.当有k层时,二叉树的最多节点为2^k-1
3.度为0其叶结点个数为n , 度为2的分支结点个数为 ,则有n+1个,而度数为1的节点最多有1个
4.若从上至下一次排序,某个子节点序号为i,则该节点的父节点为(i-1)/2
5.若从上至下一次排序,某个节点序号为i且有子节点,则左子树为2i+1,右子树为2i+2
4.二叉树的存储方式
可以是数组依顺序存储,该方法存储方便,但是对二叉树中的内容进行操作时需要用到二叉树的数学关系,即子节点与父节点的关系;也可以用链表,链表适用于更多度的树,那样方便存储
5.堆的概念
满二叉树:每层的节点全满
完全二叉树:最后一层少节点且最后一层存在的节点在逻辑结构上是连续的,没有间断的
堆:完全二叉树的形式存储数据
小堆:父节点存储的数据小于子节点的数据,子节点见没有大小顺序的差别
大堆:父节点存储的数据大于子节点的数据,子节点见没有大小顺序的差别
6.堆的实现
1.建立堆的元素
操作说明:为了存储的方便,选择的存储方式为数组。为了能够扩充数组,设计需要有动态开辟空间所以存储了数组的指针
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
2.堆的初始化
操作说明:传入的数据设计为堆的指针,符合改变什么传入该数据的指针一操作
void HeapInit(Heap* pf)
{
assert(pf);
pf->a = NULL;
pf->size = pf->capacity = 0;
}
3.堆的插入
操作说明:
1.先定设计的是小堆还是大堆,无论二则哪个操作的方式一样,所以方便后续考虑我们选择小堆
2.如果一个一个放入数据并且要排成小堆,首先拿到一个数据要先看看数组能不能存储下,所以先判断是否要扩容
if (pf->size == pf->capacity)
{
int newcapacity = pf->capacity == 0 ? 4 :pf->capacity * 2;
pf->capacity = newcapacity;
HPDataType* ret = (HPDataType*)realloc(pf->a, sizeof(HPDataType) * pf->capacity);
if (ret == NULL)
{
perror("realloc fail");
exit(-1);
}
pf->a = ret;
}
pf->a[pf->size] = x;
3.其次,我们将一个数先放到最后,因为计算机也不能知道自己存入的数据到底跟原数组的每个元素的大小关系,所以我们需要写一个判断的函数,实现的要求如下:要判断这个数跟他的父节点的数比哪个小,子节点小的往上反之就停止(子节点有了父节点怎么找呢?用到了数学的性质,节点的父节点为(i-1)/2!)
void HeapDataUp(HPDataType* a,int child)
{
int parent = (child - 1) / 2;
while (child != 0)
{
if (a[parent] > a[child])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
4.最后将自己现有多少数加一,以下是最终代码实现
void HeapPush(Heap* pf, HPDataType x)
{
assert(pf);
if (pf->size == pf->capacity)
{
int newcapacity = pf->capacity == 0 ? 4 :pf->capacity * 2;
pf->capacity = newcapacity;
HPDataType* ret = (HPDataType*)realloc(pf->a, sizeof(HPDataType) * pf->capacity);
if (ret == NULL)
{
perror("realloc fail");
exit(-1);
}
pf->a = ret;
}
pf->a[pf->size] = x;
HeapDataUp(pf->a, pf->size);
pf->size++;
}
4.堆的删除
操作说明:我们能删除的最简单方法就是删最后一个数,但是堆的实现删除是用来删除头节点的数,因为该数为最大或者最小,其他的地方其实没有实际意义。那么我们想删除最上方的那个数则需要和最末尾的数先进行交换,这样就能很好的删除,但是删除了以后最上方的数变成不是要求的数了,那么这是第二层的子树为仅次于删除数要求的两个数,但是二者也还需要比较,所以我们需要写一个节点向下换的函数,函数实现为:比一下左右节点哪个更大或者更小,满足要求的跟父节点的数交换,如果只有一个就判断一个就行(父节点有了子节点怎么找呢?用到了数学的性质,节点的左子树为2i+1,右子树为2i+2)
void HeapDataDown(HPDataType* a, int n, int parent)
{
int minchild = parent * 2 + 1;
while (minchild < n)
{
if (minchild + 1 < n && a[parent * 2 + 1] > a[parent * 2 + 2])
{
minchild = parent * 2 + 2;
}
if (a[minchild] < a[parent])
{
Swap(&a[minchild], &a[parent]);
parent = minchild;
minchild = parent * 2 + 1;
}
else
{
break;
}
}
}
删除的函数实现
void HeapPop(Heap* pf)
{
assert(pf);
assert(!HeapEmpty(pf));
Swap(&pf->a[0], &pf->a[pf->size - 1]);
pf->size--;
HeapDataDown(pf->a, pf->size, 0);
}
5.判空
bool HeapEmpty(Heap* pf)
{
assert(pf);
return pf->size == 0;
}
6.取堆顶的数据
void HeapPop(Heap* pf)
{
assert(pf);
assert(!HeapEmpty(pf));
Swap(&pf->a[0], &pf->a[pf->size - 1]);
pf->size--;
HeapDataDown(pf->a, pf->size, 0);
}
7.堆的数据个数
int HeapSize(Heap* pf)
{
return pf->size;
}
8.堆的销毁
void HeapDestory(Heap* pf)
{
assert(pf);
free(pf->a);
pf->a = NULL;
pf->size = pf->capacity = 0;
}
7.堆的应用
因为堆的特殊存储,即大堆和小堆,所以一般能进行排序的工作。
1.堆排列
操作说明:以大堆作为例子,大堆能确定的是第一层的数一定是最大的数,能定下来的便是最大数,但是怎么将他固定到确定的位置并且后续操作不对其进行干扰呢?我们知道size可以进行操作达到不去访问最后一个数,所以我们可以拿最后一个数跟最大数进行交换,如何size--,使得最大的数固定,但是最顶上的数却不是最大的数,需要向下调回,因此调用堆的下调函数进行调整,这样又变成了排除掉最大数的大堆,顶上的数为第二大的数这样就可以依次把这个堆里的最大数给拉出来。能知道大堆最终得到的数组是升序数组,那么同理小堆可以得到降序的数组。当然小堆排序需要对下调函数进行修改,使得最后排序出的一定是小堆的形式。
void HeapSort(int* a, int n)
{
int i = 0;
for (i = (n - 1 - 1) / 2; i >= 0; i--)
{
HeapDataDown(a, n, i);
}
// 升序排列靠大堆
for (i = n - 1; i >= 0; i--)
{
Swap(&a[0], &a[i]);
HeapDataDown(a, i, 0);
}
}
2.TopK问题:找出N个数里面最大/最小的前K个问题。
操作说明:在庞大的数据汇总中找到K个最大或者最小的数,其实在内存一个一个存储并且遍历比较会比较难,就像一个文本中有超过10000的数据,这样对内存的条件很苛刻,并且开辟空间在堆上操作也可能会超内存,那么我们就不能考虑所有数据都存储下来。按照规定我们需要K个最大的数(以小堆为例),我们可以遍历一遍数据,只要数据大于小堆的最小数就把小堆的最小数给换了,并且把最顶上的数向下调整,使得堆重新变为小堆,这样遍历完庞大数据就能留下最大的K个数,如果还要对K个数进行排列,那么就可以用堆排序,当以下只展示了如何找到K个最大数。该文本的数据是通过随机生成得来的。
void PrintTopK(const char* filename, int N, int k)
{
int* arr = (int*)malloc(sizeof(int) * k);
FILE* f = fopen(filename, "r");
if (f == NULL)
{
perror("fopen file");
exit(-1);
}
for (int i = 0; i < k; i++)
{
fscanf(f, "%d", &arr[i]);
}
int i = 0;
for (i = (k - 1 - 1) / 2; i >= 0; i--)
{
HeapDataDown(arr, k, i);
}
int tmp = 0;
while (fscanf(f, "%d", &tmp) != EOF)
{
if (tmp > arr[0])
{
Swap(&tmp, &arr[0]);
HeapDataDown(arr, 10, 0);
}
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
fclose(f);
}
void TestTopk(const char* filename, int N)
{
FILE* f = fopen(filename, "w");
if (f == NULL)
{
perror("fopen file");
exit(-1);
}
srand(time(0));
for (int i = 0; i < N; i++)
{
fprintf(f, "%d ", rand());
}
fclose(f);
}