由于每个节点都 只有一个父节点 ,所以我们可通过双亲来表示一棵树。具体方式通过数组的形式实现。
注意:普通二叉树可以用一维数组存储,但空间浪费严重。
图示:
说明:深度越深,空间浪费越严重
如果有一个关键码1的集合K = {k1 ,k2 ,k3 ,…, },把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足: ki<=k2*i+1 且 ki<=k2*i+2 ( ki>=k2*i+1 且ki >=k2*i+2 ), i = 0,1,2…,则称为小堆(或大堆),此处的i是节点的下标。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
//大根堆
typedef int HPDateType;
typedef struct Heap
{
HPDateType* arr;
int size;
int capacity;
}Heap;
关键思路:增和删数据
增数据:从栈底增,往上调
删数据:出栈顶,往下调
这里我实现的是:大根堆
void HeapCreate(Heap* hp)
{
HPDateType* tmp = (HPDateType*)malloc(sizeof(HPDateType) * 4);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
hp->arr = tmp;
hp->size = 0;
hp->capacity = 4;
}
void Swap(HPDateType* arr, int child, int parent)
{
int tmp = arr[child];
arr[child] = arr[parent];
arr[parent] = tmp;
}
向上调堆代码:
void AdjustUp(HPDateType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arr[parent] < arr[child])//不断与祖先比较
{
Swap(arr, child, parent);
child = parent;
parent = (child - 1) / 2;
}
else//遇到根节点或者不大于祖先就停止
{
break;
}
}
}
向堆里增加数据
void HeapPush(Heap* hp, HPDateType x)
{
int child = hp->size;
if (hp->size == hp->capacity)//判断是否需要扩容
{
HPDateType* tmp = (HPDateType*)realloc(hp->arr, \/*换行符*/
sizeof(HPDateType) * hp->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
hp->arr = tmp;
hp->capacity *= 2;
}
hp->arr[hp->size] = x;
AdjustUp(hp->arr, hp->size);
hp->size++;
}
void AdjustDown(HPDateType* arr, int parent, int size)
{
//假设左孩子为较大的孩子
int child = parent * 2 + 1;
while (child < size)//这里size是删过之后的数据个数,也是最后一个元素的下一个元素的下标
{
//先选出较大的孩子
if (child+1<size && arr[child + 1]>arr[child])
{
child++;
}
//错误示例:
// if (arr[child + 1]>arr[child]&&child+1
// {
// child++;
// }
//说明:&&从左到右进行判断,这就好比:你犯错了还要弥补有什么用?
if (arr[child] > arr[parent])
{
Swap(arr, child, parent);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp);
assert(hp->size > 0);
Swap(hp->arr, hp->size-1, 0);
hp->size--;
AdjustDown(hp->arr, 0, hp->size);
}
//取堆顶元素
HPDateType HeapTop(Heap* hp)
{
assert(hp);
assert(hp->size > 0);
return hp->arr[0];
}
高度 | 最多比较的次数 | 节点个数 |
---|---|---|
1 | h-1 | 20 |
2 | h-2 | 21 |
…… | …… | …… |
h-1 | 1 | 2h-2 |
1
(1-qn)/1-q,a1
为首项,q为等比1
=1,代入公式代码实现:
void AdjustDown(HPDateType* arr, int parent, int size)
{
//左孩子,假设左孩子为较大的孩子
int child = parent * 2 + 1;
while (child < size)
{
//先选出较大的孩子
if (child+1<size && arr[child + 1]>arr[child])
{
child++;
}
if (arr[child] > arr[parent])
{
Swap(arr, child, parent);//上文有
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapCreat(int* arr, int size)
{
//向下调整
//((size - 1) - 1) / 2 表示倒数第二层的倒数第一个节点
/*(size-1)是最后一个节点的下标*/
for (int i = ((size - 1) - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, i, size);
}
}
图略~
高度 | 最多比较的次数 | 节点个数 |
---|---|---|
2 | 1 | 21 |
3 | 2 | 22 |
…… | …… | …… |
h | h-1 | 2h-1 |
2
(N+1)-2)+22
N代码实现:
void AdjustUp(HPDateType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arr[parent] < arr[child])
{
Swap(arr, child, parent);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapCreat(int* arr, int size)
{
//向下调整
for (int i = 1; i < size; i++)
{
AdjustUpTwo(arr, i);
}
}
void HeapSort(int* arr, int size)
{
//第一步调堆
//建大根堆——升序
//第一种:向上调整
//for (int i = 1; i < size; i++)
//{
// AdjustUp(arr, i);
//}
//第二种:向下调整
for (int i = ((size - 1) - 1) / 2; i >= 0; i--)
{
AdjustDown(arr, i, size);
}
int tmp = size - 1;//最后一个元素的下标
//时间复杂度:n*logn
while (tmp)
{
Swap(arr, tmp, 0);
tmp--;
AdjustDownTwo(arr, 0, tmp+1);//tmp+1指的是当前元素的个数
}
}
我实现的是:从100000个数据中,取出前10个大的数。
所用函数:
void DatasCreat()
{
FILE* p = fopen("datas.txt", "w");
if (p == NULL)
{
perror("fopen fail");
exit(-1);
}
srand((unsigned int)time(NULL));//设置随机数种子
int i = 0;
for (i = 0; i < 1000000; i++)
{
int ret = rand() % 10000;//产生1到9999的数字
fprintf(p, "%d\n", ret);
}
fclose(p);//使用完要关闭文件
}
说明:使用过这个函数后要注释掉哦!再使用又会刷新文件数据,别问我怎么知道的。
这里先给出完整的函数声明和建小根堆的函数:
void DataSort(const char* fname, int k);
//小根堆
void AdjustUpTwo(HPDateType* arr, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (arr[parent] > arr[child])
{
Swap(arr, child, parent);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapCreat(int* arr, int size)
{
//向下调整
for (int i = 1; i < size; i++)
{
AdjustUpTwo(arr, i);
}
}
FILE* fp = fopen(fname, "r");
if (fp == NULL)
{
perror("fopen:fail");
}
int i = 0;
int arr[10] = { 0 };//也可以在堆上开辟
for (i = 0; i < 10; i++)
{
fscanf(fp, "%d", &arr[i]);
}
//建小根堆
HeapCreat(arr, sizeof(arr) / sizeof(arr[0]));
int ret = 0;
while (fscanf(fp, "%d", &ret)!=EOF)//文件的数据读完就结束
{
if (ret > arr[0])
{
arr[0] = ret;
AdjustDownTwo(arr, 0, sizeof(arr) / sizeof(arr[0]));
}
}
HeapSort(arr, 10);
for (i = 0; i < 10; i++)
{
printf("arr[%d]:%d\n", i, arr[i]);
}
fclose(fp);
汇总TOPK排序的代码:
void DataSort(const char* fname, int k)
{
FILE* fp = fopen(fname, "r");
if (fp == NULL)
{
perror("fopen:fail");
}
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
fscanf(fp, "%d", &arr[i]);
}
//建小根堆
HeapCreat(arr, sizeof(arr) / sizeof(arr[0]));
int ret = 0;
while (fscanf(fp, "%d", &ret)!=EOF)
{
if (ret > arr[0])
{
arr[0] = ret;
AdjustDownTwo(arr, 0, sizeof(arr) / sizeof(arr[0]));
}
}
HeapSort(arr, 10);
for (i = 0; i < 10; i++)
{
printf("arr[%d]:%d\n", i, arr[i]);
}
fclose(fp);
}
void DatasCreat()
{
int i = 0;
FILE* p = fopen("datas.txt", "w");
if (p == NULL)
{
perror("fopen fail");
exit(-1);
}
srand((unsigned int)time(NULL));
for (i = 0; i < 1000000; i++)
{
int ret = rand() % 10000;
fprintf(p, "%d\n", ret);
}
fclose(p);
}
希望对您有所帮助!
数据元素中能起标识作用的数据项 ↩︎