目录
一、堆
分为大堆和小堆
大堆是每个父节点都大于子节点,小堆则相反是每个父节点都小于子节点
底层抽象结构是完全二叉树,下面实现方式底层是数组
二、堆的模拟实现
1.结构体
cpp
typedef int hptype;
typedef struct heap
{
hptype* data;
int size;
int capacity;
}hp;
void hpinit(hp* x)
{
assert(x);
x->data = NULL;
x->capacity = x->size = 0;
}
void hpdestory(hp* x)
{
assert(x);
free(x->data);
x->data = NULL;
x->capacity = x->size = 0;
}
上面的typedef是为了方便改动
2.push
cpp
void swap(hptype* p1, hptype* p2)
{
hptype tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//把底下不符合堆的进行调整
//小堆
void adjustup(hptype* data, int child)
{
assert(data);
int parent = (child - 1) / 2;
while (child > 0)
{
if (data[child] < data[parent])
{
swap(&data[child], &data[parent]);
child = parent;
parent = (child - 1) / 2;
}
else { break; }
}
}
void hppush(hp* p, hptype x)
{
assert(p);
if (p->capacity == p->size)
{
int num = p->capacity == 0 ? 4 : p->capacity * 2;
hptype* newdata = (hptype*)realloc(p->data, sizeof(hptype) * num);
if (newdata == NULL)
{
perror("newdata==null");
return;
}
p->data = newdata;
p->capacity = num;
}
p->data[p->size++] = x;
adjustup(p->data, p->size - 1);
}
3.pop和top
cpp
void adjustdown(hptype* data, int size, int parent)
{
assert(data);
int small = parent * 2 + 1;
while (small < size)
{ if(small+1<size&&data[small]>data[small+1])
{
small++;
}
if (data[parent] > data[small])
{
swap(&data[parent], &data[small]);
parent = small;
small = parent * 2 + 1;
}
else { break; }
}
}
void hppop(hp* x)
{
assert(x);
assert(x->size > 0);
swap(&x->data[0], &x->data[x->size - 1]);
x->size--;
adjustdown(x->data, x->size, 0);
}
hptype hptop(hp*x)
{
assert(x);
return x->data[0];
}
bool hpempty(hp*x)
{
assert(x);
return x->size == 0;
}
pop是实现的把堆顶pop,还要保持堆的形象
应用:实现top_k个最小数的打印
思路:先将数据建堆,在获取top后pop,循环k次
cpp
void test2()
{
int a[] = { 10,2,99,4,5,9,7,8,6, };
hp x;
hpinit(&x);
for (int i=0;i<9;i++)
{
hppush(&x, a[i]);
}
int k = 0;
scanf("%d", &k);
while(k--)
{
printf("%d ", hptop(&x));
hppop(&x);
}
}
三.实现堆排序
1.成堆算法
1.adjustup+for;
2.adjustdown+for
实例逻辑是小堆
cpp
void adjustup(hptype* data, int child)
{
assert(data);
int parent = (child - 1) / 2;
while (child > 0)
{
if (data[child] < data[parent])
{
swap(&data[child], &data[parent]);
child = parent;
parent = (child - 1) / 2;
}
else { break; }
}
}
void adjustdown(hptype* data, int size, int parent)
{
assert(data);
int small = parent * 2 + 1;
while (small < size)
{ if(small+1<size&&data[small]>data[small+1])
{
small++;
}
if (data[parent] > data[small])
{
swap(&data[parent], &data[small]);
parent = small;
small = parent * 2 + 1;
}
else { break; }
}
}
for正序遍历+adjustup,时间复杂度 nlogn
cpp
void test3(){
int a[10] = { 2,3,1,5,6,7,0,39,99,33 };
for(int i=1;i<10;i++)
{
adjustup(a, i);
}
for(int i=0;i<10;i++)
{
printf("%d ", a[i]);
}
}
for从叶子节点的父节点遍历+adjustdown
cpp
void test4() {
int a[10] = { 2,3,1,5,6,7,0,39,99,33 };
for (int i = (10-1-1)/2;i >=0;i--)
{
adjustdown(a, 10,i);
}
for (int i = 0;i < 10;i++)
{
printf("%d ", a[i]);
}
}
*调整算法复杂度讨论
adjustup+for
adjustdown+for
2.堆排序
底层思想是先成堆,在取堆顶数与末尾交换,即此时末尾一定是最大或最小,再以此类推
cpp
void heapsort(hptype *x,int n)
{
assert(x);
for(int i=1;i<n;i++)
{
adjustup(x, i);
}
int end = n - 1;
while(end>0)
{
swap(&x[0], &x[end]);
adjustdown(x, end, 0);
end--;
}
}
堆排序的升降是由堆是大小堆决定的,如果建成大堆,那么顶就是最大,取到队尾就是升序
如果小堆,取到队尾就是降序
升序=>大堆
降序=>小堆
*堆排序中while+adjustdown复杂度
四.*topk问题
求一堆数据中最大的前k个,这k个返回时不需要之间大小排序
思路1:直接对数据建大堆,顶上为最大,接着pop+top继续时间复杂度:建堆O(n)+pop_topO( k*log(N) ) =>O(n)
空间复杂度:建堆O(n)
思路2:建k小堆,比顶上大的就顶上交换,然后再调整堆,以此类推时间复杂度:建堆O(K)+遍历O(N-K) =>O( n )
空间复杂度:O(K)
cpp
void topk()
{
int k = 0;
scanf("%d", &k);
int* kmax = (int*)malloc(k * sizeof(int));
FILE* fout = fopen("data.txt", "r");//先取k个数进行建堆
for(int i=0;i<k;i++)
{
fscanf(fout, "%d", &kmax[i]);
}
for(int i=(k-1-1)/2;i>=0;i--)
{
adjustdown(kmax, k, i);
}
int x = 0;
//打开文件开始遍历比较
while(fscanf(fout,"%d",&x)>0)
{
if(x>kmax[0])
{
kmax[0] = x;
adjustdown(kmax, k, 0);
}
}
//打印
for(int i=0;i<k;i++)
{
printf("%d ", kmax[i]);
}
}