![](https://i-blog.csdnimg.cn/blog_migrate/3fd6dc86f3884d4fc9d5433ce072c33d.png)
树:(如上图)
树的基本概念:
节点的度:一个节点连接子节点的个数。如A节点的度为3,B节点的度为2。
树的度:树的度为最大节点度,在树中A的节点最大为3,所以树的节点的为3。
根节点:无父母节点的节点,树的节点是A
叶节点:节点的度为0的节点,叶节点:E F G H I。
祖先节点:根节点
父母节点和子节点:A是B,C,D的父母节点,B,C,D是A的子节点。
兄弟节点,有同一个父节点。BCD互为兄弟节点。
树的高度或者深度:树的层数,上图树的高度为3,空树为0,只有根节点为1。
树的子节点只能用一个父节点,兄弟节点不相连。
二叉树:只有两个子节点的树。
![](https://i-blog.csdnimg.cn/blog_migrate/c246a73e29bf0b3755833553e99a0d0e.png)
完全二叉树:
二叉树的每一层从左到右都是满的,二叉树的图片就不是完全二叉树,下面这个才是。
![](https://i-blog.csdnimg.cn/blog_migrate/c6c37adc1c3866b18b3229c79e27bc7d.png)
满二叉树:
每一层都没有空节点。
![](https://i-blog.csdnimg.cn/blog_migrate/309801b76d749c23b3d81c9089cbe9ae.png)
树的存储方式可以用链表:
struct Tree
{
int val;
int*brother;
int*child;
}
child指针连接第一个子节点,兄弟指针连接兄弟节点。
堆是完全二叉树
堆的实现可以用链表,但更简单的方式是用数组。
![](https://i-blog.csdnimg.cn/blog_migrate/cec7960f4842500a040fcf988801fc3b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8dc75636b253c7481ede940d3ba297b0.png)
如图,利用下标间的关系,把树存在数组中
下标从零开始
(子节点下标-1)/2=父节点下标
父节点下标*2+1等于左子节点下标
父节点下标*2+2等于右子节点下标
节点数量和高度关系(等比数列求和)
节点数量范围[2^(h-1),2^h-1]
不同度的节点的关系
N0=N2+1
N1=1或0
分析:满二叉树由等比数列来看N0:2^k-1-(2^(k-1)-1)=2^(k-1)
N2=2^(k-1)-1是对的。
大小根堆:
大根堆,父节点大于或等于子节点
小根堆,父节点大于或等于子节点。
大根堆的实现:
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<stdio.h>
typedef int HeapDatatype;
typedef HeapDatatype HD;
typedef struct Heap
{
HD* hp;
int size;
int capacity;
}Heap;
void HeapInit(Heap* php);
void HeapDestory(Heap* php);
void HeapPush(Heap* php,HD x);
void HeapPop(Heap* php);
int HeapTop(Heap* php);
Heap* HeapAddspace(Heap* php);
bool HeapEmpty(Heap* php);
bool HeapFull(Heap* php);
void HeapPrint(Heap* php);
#include"Heap.h"
void HeapInit(Heap* php)
{
assert(php);
php->hp = (HD*)malloc(sizeof(HD) * 4);
if (!php->hp)
{
perror("malloc error");
return ;
}
php->size = -1;
php->capacity = 4;
}
void HeapDestory(Heap* php)
{
assert(php);
free(php->hp);
free(php);
}
void swap(HD* p1, HD* p2)
{
int cmp = *p1;
*p1 = *p2;
*p2 = cmp;
}
void adjustup(HD* arr, int child, HD x)
{
int parent = (child - 1) / 2;
while (arr[parent] < arr[child])
{
swap(&arr[parent], &arr[child]);
child = parent;
parent = (child - 1) / 2;
if (arr[parent] == arr[child])
{
break;
}
}
}
void HeapPush(Heap* php,HD x)
{
assert(php);
if (HeapFull(php))
{
php=HeapAddspace(php);
}
php->hp[++php->size] = x;
adjustup(php->hp,php->size,x);
}
void adjustdown(HD*arr,int size)
{
int parent = 0;
int child = parent * 2 + 1;
while (child<=size)
{
if (child+1<=size&&arr[child] <arr[child + 1])
{
child++;
}
if (arr[parent] > arr[child])
{
break;
}
swap(&arr[parent], &arr[child]);
parent = child;
child = parent * 2 + 1;
}
}
void HeapPop(Heap* php)
{
assert(php);
if (HeapEmpty(php))
{
return;
}
swap(&php->hp[0], &php->hp[php->size]);
php->size--;
adjustdown(php->hp,php->size);
}
int HeapTop(Heap* php)
{
assert(php);
assert(php->hp);
return php->hp[0];
}
Heap* HeapAddspace(Heap* php)
{
assert(php);
HD* tmp = (HD*)realloc(php->hp, sizeof(HD) * (php->capacity) * 2);
if (!tmp)
{
perror("realloc error");
return NULL;
}
php->hp = tmp;
php->capacity *= 2;
return php;
}
bool HeapEmpty(Heap* php)
{
assert(php);
return php->size == -1;
}
bool HeapFull(Heap* php)
{
assert(php);
return php->size+1 == php->capacity;
}
void HeapPrint(Heap* php)
{
int count = 0;
while (count <=php->size)
{
printf("%d->", php->hp[count]);
count++;
}
printf("\n");
}
关键在于adjustup()和adjustdown()这两个函数
adjustup()向上调整函数
在push时我们把x放在最新的位置,然后和父节点比较,大于就交换,小于就停止,最极限的情况是x成为新节点,此时child和parent会相同且等于0,跳出循环即可。
adjustdownn()向下调整函数,
在pop掉根节点时,我们把size位置的值x和根节点的值交换,并size--来控制有效数据数量,当x成为根节点时,让他向下移动,找到最大的孩子节点比较,大于x和x交换,小于就不变,循环停止条件是小于或者子节点大于size值。注意比较子节点时child+1不要越界。
仔细想想大小堆有什么用?
大堆可以不断pop出最大值,次大值,次次大值....
可以做到排序的作用而且效率非常高,n个数据找到最大值时间复杂度logN,n个数据排序nlogN.