一:二叉树的顺序结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.1堆的结构及概念
1、堆在逻辑上是一颗完全二叉树(类似于一颗满二叉树只缺了右下角)。
2、堆的实现利用的是数组,我们通常会利用动态数组来存放元素,这样可以快速扩容也不会很浪费空间,我们是将这颗完全二叉树用层序遍历的方式储存在数组里的。
3、堆有两种分别是大根堆和小根堆 。
二.堆的代码实现
1.1 堆的实现
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
int arary[]={27,15,19,18,28,34,65,49,25,37}
1.2堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根结点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子结点的子树开始调整,一直调整到根结点的树,就可以调整成堆。
int aray[]={1,5,3,8,7,6}
1.3建堆的时间复杂度
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果)
所以建堆的时间复杂度为O(N)
1.4堆的插入
先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。
1.5堆的删除
删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法.
1.6堆的代码实现
typedef int HpDatatype;
typedef struct Heap
{
HpDatatype* a;
int size;
int capacity;
}HP;
void HpInit(HP* php);//堆的初始化
void Adjustup(HpDatatype* a, int child);//堆的向上调整
void Adjustdown(HpDatatype* a, int n, int parent);//堆的向下调整
void HpDestroy(HP* php);//堆的销毁
void HpPush(HP* php, HpDatatype x);//堆的插入
void HpPop(HP* php);//堆的删除
HpDatatype HPtop(HP* php);//取堆顶的数据
bool HPempty(HP* php);//堆的判空
void HpInit(HP* php)
{
php->a = NULL;
php->capacity = php->size = 0;
}
void HpDestroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
void swap(HpDatatype* p1, HpDatatype* p2)
{
HpDatatype temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void Adjustup(HpDatatype*a,int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
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* temp = (HpDatatype*)realloc(php->a, newcapacity * sizeof(HpDatatype));
if (temp == NULL)
{
perror("realloc fail");
return;
}
php->a = temp;
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)
{
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;
}
}
}
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;
}
三.用堆解决Top-k问题
TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
void text1()
{
int a[] = { 1,9,6,4,8,2,7,65,43,78,99,3,5,63 };
int size = sizeof(a) / sizeof(int);
HP hp;
HpInit(&hp);
for (int i = 0; i < size; i++)
{
HpPush(&hp, a[i]);
}
int i = 0;
scanf("%d", &i);
while (i--)
{
printf("%d ", HPtop(&hp));
HpPop(&hp);
}
}
此代码中的函数在上面的代码中有体现。