一、树
树是一种非线性数据结构,它可以看作是由 n(n⩾0) 个具有父子关系的节点组成的有限集合。
- 根:树的最顶端节点,是整个树的起点。在上面的示例树中,节点 A 是根。
- 节点的度:一个节点拥有的子树数目称为该节点的度。如节点 A 的度是 6。
- 叶节点:度为零的节点成为叶节点。如P、Q、K等
- 父节点:一个节点的直接上级节点称为其父节点。如节点 B 和 C 的父节点是节点 A。
- 子节点:一个节点的直接下级节点称为其子节点。例如,在示例树中,节点 B 和 C 是节点 A 的子节点。
- 兄弟节点:同一父节点下的子节点互为兄弟关系。如节点 B 和 C 是兄弟节点。
- 祖先:从根节点到某节点路径中的所有节点都是该节点的祖先。例如,节点 J的祖先是 A 和 E。
- 子孙节点:一个节点的所有子节点、子节点的子节点等等都是它的子孙。在示例树中,节点 A 的子孙包括 B、C、D、E、F、I、Q。
- 树的深度:树中节点的最大层次称为树的深度。在示例树中,根节点 A 处于第 1 层,节点 B、C 处于第 2 层,节点 H、I处于第 3 层,节点P、Q处于第4层。因此,该树的深度是 4。
- 数的度:一棵树中,最大度的结点的度称为树的度。该树的度为6。
- 森林:由m(m>0)不相交的树的集合称为森林。
树的性质 - 子树是不相交的
- 除了根节点外,每个节点仅有一个父节点
- 一个有n个结点的树有n-1条边
二、二叉树
二叉树是一种特殊的树形数据结构,每个节点最多有两个子节点,通常被称为左子节点和右子节点。
满二叉树
所有非叶子节点都有两个子节点,并且所有的叶子节点都在同一层的二叉树。满二叉树具有最大的节点数和最紧凑的结构。
完全二叉树
除最后一层外,每一层都被完全填满,并且最后一层的节点是连续的。
- 对于任何一棵二叉树,度为0的节点比度为2的节点多一个
- 高度为h的满二叉树,每层节点数为2(h-1),总结点数为2h-1
- 高度为h的完全二叉树,第h层节点范围是[2(h-1),2h-1]
三、堆
- 堆是一种完全二叉树的数据结构,分为最大堆和最小堆
- 最大堆:每个节点的值都大于或等于其子节点的值,根节点是堆中的最大值。
- 最小堆:每个节点的值都小于或等于其子节点的值,根节点是堆中的最小值。
完全二叉树在数组中父子位置的下标关系 - parent=(child-1)/2
- leftchild=parent*2+1
- rightchild=parent*2+2
如何创建最大堆
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
一、堆的初始化
void HeapInit(HP* php)
{
assert(php);
php->a = (HPDataType*)malloc(sizeof(HPDataType) * 4);
if (php->a == NULL)
{
perror("malloc fail");
return;
}
php->size = 0;
php->capacity = 4;
}
二、堆的销毁
void HeapDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
三、判断堆是否为空
bool HeapEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
四、获取堆的大小
int HeapSize(HP* php)
{
assert(php);
return php->size;
}
五、获取堆顶数据
HPDataType HeapTop(HP* php)
{
assert(php);
return php->a[0];
}
六、向堆中添加数据
void AdjustUp(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;
}
}
}
void HeapPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity * 2);
if (tmp == NULL);
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity *= 2;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size-1);//尾插之后,从下向上调整,才能符合二叉树的规则
}
七、从堆中删除数据
void AdjustDown(HPDataType* a, int n, int parent)
{
int child = parent * 2 + 1;//左孩子
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])//从左右孩子中选出较大的那一个
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]);
parent = child;//向下走
child = parent*2+1;
}
else
{
break;
}
}
}
void HeapPop(HP* php)
{
assert(php);
assert(!HeapEmpty(php));
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);//从上向下调整
}
代码测试
int main()
{
HP hp;
HeapInit(&hp);
HeapPush(&hp, 4);
HeapPush(&hp, 18);
HeapPush(&hp, 42);
HeapPush(&hp, 12);
int k = 0;
scanf("%d", &k);
while (!HeapEmpty(&hp) && k--)
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
printf("\n");
return 0;
}
堆的应用
1堆排序
- 将数组构建成一个最大堆(升序排序)或最小堆(降序排序)。
- 重复删除堆顶元素,并将其放入已排序序列的末尾。
- 重新调整堆,直到排序完成。
void HeapSort(int* a, int n)
{
// 建堆 -向下调整建堆(升序)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
//n-1是最后一个数据的下标,找到父亲节点,(child-1)/2
{
AdjustDown(a, n, i);
}
// 自己先实现 -- O(N*logN)
int end = n - 1;
while (end > 0)
{
Swap(&a[end], &a[0]);
AdjustDown(a, end, 0);
--end;
}
}
TOP——K问题
- 一般求数组中前k个最大或最小的元素
- 求前k个最大元素,建小堆
- 求前k个最小元素,建大堆
- 用数据剩余n-k个元素依次与堆顶比较,不满足条件则与堆顶替换
- 比较完后,堆中剩余的的k个元素就是所求
void PrintTopK(const char* file, int k)
{
// 1. 建堆--用a中前k个元素建小堆
int* topk = (int*)malloc(sizeof(int) * k);
assert(topk);
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
// 读出前k个数据建小堆
for(int i = 0; i < k; ++i)
{
fscanf(fout, "%d", &topk[i]);
}
for (int i = (k-2)/2; i >= 0; --i)
{
AdjustDown(topk, k, i);
}
// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换
int val = 0;
int ret = fscanf(fout, "%d", &val);
while (ret != EOF)
{
if (val > topk[0])
{
topk[0] = val;
AdjustDown(topk, k, 0);
}
ret = fscanf(fout, "%d", &val);
}
for (int i = 0; i < k; i++)
{
printf("%d ", topk[i]);
}
printf("\n");
free(topk);
fclose(fout);
}
void CreateNDate()
{
// 造数据
int n = 10000000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (size_t i = 0; i < n; ++i)
{
int x = rand() % 10000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
int main()
{
//int a[10] = { 2, 1, 5, 7, 6, 8, 0, 9, 4, 3}; // 对数组排序
//HeapSort(a, 10);
CreateNDate();
//PrintTopK("data.txt", 10);
return 0;
}