堆分为大根堆和小根堆,都是完全二叉树。 大根堆:要求每个节点不大于其父节点 小根堆 : 要求每个节点不小于其父节点
堆排序的话,最重要还是建立堆,然后调整堆,使得堆满足大根堆(或小根堆)的定义。 我们定义了一个维护堆的方法percDown,这个方法维护最大堆的性质。 建立堆的过程其实是生成了一个数组,这个数组满足最大堆,但是这个数组不满足非降序的顺序。所以我们要把最大的元素一个一个提取,然后提取一个进行一次堆调整 最后生成出来的数组就是排序好的
堆排序先建堆,也就是将输入数组A[1..N]建成最大堆,注意这里下标是1开始,A[0]我们是用来作为一个哨兵使用。 首先其实是一个建堆过程,这个过程我们让i从A.length/2开始, 因为一个堆有一半的节点都是叶节点,而且一般都是存到数组的后半部分。 所以我们的i从一半长度开始即可。
如图:
我们需要从最后一个元素开始排序,因为其实我们是每次都把最大的那个数取出来,然后减小堆的size,然后再执行一个percDown调整堆,然后产生一个新的大顶堆,然后又继续这个操作,不断直到堆已经完全转为一个数组。 所以其实我们只是对0到i-1进行percDown,i到N-1是排好序的,所以是倒着的
public void heapsort(int[] A){ if(A==null) return; if(A.length<1) return; int N=A.length; //Build heap for(int i=A.length/2;i>=0;i--){ percDown(A,i,A.length); } //swap for(int i=N-1;i>0;i--){//A[0]是最大的元素,A[i]是当前堆的最后一个元素 swap(A,0,i); //产生新的大顶堆,结束后堆的规模减1, i means the length of heap percDown(A,0,i);//交换完之后重新调整一下0 和 i之间的节点,把A[0]下移到合适的位置 } }一般我们在堆排序的时候,二叉堆实质我们是用一个数组存储。 利用下标 childNode=parentNode*2 来访问子节点。
思想: 我们要维护一个大根堆的性质, percDown函数传入数组和一个指定的下标i, i表示当前的父节点,然后我们通过Child=2*i访问这个子节 点,我们比较当前这个父节点和子节点, 如果父节点比最大的子节点都大那么不需要交换。 如果子节点比父节点大而且没有超出数组,则把子节点上移到父节点的位置。 然后继续循环,比较下一级,即上一次的Child是当前的父节点,仍然是和它的子节点比较。 仍然小于子节点则继续把子节点上移,直到最后当前节点比子节点都大或者超出数组范围。 最后交换一开始的节点和最后的i的child节点。
public void percDown(int[] A,int i,int N){//i means parent!actually int child; int tmp; for(tmp=A[i];(2*i+1)<N;i=child){//i=child 下沉到child child=2*i+1; if(child!=N-1&&A[child+1]>A[child]) child++; if(tmp>=A[child]) //比最大的儿子还要大,不需要交换 break; else//tmp 是小的,因为是大顶堆所以需要把tmp下移到合适的位置然后把child上移 A[i]=A[child]; //把最大的child上移 } //tmp store the initial value. A[i]=tmp;//actually here i means child,notice i==child! }还有一种是递归实现,仍然是比较当前节点A[i]和他的孩子节点,如果当前节点是最大的,则以该节点为根的子树已经是最大堆,结束。 否则最大元素是它的某个孩子节点,需要交换A[largest]和当前A[i]。但是交换后,以A[i]的孩子节点largest为根的子树可能又会违反最大堆的性质,所以这时候递归调用MAX-HEAPIFY
伪码描述
MAX-HEAPIFY(A,i) l=2*i; r=2*i+1; if(l<=A.heap-szie && A[l]>[i]) largest=l else largest=i if(r<=A.heap-size && A[r]>A[largest] largest=r if(largest!=i) swap(i,largest) MAX-HEAPIFY(A,largest)最坏 最好 平均 都是 O(N*logN)