堆排序
堆
定义:堆是一种数据结构,可以按层次从左到右,构造成一颗完全二叉树。除却最后一层,其它层一定是排满的。
性质:
(1)父子结点的位置关系:
设定:根节点的位置为1,heap[1],那么对于完全二叉树中一个拥有父子节点的节点来说,他们的位置必定存在以下关系
parent[i] = ⌊ i/2 ⌋----i/2向下取整。
left[i] = 2i
right[i] = 2i+1;
设定(java):根节点的位置为0,heap[0],那么对于完全二叉树中一个拥有父子节点的节点来说,他们的位置必定存在以下关系parent[i] = ⌊ (i-1) / 2 ⌋----( i -1 ) / 2向下取整。
left[i] = 2i+1
right[i] = 2i+2;
(2)最大堆和最小堆
最大堆:除了根节点以外的节点都满足:parent[i] >= A[i]
最小堆:除了根节点以外的节点都满足:parent[i] <= A[i]
(3)构建最大、小堆的时间复杂度为:O(nlgn)
(4)堆排序的基础:构建最大/最小堆分两步:
第一步:保证以i为子树的根为最大堆,我们先从最简单的树来看:
要想保证堆具有最大堆的性质:仅需要保证任意一点的数值大于左右子节点-------也就是 三个节点中寻找最大值,将最大值所在的数组位置和根节点的位置对调。
原数组: int heap [ ] = {1,2,3}
经过最大堆排序后的数组: heap [] = {3,2,1} 在比较完成后:将heap[0] 和 heap[2]的数组对调顺序。在看一个高度为2的树:==>
==>
==>????
由1到2可以发现根节点已经满足最大堆的性质,从2到3发现根节点的左孩子也满足了最大堆的性质,但是根节点变得不满足了,也就是说我们之前对根节点做的满足堆性质的操作被破坏了(最大堆性质不稳定)。随着树中数据的增加,我们可能需要对之前做过最大堆性质判断的节点重新判断N次之后,才能构造出最大堆。上述操作都是从根节点开始,到叶子节点结束---(自顶向下的遍历判断)--这种判断具有不稳定性,会导致已经判断好的节点的最大堆性质遭到破坏。下面将一种(自底向上的遍历判断):我们从叶子节点向根节点遍历扫描:==>
==>
可以看到:节点4,5,3因为没有左右孩子节点而满足最大堆性质,下面我们来创造节点2的最大堆性质。接着我们创造根结点的最大堆性质:这时候却发现节点2同样不满足最大堆的性质了,也需要重新发现个节点2为根的子树,使其满足最大堆性质,和自顶向下的遍历一样?
仔细分析一下:自顶向下的遍历:每次子节点为根的子树导致其父节点为根节点的子树不符合最大堆时,可能会不断的向上传递不符合最大堆的范围,导致不符合最大堆性质的子树越来 越大,甚至需要重头再来。自底向上的遍历:如果当前节点的改变会使其它节点为根节点的子树不符合最大堆的性质,那么其它节点必然是当前节点的子节点,它的不符合特性是向下传递的,我们只需要在子节点上在遍历一遍就可以解决。
上述两种方式的比较:第一种方式,需要我们一遍一遍的循环遍历所有的节点,直到所有的节点为根节点的子树都满足最大堆性质遍历结束。第二种方式,从叶子节点开始(叶子节点可以不用判断,直接从最后一个中间节点判断即可,后面会给出最后一个中间节点的表达式和证明),在向上遍历的同 时通过对当前节点为根节点及它所有的子树的不断修正,即可达到目的。下面给出第二种方式的伪码(建议大家在写思路的时候,多用伪码,虽然没有流程图那么直观,但是可以更好的找到程序实现的思路--本人大学期间属于会说不会写的菜鸟):MAX-HEAP(heap,i) //堆数组,节点ileft(i) //求出左孩子下标right(i) //求出又孩子的下标int largest = i; //保存i,left(i),right(i)中最大的数的下标if(left(i) < heap.length && heap[largest] < heap[left(i)])then largest = left(i)if(right(i) < heap.length && heap[largest] < heap[right(i)] )then largest = right(i)if(largest != i)then exchange heap[largest]<==>heap[i]MAX-HEAP(heap,largest) //向当前节点为根节点的子树递归上面讲到我们需要自底向上的去遍历:第一:找到当前数组的最后一个中间节点:⌊ heap.length / 2 ⌋ ----->由父子节点的关系可以得到,heap中最后一个节点的父节点就是数组中最后一个中间节点
BUILD-MAXHEAP(heap)for i <--- ⌊ heap.length / 2 ⌋ downto 1
do MAX-HEAP(heap,i)总共两个函数一个负责循环,一个负责构建。堆排序
堆排序过程:BUILD-MAXHEAP(heap); //构建一个最大/小堆for(heap.size > 1:i) //保证堆中至少有两个元素,否则堆排序结束{backupheap[i] = heap[1]; //将构建出来最大/小堆的最大和最小元素保存new heap [] = heap[] - heap[1]; //删除当前堆中的最大/最小元素,构建新的堆序列BUILD-MAXHEAP(new heap[]); //对新的堆序列构建最大堆为达到原地排序的目的,更好的实现方法:
DO Exchange heap[1] <--> heap[size] //将最大值/最小值与heap[size]交换
heap[] <-- heap[1]...heap[size-1] //将最后一个值去掉之后组成新的heap序列
BUILD-MAXHEAP(heap)}