堆
1.堆
堆的逻辑结构是一颗完全二叉树
堆的物理结构是一个顺序表
通过下标去实现父子节点关系
leftchild=parent*2+1 rightchild=parent*2+2 parent=(child-1)/2;
就类似于下图
2.堆的两个特性
大堆:所有父亲大于等于孩子
小堆:所有父亲小于等于孩子
结构性:用数组表示的完全二叉树
有序性:任意结点的关键字是其子树所有结点的最大值(或最小值)
“最大堆”也称“大堆顶”:最大值
“最小堆”也称“小堆顶”:最小值
3 .向下调整算法(以大堆为例)
//向下调整算法(左右子树必须是大堆) void AdjustDown(int*a,int n,int root) { int parent=root; int child=parent*2+1; while(child<n) { if( child+1<n && a[child+1]>a[child]) { child+=1; } if(a[child]>a[parent]) { Swap(&a[child],&a[parent]); parent=child; child=parent*2+1; } else { break; } } }
那么左右子树不是小堆,就不能直接使用向下调整算法了,怎么办 可以倒着从最后一颗子树开始调 从最后一个非叶子结点开始调,最后一个非叶子节点的下标是(n-1-1)/2
for(i=(n-2)/2;i>=0;i--) { AdjustDown(arr,n,i); }
2.排序
如果我们要给数组排升续,那么我们需要建大堆
每一次向下调整都可以找出一个最大的数放在第一个位置,
我们可以把第一个和最后一个交换,然后把最后一个数(也就是找出的那个最大的数)不看作
前n-1个数,继续向下调整,选出次大的数,再跟倒数第二个位置交换,后面过程以此类推。
到最后就可以完成排序。
//建堆O(n) for(i=(n-2)/2;i>=0;i--) { AdjustDown(arr,n,i); } //排升序,建大堆 int end=n-1; while(end>0) { Swap(&arr[0],&arr[end]); AdjustDown(arr,end,0); end--; }
4.完整代码
//交换两个数函数的分装 void Swap(int*x,int*y) { int temp=*x; *x=*y; *y=temp; } //向下调整算法(左右子树必须是大堆) void AdjustDown(int*a,int n,int root) { int parent=root; int child=parent*2+1; while(child<n) { if( child+1<n && a[child+1]>a[child]) { child+=1; } if(a[child]>a[parent]) { Swap(&a[child],&a[parent]); parent=child; child=parent*2+1; } else { break; } } } #include<stdio.h> int main() { int i=0; int arr[]={10,9,8,7,6,5,4,3,2,1}; int n=sizeof(arr)/sizeof(arr[0]); //建堆O(n) for(i=(n-2)/2;i>=0;i--) { AdjustDown(arr,n,i); } //排升序,建大堆 int end=n-1; while(end>0) { Swap(&arr[0],&arr[end]); end--; AdjustDown(arr,end,0); } for(i=0;i<n;i++) { printf("%d ",arr[i]); } return 0; }
5.时间复杂度的计算
用n表示元素个数
向下调整算法 ,最多向下调整高度(二叉树高度)次。
h=log(n);
所以一次向下调整的时间复杂度是log(n)
建堆的时间复杂度
T(n)=1*(h-1)+ 2* (h-2)+ 4* (h-3)+…2^(h-2)
T(n)= -h+2^h-1
建堆的时间复杂度就约等于O(N)
整体的时间复杂度O(N*logN)