/**
* 需求:堆排序的实现
* 知识储备:
* 满二叉树:除叶子结点外的所有结点均有两个子结点,所有叶子结点必须在同一层上。
* 完全二叉树:
* 若二叉树的深度为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");
}
}
阿布学排序之堆排序
最新推荐文章于 2014-07-16 21:44:19 发布