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

算法外功修炼之二 堆排序的java实现

2014-12-18 12:01 375 查看
上周抽了点时间写了些快排,这周继续下一个比较有意思的算法:堆排序。依旧是分两部分,第一部分,复制粘贴后可运行的堆排序实现代码。第二部分,这次继续放大招,把排序变简单。

一、代码篇

分两个类,一个是堆排序,实现堆排序算法。一个是堆排序的父类,其中实现了列表的生成,输出等功能。

package com.marsyoung.algorithm;

import java.util.List;

/**
* @author Mars
*         堆的定义:(1)堆是一个完全二叉树(2)根结点的值或者大于左右子树的值或者小于左右子树的值(3)左右子树也是一个堆
*         叶子节点就是树中最底端的节点,叶子节点没有子节点。
*         非叶子结点,就是非  叶子节点。
*/
public class HeapSort extends AlgorithmBase {

/**
* 调整堆,其过程就是 比较节点和他所对应的左孩子和右孩子的大小, 如果满足条件,要交换,交换后要对被交换的节点再进行 调整堆(这是一个递归的过程。)
*
* @param list
* @param i 节点位置
* @param size 结束位置
*/
public void heapAdjust(List<Integer> list, int heapPosition, int endPosition) {
int lchild = 2 * heapPosition + 1;
int rchild = 2 * heapPosition + 2;
int max = heapPosition;
//判断 堆的位置和无序队列结束位置 满足条件
//假设,有3个数字,那么堆的位置为0,结束位置为2 2/2=1 >=0;假设有2个数字,那么堆的位置为0 1/2=0 >=0;
if (heapPosition <= endPosition / 2) {
if (lchild <= endPosition && list.get(lchild) > list.get(max)) {
max = lchild;
}
if (rchild <= endPosition && list.get(rchild) > list.get(max)) {
max = rchild;
}
if (max != heapPosition) {
// 交换
int temp = list.get(max);
list.set(max, list.get(heapPosition));
list.set(heapPosition, temp);
heapAdjust(list, max, endPosition);
}
}
}

/**
* 建堆,其过程就是从最后一个非叶子节点开始,执行调整堆操作。
*
* @param list
* @param size
*/
public void buildHeap(List<Integer> list, int size) {
// size/2-1为最后一个非叶子节点 例如,有3个数字时,那么非叶子节点的位置应该为0,有4个数字时,最后一个非叶子节点的位置为1. 当有5个数字时,最后一个非叶子结点的位置为1.
for (int i = size / 2 - 1; i >= 0; i--) {
heapAdjust(list, i, size - 1);
}
}

/**
* 堆排序的全部过程
* 堆排序包括两个步骤
* (1)初始堆
* (2)调整堆(当初始小顶堆之后,堆顶元素是最小的元素,取出最小的元素与最后一个元素相交换,再把剩下n-1个元素调整成堆,依次调整直到1为止)
*
* @param list
*/
public void heapSort(List<Integer> list) {
int size = list.size();
buildHeap(list, size);
printList(list);
for (int i = size - 1; i >= 0; i--) {
// 交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面,每次交换都有一个有序的值被沉淀到了最后
int temp = list.get(0);
list.set(0, list.get(i));
list.set(i, temp);
// 调整剩余元素组成的堆
heapAdjust(list, 0, i-1);
printList(list);
}
}

public static void main(String[] args) {
HeapSort hs = new HeapSort();
hs.initDisorderList(10, 100);
hs.printList(hs.disorderList);
hs.heapSort(hs.disorderList);
hs.printList(hs.disorderList);
}
}


package com.marsyoung.algorithm;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class AlgorithmBase {

List<Integer> disorderList;

/**
* 用来生成数据
* @param n  生成的list包含元素个数
* @param max  最大值
*/
public void initDisorderList(int n,int max){
disorderList=new ArrayList<Integer>();
for(int i=0;i<n;i++){
disorderList.add(new Random().nextInt(max));
}
}

/**
* 用来输出数据
* @param list
*/
public void printList(List<Integer> list){
for (Integer i : list) {
System.out.print(i + " ");
}
System.out.print("\n");
}

public static void main(String[] args) {
AlgorithmBase ab=new AlgorithmBase();
ab.initDisorderList(100, 100);
for(Integer i:ab.disorderList){
System.out.println(i);
}
}
}


二、心得篇

堆排序,起初看到这个名字,让人有点害怕,因为堆这个概念让人联想到的是java中的堆存储空间,还有新能源中的核反应堆,如果联想到大学学习的数据结构,堆应该是一种数据结构,定义的话,直接上百度百科:

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

看完上面的定义,应该对堆有一个简单认识。

堆排序,就是利用堆的性质,来排序。

假设有一组数字需要排序:

1     9      4     8      6      3     5     2

那么如何利用堆的性质呢?首先应该把这组数字放到一个堆结构里吧,也就是建立一个堆,然后根据堆的性质,那么堆顶,就是最上面的那个数应该是最小的或者最大的。

问题来了,这么一堆无序的数字,怎么能建成一个堆?(好吧,这里先要观察总结一些规律:二叉堆,就是那种只有两个分支的堆,他的父节点所在的位置乘以2加1是它左边孩子的位置,再加一是右边孩子的位置。这个性质是建立堆的一个基础,如下图)



(规律如下:

0 *2 +1=1

0*2 +2=2

1*2 +1 =3

2*2+1 =  4

...

6*2+1=13

6*2+2=14

这个规律如果在写程序的时候搞不清楚,坐标什么的容易位置对错。)

另外一个规律,最后一个非叶子节点(定义在代码中有)的位置为所有节点的个数除以2后减一。如上图中有15个节点,最后一个非叶子结点的标号为6,那么15/2-1=6.

有了这两个规律,我们就好建立堆了,我们先把数组中的数字按照 数组坐标塞进上图中,然后可以得到:



这个过程只是一个假想过程,实际上数字还在数组中,图中红色的数字可以看做圆圈中的数在数组中的位置。

我们想把这个假想的结构变成假想的堆,那么对于每一个非叶子结点,比较和交换一下位置(调整堆),让他们的值都大于他连着的两个儿子节点。如果我们从下往上,每个非叶子节点都比较一次,那么到最后所有节点都满足了堆的性质,这就是建堆。

这个堆是假想的,但是建立堆这个过程却是作用在了数组上,数组的位置会调整。

建立堆之后,最上面的数字一定是最大的,那么我们把这个数字选出来,扔一边,然后对剩余的数字再建立堆,然后再把那个堆顶的数字选出来,扔一边,排在刚才选出来的数字之前。就像冒泡排序一样,我们每次选出一个最大的,当全部选完之后,那么我们就排好序了。

在java中,你可以新建一个ListArray,每次都把选出来的数字放到其中,把原来需要排序的ListArray中的数字移除,最后新生成的listArray就是有序的,而原来的listArray为空。但是啊,犀利的程序猿们,怎么会这么浪费空间呢,他们把每次选出来的堆顶元素和最后一个元素交换位置,然后不管最后一个数字,对剩余的数字执行同样的过程。

这就是堆排序,一种建立在虚拟堆,真实数组上的排序方式

另外,建立堆和调整堆有什么区别呢,或者说为什么要建立堆?

我认为,建立堆的过程其实是对每个非叶子结点进行调整堆。而调整堆,只能保证被调整的节点和他的儿子节点保留堆的性质。建立好的堆,经过调整堆之后也能保证所有节点满足堆的性质。如果一个二叉树直接进过调整堆之后是不能保证所有节点都满足堆的性质的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息