目录
1.二叉树的顺序结构及实现
1.1二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.2 堆的概念及结构
如果有一个关键码的集合K = { k0,k1,k2,,,,,,,,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: ki<=k2*i 且 ki <= k2*i+2 (k >= k2*i+1 且ki >= k2*i+2) i = 0,1, 2…,则称为小堆(或大堆)。将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
堆的性质:
如下图所示:
1.3堆算法的实现
1.3.1堆结构的实现
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
1.3.2堆操作函数
//堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调正算法
void AdjustUp(HPDataType* a, int child);
//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);
//堆的初始化
void HPInit(HP* php);
//堆的销毁
void HPDestroy(HP* php);
//数据近堆
void HPPush(HP* php, HPDataType x);
//堆顶元素出堆
void HPPop(HP* php);
//输出队顶元素
HPDataType HPTop(HP* php);
//堆的判空
bool HPEmpty(HP* php);
1.3.2.1堆的初始化
void HPInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
1.3.2.2堆的销毁
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
1.3.2.3堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
1.3.2.4堆的调整算法
1.3.2.4.1向上调整建堆(以小根堆为例)
如下图所示(以小根堆为例):
代码展示:
void AdjustUp(HPDataType* a, int child)
{
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
这里我们的循环停止条件有两种:
1.3.2.4.2向下调整建堆(以小根堆为例)
代码展示:
void AdjustDown(HPDataType* a, int n, int parent)
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // child >= n说明孩子不存在,调整到叶子了
{
// 找出小的那个孩子
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
1.3.2.5数据进堆
代码展示:
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;
php->size++;
AdjustUp(php->a, php->size - 1);
}
1.3.2.6堆顶元素出堆
void HPPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
1.3.2.7输出堆顶元素
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
1.3.2.8堆的判空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
练习:
2.堆排序
2.1向上调整建堆
2.1.1小根堆
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
HPPush(&hp, a[i]);
}
测试数据:
int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
//a[i++] = HPTop(&hp);
HPPop(&hp);
}
printf("\n");
输出结果:
2.1.2大顶堆
建立大顶堆只需要改变我们之前写的向上向下调整代码:
向上调整代码:
void AdjustUp(HPDataType* a, int child)
{
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
//如果孩子比parent大
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
向下调整代码:
void AdjustDown(HPDataType* a, int n, int parent)
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child < n) // child >= n说明孩子不存在,调整到叶子了
{
// 找出小的那个孩子
//找出大的那个孩子
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
//如果孩子比母亲大
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
测试数据和代码不变:
2.2向下调整建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
2.2.1小根堆
测试数据 int a[] = { 4,2,8,1,5,6,9,7,2,7,9 };
2.2.2大根堆
数据和小根堆一样:
2.3堆排序的完整代码参考:
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
printf("%d ", a[0]);
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
int main()
{
int a[] = { 4,2,8,1,5,6,9,7,2,7,9 };
HeapSort(a, sizeof(a) / sizeof(int));
return 0;
}
2.4建堆的时间复杂度
2.4.1向下调整建堆的时间复杂度:
2.4.1向上调整建堆的时间复杂度:
2.5堆排序的应用-- TOP-K问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
方法一:
这个方法是可行的,不过不够完美,因为再TOP K问题中,N往往是很大的,这样你建堆的一个内存就很大,算法的空间损耗会很大
方法二:
测试方法二:
创建数据:
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) % 10000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
输出结果:
TOP-K求解:
void TestHeap3()
{
int k;
printf("请输入k>:");
scanf("%d", &k);
int* kminheap = (int*)malloc(sizeof(int) * k);
if (kminheap == NULL)
{
perror("malloc fail");
return;
}
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
// 读取文件中前k个数
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &kminheap[i]);
}
// 建K个数的小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(kminheap, k, i);
}
// 读取剩下的N-K个数
int x = 0;
while (fscanf(fout, "%d", &x) > 0)
{
if (x > kminheap[0])
{
kminheap[0] = x;
AdjustDown(kminheap, k, 0);
}
}
printf("最大前%d个数:", k);
for (int i = 0; i < k; i++)
{
printf("%d ", kminheap[i]);
}
printf("\n");
}
手动更改十个数据大于10000000的数,查看结果:
输出结果:
参考代码:
Heap.h:
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}HP;
//堆顶和堆尾元素的交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调正算法
void AdjustUp(HPDataType* a, int child);
//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);
//堆的初始化
void HPInit(HP* php);
//堆的销毁
void HPDestroy(HP* php);
//数据近堆
void HPPush(HP* php, HPDataType x);
//堆顶元素出堆
void HPPop(HP* php);
//输出队顶元素
HPDataType HPTop(HP* php);
//堆的判空
bool HPEmpty(HP* php);
Heap.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HPInit(HP* php)
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HPDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{
// 初始条件
// 中间过程
// 结束条件
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
//如果孩子比parent大
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
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 * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
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) // child >= n说明孩子不存在,调整到叶子了
{
// 找出小的那个孩子
//找出大的那个孩子
if (child + 1 < n && a[child + 1] < a[child])
{
++child;
}
//如果孩子比母亲大
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// logN
void HPPop(HP* php)
{
assert(php);
assert(php->size > 0);
Swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->a[0];
}
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
text.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include <time.h>
#include"Heap.h"
void TestHeap1()
{
int a[] = { 4,2,8,1,5,6,9,7,3,2,23,55,232,66,222,33,7,1,66,3333,999 };
HP hp;
HPInit(&hp);
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
HPPush(&hp, a[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
//a[i++] = HPTop(&hp);
HPPop(&hp);
}
printf("\n");
// 找出最大的前k个
/*int k = 0;
scanf("%d", &k);
while (k--)
{
printf("%d ", HPTop(&hp));
HPPop(&hp);
}
printf("\n");*/
HPDestroy(&hp);
}
// 堆排序 O(N*logN)
// 冒泡排序 O(N^2)
void HeapSort(int* a, int n)
{
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
printf("%d ", a[0]);
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void CreateNDate()
{
// 造数据
int n = 100000;
srand((unsigned int)time(NULL));
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) % 10000000;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void TestHeap3()
{
int k;
printf("请输入k>:");
scanf("%d", &k);
int* kminheap = (int*)malloc(sizeof(int) * k);
if (kminheap == NULL)
{
perror("malloc fail");
return;
}
const char* file = "data.txt";
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
perror("fopen error");
return;
}
// 读取文件中前k个数
for (int i = 0; i < k; i++)
{
fscanf(fout, "%d", &kminheap[i]);
}
// 建K个数的小堆
for (int i = (k - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(kminheap, k, i);
}
// 读取剩下的N-K个数
int x = 0;
while (fscanf(fout, "%d", &x) > 0)
{
if (x > kminheap[0])
{
kminheap[0] = x;
AdjustDown(kminheap, k, 0);
}
}
printf("最大前%d个数:", k);
for (int i = 0; i < k; i++)
{
printf("%d ", kminheap[i]);
}
printf("\n");
}
int main()
{
//CreateNDate();
TestHeap3();
return 0;
}