您的位置:首页 > 编程语言 > Java开发

Java 堆排序

2016-03-03 22:37 295 查看
堆排序 是一种比较复杂的排序,看了算法书上的解释,网上又找了解释(网上算法解释一般是来坑人的,都不详细,且个人观点太强)

虽然不能保证本篇解释到位,力求易于理解,代码简洁精确。

堆排序是对简单选择排序的优化,是一种原地排序的排序过程,只需要一个辅助空间进行交换操作 ,所以其空间复杂度是O(1)

时间复杂度无论是最坏还是最好都是 :O(nlgn) ,

但是它也是一种不稳定的算法,不适用于较小的数组 ,对于较大的文件比较有效

算法思想: 先将一个数组看成完全二叉树,

比如待排数组为:55 9 15 36 8 6 11 4 21 47

组成的完全二叉树为:



接着是建初堆过程

就是将此完全二叉树自底向上逐步转化为堆

堆定义:树中任意结点满足 它的值大于等于它的左子树且大于等于右子树

// 普及下知识:若数组第一个元素为data[0], 则任意结点data[i] 的做孩子是 data[2i+1],右孩子是

data[2i+2], 双亲结点是data[(i-1)/2] ((i-1)/2取余运算)

如何自底向上呢,需要把树中最后一个非叶子节点 data[length/2 - 1](length为数组长度)作为根节点,

把这个结点和它的左子树右子树们看成一个完全二叉树进行重建堆操作,

然后把这个小的二叉树转化为堆,一层层向上,最后整体满足堆

建初堆里面有一步是重建堆,so

解释下重建堆:已知一个二叉树

以k为根节点,和它的左右结点较大者data[m]比较,

如果大于,则过程结束;

如果小于,则data[m]覆盖根结点,但是此时根节点还没找到位置,

根结点继续和data[m]的左右结点较大的一个结点作比较,

重复上面的过程

以上面的图的二叉树为例:用画图描述建初堆的过程:

第(1)步:找到最后一个非叶子结点即8,它的左子树是47大于8,把47放到8这,然后找原来47的左子树,没有,就只能把8放到原来47的位置





第(2)步:

根结点转移到了36,它左右子树最大值21小于它,二叉树无需变化结束此次重建堆



第(3)步:

根结点转移到了15,大于它的左右子树最大值,二叉树无需变化,结束重建堆



第(4)步,这次是9为根节点,小于47, 47上移,原来47的左子树为8, 9大于8,所以把9放在原先47的位置



最后到了55,以它为根节点,大于 47,15,二叉树不变,整个建初堆过程结束,最后产生的二叉树是不是满足堆的定义了 ?

答案是肯定的

建初堆之后二叉树仍然不是完全有序,但是最大的数已经出来了,就是根节点,当然我这里给出的数组第一个数就是最大值,这是我的失误,

但是分析一下就知道我们通过自底向上重建堆,最顶上的数一定是最大的

把最大的数和二叉树最后一个数交换,然后把它踢出二叉树,不管它,剩下的仍看成一棵完全二叉树,对这棵二叉树进行重建堆操作,又可以把最大的数筛出来,

重复上述过程,最后就得到一个完全有序的二叉树,当然这里得到的二叉树 根节点向下是从小到大排列的

上代码时刻:

public class Test {
private static int data[];

public static void Rebuild(int k, int m) {
int x = data[k]; // x 表示此时的根节点
int i = k;
int j = 2 * i + 1; // 左子树的位置
boolean finishend = false;
while (j < m && !finishend) { // 每次循环的过程是根节点和它左右节点最大的那个进行比较
if (j < (m - 1) && data[j] < data[j + 1]) { // 左子树小于右子树,就用右子树和x进行比较,总是要把最大的选到上面
j++; // 树向后移动
}
if (x >= data[j]) {          // 堆顶元素大于左子树
finishend = true;    // 本轮执行完跳出while循环
} else {                     // 如果左或右子树大于根节点,那么我们现在还是不知道这个根节点x该放哪才能满足堆的定义
data[i] = data[j];   // 如果x小于 就交换,每次交换的不是根节点 是上一次较大的子树的位置
i = j;               // i表示这次比较的左(右)子树
j = 2 * i + 1;       // j变为 j的左子树
}
}
data[i] = x;  // 循环结束,i的位置即是最适合x 的位置 ,放进去,重建堆OK
}

/**
* 建初堆过程:可以将任意连续的序列看成完全二叉树,所以从最后一个非叶子结点开始进行重建堆操作 ,逐步向根节点靠近,最后得到的树整体满足堆定义
*
* 建初堆的时间复杂度为O(n)
* @param length
*            数组总长度
*/
public static void initHeap(int length) {

for (int i = (length / 2 - 1); i >= 0; i--) { // 最后一个非叶子结点位于 --(length/2
// -1)-- 的位置
Rebuild(i, length);
}
}

/**
* 堆排序
*/
public static void HeapSort() {
initHeap(10);

for (int i = 9; i > 0; i--) {
int b = data[0];   // *------
data[0] = data[i]; // 根结点(总是当前完全二叉树的最大值)与当前待排序的完全二叉树最后一个元素交换,然后把最后一个元素剔除出二叉树,进入重建堆过程
// 建初堆已经把二叉树变成完整的堆了 现在只需要一次重建堆就可以变成完整的堆了
data[i] = b;       // -----*
Rebuild(0, i);
}
}

public static void PrintData() {
for (int i = 0; i < 10; i++) {
System.out.print(" " + data[i]);
}
System.out.println("");
}

public static void main(String[] args) {
data = new int[10];
Scanner scanner = new Scanner(System.in);
System.out.println("输入十个数字进行-堆排序-");
for (int m = 0; m < 10; m++) {
data[m] = scanner.nextInt();
}
HeapSort();
PrintData();
scanner.close();
}

}


代码写的真心累,有不好的地方,多多指教
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: