堆
长得像树的数组
啥玩意是堆啊
堆,其实就是一个数组,按一定的规律排列其中的元素。他虽然是数组,但是可以看成一个完全树。
-
以二叉堆(这也是用的最多的堆)为例,按图形展示他就长这样:
在一个完整的大堆中,所有的有子节点的节点,都是一个小堆的堆顶元素。 -
按照数组的形式表示的话就长这样:
其中连线表示前面的节点是后面的节点的父节点。
堆又有什么性质呢
- 堆分为两种,一种是最大堆,一种是最小堆,后面都以最大堆为例。
- 在最大堆中,所有的父节点都大于他的子节点,但是子节点没有顺序要求,毕竟不是平衡二叉树。
- 满足完全二叉树的定义,也就是除了最后一层,其他层的节点必须是满的。
- 如果是用数组储存的话,那么假设某个节点的下标是i,那么他的两个子节点就是 2i 和 2i + 1。同理,他的父节点的下标就是 i / 2。
代码部分
核心:维护堆的性质
为了维护堆的基本性质,那么就需要写个函数出来,满足父节点一定大于子节点。
那么在不满足的条件下,也就是父节点小于了子节点,这很好办,交换就完事了。
但是父节点的父节点也小于这个牛逼的子节点呢,万一祖宗结点还是小于这个字结点呢。
所以我们不能好事只做一般,光把这个子节点和他的父节点换了,还要一直往上找,一直比较然后找到他的老祖宗,并且依次交换。
这里可以用递归也可以直接用个while循环。
- 算法导论上的伪代码:
- 我自己按照伪代码写的程序:
//堆的构造,输入数组、构造点和数组长度
void heapify(int A[], int i , int n)
{
int l = i*2;
int r = i*2+1;
int largest = i;
if(l <= n-1 && A[l] > A[largest])
largest = l;
if(r <= n-1 && A[r] > A[largest])
largest = r;
if(i != largest)
{
swap(A, i, largest);
heapify(A, largest, n);
}
}
- 时间复杂度:
按道理来说是时间复杂度是O(lg n)
通过之前讲的主定理解得T(n) = O(logn)
堆的应用
见的多的应该是堆排序,用的多的应该是优先队列。
堆排序
快又快不过快速排序,只是作为知识点了解下就好了
构建堆
很简单,把所有带有子节点的结点都heapify一边就完事了
- 算法导论上的伪代码
- 我自己写的代码
//构建堆
void build_heap(int A[], int n)
{
for(int i = n/2; i>=0; --i)
heapify(A, i, n);
}
- 时间复杂度
树高 lg n, 高度为h的堆最多包括n/2^(k+1)个结点
所有时间复杂度就是O(n)
堆排序
我们还是拿最大堆举例,因为现在已经知道了堆顶的元素是最大元素,那么直接将这个元素提出来,再对剩下的元素继续heapify,然后依次取出堆顶元素就好了。
- 算法导论上的伪代码:
- 我自己写的:
//堆排序
void heapsort(int A[], int n)
{
build_heap(A, n);
for(int i = n-1; i > 0; i--)
{
swap(A, 0, i);
heapify(A, 0, i);
}
}
- 时间复杂度分析:
所以堆排序的时间效率就是O(n lg n)
优先队列
这个部分都挺简单,直接贴个伪代码和C语言版本的就算了。
MAXIMUM
-
伪代码:
-
C语言代码:
//仅返回队列中最大的元素
int max_mum(int A[])
{
return A[0];
}
EXTRACT-MAX
- 伪代码:
- C语言代码:
//返回队列中最大的元素,并且在队列中删除该元素
int extract_max(int A[], int n)
{
if(n < 1)
{
printf("数组空,没最大");
return -1;
}
int max = A[1];
n--;
heapify(A, n, 0);
return max;
}
INCREASE-KEY
-
伪代码:
-
C语言代码:
//增加i的值到key(只能增加,减小的话可以改下条件)
void increase_key(int A[], int i, int key)
{
if(key < A[i])
{
printf("更改的值小于原来的值");
return ;
}
A[i] = key;
while(i > 0 && A[i/2] < A[i])
{
swap(A, i, i/2);
i = i/2;
}
}
INSERT
- 伪代码:
- C语言代码:
因为我的数组长度不是一个全局变量,所以需要返回重置n的值
//插入一个元素
int insert(int A[], int key, int n)
{
n++;
A[n] = -10086;
increase_key(A, n, key);
//返回改变后的队列长度
return n;
}
全部的完整代码
#include<stdio.h>
//自己写的数组交换函数
void swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
//堆的构造,输入构造点和数组长度
void heapify(int A[], int i , int n)
{
//左右子节点
int l = i*2;
int r = i*2+1;
//寻找三个结点中最大的结点
int largest = i;
if(l <= n-1 && A[l] > A[largest])
largest = l;
if(r <= n-1 && A[r] > A[largest])
largest = r;
//将最大的元素置为堆顶,并递归执行
if(i != largest)
{
swap(A, i, largest);
heapify(A, largest, n);
}
}
//构建堆
void build_heap(int A[], int n)
{
for(int i = n/2; i>=0; --i)
heapify(A, i, n);
}
//堆排序
void heapsort(int A[], int n)
{
//先初始化堆
build_heap(A, n);
//通过循环一次将数组第一位排出
for(int i = n-1; i > 0; i--)
{
swap(A, 0, i);
heapify(A, 0, i);
}
}
/*******下面是优先队列部分*******/
/*以最大优先队列为基础*/
//仅返回队列中最大的元素
int max_mum(int A[])
{
return A[0];
}
//返回队列中最大的元素,并且在队列中删除该元素
int extract_max(int A[], int n)
{
if(n < 1)
{
printf("数组空,没最大");
return -1;
}
int max = A[1];
n--;
heapify(A, n, 0);
return max;
}
//增加i的值到key(只能增加,减小的话可以改下条件)
void increase_key(int A[], int i, int key)
{
if(key < A[i])
{
printf("更改的值小于原来的值");
return ;
}
A[i] = key;
while(i > 0 && A[i/2] < A[i])
{
swap(A, i, i/2);
i = i/2;
}
}
//插入一个元素
int insert(int A[], int key, int n)
{
n++;
A[n] = -10086;
increase_key(A, n, key);
//返回改变后的队列长度
return n;
}
/**************over**************/
int main()
{
int tree[] = {2, 5, 3, 1, 10, 4};
int n = 6;
heapsort(tree, n);
for(int i; i<n; i++)
{
printf("%d ",tree[i]);
}
return 0;
}