/** * 需求:堆排序的实现 * 知识储备: * 满二叉树:除叶子结点外的所有结点均有两个子结点,所有叶子结点必须在同一层上。 * 完全二叉树: * 若二叉树的深度为h,除第h层外,其它各层(1~h-1)的节点数都达到最大个数,第h层所有结点都连续集中在最左边。 * 完全二叉树是有满二叉树而引出来的,对于深度为K的,有N个结点的二叉树,当且仅当每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称它为完全二叉树。 * * 二叉堆是完全二叉树或者是近似完全二叉树。 * 二叉堆特性: * 1、父结点的键值总是大于或等于(小于或等于)任何一个子结点的键值 * 2、每个结点的左子树和右子树都是一个二叉堆(都是最大堆或者最小堆) * 最大堆:父结点的键值总是大于或等于任何一个子结点的键值。 * 最小值:父结点的键值总是小雨或等于任何一个子结点的键值。 * * 堆的存储特性: * 一般用数组存储,i结点的父结点的下标是(i- 1)/ 2,它的左右结点下标为2 * i + 1, 2 * i + 2。如第0个结点左右结点下标为1和2。 * (a)逻辑结构:(按层编号) * 10 * / * 15 56 * / \ / * 25 30 70 * (b)存储结构 * 10 15 56 25 30 70 * * 常见的堆操作: * 1、建立堆: * 数组具有对应的树表示形式,一般情况下,树并不满足堆的条件,通过重新排列元素,可以一棵“堆化树”,如下图建立一个最小堆 * 初始表:40 10 30 堆化树:10 40 30 * A[0] A[0] * 40 10 * / \ / * 10 30 -----> 40 30 * A[1] A[2] A[1] A[2] * 2、插入一个元素: * 新元素加入到表中,随后树被更新为最小堆次序,例如下图将15加入到表中 * 插入后的表:10 40 30 15 * 15加入到A[3] 重新树的排序 * 10 10 * / \ / * 40 30 -----> 15 30 * / / * 15 40 * 3、删除一个元素: * 删除总是发生在跟A[0]处,表中最后一个元素被用来填补空缺位置,结构树被更新以恢复堆的形式 * 删除位于A[0]的元素10 将40移到A[0] 重新恢复堆 * 空 40 15 * / \ / \ / * 15 30 -----> 15 30 40 30 * / / * 40 空 * 思路:时间复杂度为O(N * logN) * 对于插入操作: * 每次插入都是将新数据放在数组最后,可以发现从这个新数据的父结点到根节点必然为一个有序的数列。 * 1、对于插入的下标为i的结点,其父结点下标为(i - 1)/2 * 2、进行比较,交换,最后恢复成最小堆 * 对于删除操作: * 堆中每次删除数据都只能删除第0个数据,为了便于重建堆,实际的操作是将最后一个数据的值赋给跟结点,然后再从根节点开始进行一次从上而下的调整。 * 调整时,先在左右孩子节点中找最小的,如果父结点比这个最小的子结点还小则不需要调整了,反之将父结点和它交换后再考虑结点,想当于从根节点 * 将一个数据的下沉过程。 * 1、从i结点开始调整,n为结点总数,从0开始计算,第i结点的子结点为2 * i + 1,2 * i + 2 * @author AbuGe * */ public class HeapSort { //在最小堆中的数据上升函数函数 public void minHeapFixup(int[] a, int i) { int j; j = (i - 1) / 2;//父结点下标 int tmp = a[i]; while(j >= 0 && i != 0) { if(a[i] >= a[j]) break; //a[i] = a[j];//把较大的结点往下移动,替换它的子结点 //a[j] = tmp; swap(a, i, j); i = j; j = (i - 1) / 2; } } //在最小堆中的数据下降函数 public void minHeapFixdown(int[] a, int i, int n) { int j; //这一句很重要 int tmp = a[i]; j = 2 * i + 1;//左结点坐标 while(j < n) { //在左右孩子中找最小的 if(j + 1 < n && a[j + 1] < a[j]) j++; if(tmp <= a[j]) break; swap(a, i, j); //a[i] = a[j]; //a[j] = tmp; i = j; j = 2 * i + 1; } } //建立最小堆 public void makeMinHeap(int[] a, int n) { for(int i = n / 2 - 1; i >= 0; i--) { minHeapFixdown(a, i, n); } } //最小堆中添加元素 public void minHeapAdd(int[] a, int n, int nNum) { a[n] = nNum; minHeapFixup(a, n); } //最小堆中删除元素 public void minHeapDelete(int a[], int n) { swap(a, 0, n - 1); minHeapFixdown(a, 0, n - 1); } //交换数据 public void swap(int[] a, int x, int y) { int tmp = a[x]; a[x] = a[y]; a[y] = tmp; } /** * 注意使用最小堆排序后是递减数组,要得到递增数组,用最大堆,由于每次重新恢复堆的时间复杂度为O(logN),共n-1次重新恢复操作, * 再加上前面建立堆时N/2次向下调整,,每次调整的时间复杂度为O(logN),两次操作的时间复杂度相加还是O(N*logN),故堆排序的时间复杂 * 度为O(NlogN) */ //最小堆-->降序排序 /** * 首先堆建立好之后第0个数据是堆中最小的数据,取出这个数据再执行minHeapFixdown的删除操作。这样执行后第0个数据又是堆中最小的 * 数据,重复操作直至堆中只剩一个数据时就得到一个降序的数组。 * @param a * @param n */ public void minHeapToDescendSort(int a[], int n) { for(int i = n - 1; i >= 1; i--) { swap(a, i, 0); minHeapFixdown(a, 0, i); } } public static void main(String[] args) { int[] a = {9, 12, 17, 30, 50, 20, 60, 65, 4, 19}; int len = a.length; HeapSort hs = new HeapSort(); hs.makeMinHeap(a, len); for(int sort : a) System.out.print(sort + "\t"); System.out.println(); hs.minHeapToDescendSort(a, len); for(int sort : a) System.out.print(sort + "\t"); } }
原文地址:http://blog.csdn.net/hxysea/article/details/25901743