堆排序

来源:互联网 发布:软件评测师考试报名 编辑:程序博客网 时间:2024/06/11 18:41

先说说堆

堆分为大根堆和小根堆,都是完全二叉树。
大根堆:要求每个节点不大于其父节点
小根堆 : 要求每个节点不小于其父节点

复习一下完全二叉树

  • 除了最后一层外,其他各层都达到该层节点的最大数,最后一层如果不满,该层所有节点都全部靠左排
  • 一颗二叉树,深度为k,对这棵树编号,如果所有的编号都和满二叉树的编号对应,则这棵树是完全二叉树

堆排序思想

堆排序的话,最重要还是建立堆,然后调整堆,使得堆满足大根堆(或小根堆)的定义。
我们定义了一个维护堆的方法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的实现。

思想:
我们要维护一个大根堆的性质,
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!    }

递归思想MAX-HEAPIFY

还有一种是递归实现,仍然是比较当前节点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)

0 0
原创粉丝点击