堆排序算法介绍
堆是一种重要的数据结构,为一棵完全二叉树, 底层如果用数组存储数据的话,假设某个元素为序号为i(Java数组从0开始,i为0到n-1),如果它有左子树,那么左子树的位置是2i+1,如果有右子树,右子树的位置是2i+2,如果有父节点,父节点的位置是(n-1)/2取整。分为最大堆和最小堆,最大堆的任意子树根节点不小于任意子结点,最小堆的根节点不大于任意子结点。所谓堆排序就是利用堆这种数据结构来对数组排序,我们使用的是最大堆。处理的思想和冒泡排序,选择排序非常的类似,一层层封顶,只是最大元素的选取使用了最大堆。最大堆的最大元素一定在第0位置,构建好堆之后,交换0位置元素与顶即可。堆排序为原位排序(空间小), 且最坏运行时间是O(nlgn),是渐进最优的比较排序算法。
实现思路
堆排序简单来说可以概括为三个步骤:
1.根据数据建立无序的堆
2.对堆按大顶堆或小顶堆调整。大顶堆即是满足树中根节点的值始终大于孩子节点的值,局部也满足这个条件,小顶堆相反。
3.每一次调整过后再顶部得到这些数据的最大值或最小值,出堆,并把最后一个节点放到顶部,以此循环,直到堆里的数据全部给出。
具体代码
堆排序类
public class HeapSort<T extends Comparable<T>> {
private List<T> sub;// 排序资源列表
private Integer listLength = 0;// 元素个数
private Integer nodeNum = 0;// 堆中节点个数
private Node top;// 顶节点
private Node end;// 最后一个节点
private Node end2;// 倒数第二个节点
private List<T> result = new ArrayList<T>();//排序结果
public HeapSort(List<T> srcList) {
this.nodeNum = 1;
this.top = new Node((T)srcList.get(0), 1, 1);// 顶节点默认层数为1,编号为1
this.sub = srcList.subList(1, srcList.size());
this.listLength = srcList.size();
this.end = null;
createHeap(this.top);
}
// 创建堆
private void createHeap(Node h) {
if (sub == null || sub.size() <= 0) {
return;
}
// 添加左孩子
createLeftChild(h);
// 添加右孩子
createRightChild(h);
return;
}
// 创建左孩子
private void createLeftChild(Node h) {
if (h.getLeft() != null) {
return;
}
if (listLength >= h.getNum().intValue() * 2) {// 可以有左孩子
nodeNum++;
Node leftChild = new Node((T)sub.get(0), h.getFloor() + 1, h.getNum() * 2);
h.setLeft(leftChild);
leftChild.setParent(h);
if (leftChild.getNum() == listLength) {// 最后一个节点
end = leftChild;
}
sub = sub.subList(1, sub.size());
createHeap(leftChild);
}else{
end = h;
}
return;
}
// 创建右孩子
private void createRightChild(Node h) {
if (h.getRight() != null) {
return;
}
if (listLength >= h.getNum().intValue() * 2 + 1) {// 可以有右孩子
nodeNum++;
Node rightChild = new Node((T)sub.get(0), h.getFloor() + 1, h.getNum() * 2 + 1);
h.setRight(rightChild);
rightChild.setParent(h);
if (rightChild.getNum() == listLength) {// 最后一个节点
end = rightChild;
}
sub = sub.subList(1, sub.size());
createHeap(rightChild);
}
return;
}
/**
* 调整大顶堆,并且返回最顶端的值
* @param h 根节点
* @return
*/
private T adjustMaxHeap(Node h) {
// 通过迭代遍历根节点和孩子节点,调整为根节点最大
if (h == null) {
return null;
}
if(nodeNum==1){
return (T) top.getValue();
}
if (h.getNum().equals(nodeNum - 1)) {// 调整堆的时候添加上倒数第二个节点的引用,以便删除最后一个节点后将end指向此节点
end2 = h;
}
T top = (T) h.getValue();
// 调整左孩子
T maxLeft = adjustMaxHeap(h.getLeft());
// 调整右孩子
T maxRight = adjustMaxHeap(h.getRight());
if (maxLeft == null && maxRight == null) {// 没有左右孩子
return (T) h.getValue();
} else if (maxLeft == null && maxRight != null) {// 没有左孩子
if (maxRight.compareTo(top)>0) {
h.setValue(maxRight);
h.getRight().setValue(top);
}
} else if (maxLeft != null && maxRight == null) {// 没有右孩子
if (maxLeft.compareTo(top)>0) {
h.setValue(maxLeft);
h.getLeft().setValue(top);
}
} else {// 有左右孩子
if (maxLeft.compareTo(top)>0 || maxRight.compareTo(top)>0) {
if (maxLeft.compareTo(maxRight)>=0) {// 左孩子最大
h.setValue(maxLeft);
h.getLeft().setValue(top);
} else {// 右孩子最大
h.setValue(maxRight);
h.getRight().setValue(top);
}
}
}
return (T) h.getValue();
}
/**
* 堆排序方法(大顶堆)
* @param srcList
* @return
*/
public List<T> sort() {
// 循环调整堆
while (floorMoreOne()) {
T max = adjustMaxHeap(top);
//stack.push(max);
result.add((T) max);
top.setValue(end.getValue());
Node parent = end.getParent();
if (end.getParent() == null) {
break;
}
if (parent.getLeft() != null && parent.getLeft().getNum().intValue() == end.getNum().intValue()) {
parent.setLeft(null);
}
if (parent.getRight() != null && parent.getRight().getNum().intValue() == end.getNum().intValue()) {
parent.setRight(null);
}
nodeNum--;
end = end2;
// end指向倒数第二个节点
}
result.add((T) top.getValue());
return result;
}
// 判断堆的层数是否大于一
private boolean floorMoreOne() {
if (top.getLeft() != null || top.getRight() != null) {
return true;
}
return false;
}
}
节点类
public class Node<T> {
private T value;// 节点的值
private Integer floor;// 节点所在层
private Integer num;// 节点编号
private Node parent;// 父节点
private Node left;// 左孩子
private Node right;// 右孩子
public Node() {
}
public Node(T value) {
super();
this.value = value;
}
public Node(T value, Integer floor) {
super();
this.value = value;
this.floor = floor;
}
public Node(T value, Integer floor, Integer num) {
super();
this.value = value;
this.floor = floor;
this.num = num;
}
public Node<T> getLeft() {
return left;
}
public Integer getNum() {
return num;
}
public void setLeft(Node leftChild) {
this.left = leftChild;
}
public void setRight(Node rightChild) {
this.right = rightChild;
}
public void setParent(Node h) {
this.parent = h;
}
public int getFloor() {
return floor;
}
public T getValue() {
return value;
}
public Node getRight() {
return right;
}
public void setValue(T value) {
this.value = value;
}
public Node getParent() {
return parent;
}
}
这个实现中用到了泛型,可以对多种类型排序,只需要实现comparable接口并实现了compareTo方法。
测试代码
public void test01(){
@SuppressWarnings("unchecked")
int j=0;
String[] strs = new String[1000];
while(j<strs.length){
int len = (int) Math.floor(Math.random()*10+1);
byte[] bytes = new byte[len];
for(int k=0;k<len;k++){
bytes[k] = (byte) Math.floor(Math.random()*26+65);
}
strs[j] = new String(bytes);
j++;
}
HeapSort<String> sort = new HeapSort<String>((List<String>)Arrays.asList(strs));
List<String> list1 = sort.sort();
for(String o:list1){
if(list1.indexOf(o)%10==0){
System.out.println(o.toString()+" ");
}else{
System.out.print(o.toString()+" ");
}
}
}
测试结果
这个实例是纯java面向对象的实现,相比较原生面向过程的实现处理速度回慢,占用资源也多,进攻学习用。如果有什么问题,希望道友们慷慨指正。