具体实现:
堆这种数据结构可以理解为二叉堆,也就是二叉树的的结构,要满足以下两个条件:
1)是完全二叉树(最后一行的叶子节点的父结点总是没有右结点)
2)父结点总是大于它的两个子节点
动态数组保存,从下标0开始,这样索引就是:
左孩子: 2*i + 1
右孩子: 2*i + 2
父结点:(i - 1)/2
代码:
1、先是类中的基础变量和一些简单的方法,主要是辅助方法
public class BinaryHeap<E extends Comparable<E>> {
private ArrayList<E> data;
public BinaryHeap(int capacity){
data = new ArrayList<>(capacity);
}
public BinaryHeap(){
data = new ArrayList<>();
}
//返回堆中的元素
public int size(){
return data.size();
}
//表示堆中是否为空
public boolean isEmpty(){
return data.isEmpty();
}
//辅助函数。寻找对应的索引值
private int parent(int index){
if(index == 0)
throw new IllegalArgumentException("it is root");
return (index - 1)/2;
}
private int leftchild(int index){
return 2*index + 1;
}
private int rightchild(int index){
return 2*index + 2;
}
2、接上述代码,这个是向堆中添加结点。由于完全二叉树的性质,添加元素是把要添加的结点先放在最后一层的末尾位置,之后不断跟它的父结点进行比较,选择合适的位置。这个过程称为上移。
//向堆中添加元素;由于完全二叉树的性质,添加元素是把要添加的放在在最后一层的
//最后位置,之后不断跟它的父结点进行比较,选择合适的位置。这个过程称为上移
public void add(E e){
data.add(e);
siftUp(data.size() - 1);
}
//k只是数组下标,并不是值
private void siftUp(int k){
// k和k的父亲元素做比较,如果父亲比k小,则
while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0){
//ArrayList这个类里面并没有swap这个方法,这里只是为了方便表达出交换的意思
data.swap(k, parent(k));
// 这时k已经来到了相对来说新的位置,所以要进行更新
k = parent(k);
}
}
3、接上,这个是从堆中取出最大元素。由于完全二叉树的性质,取出元素是把最后一层的最后一个元素跟要取出元素的位置也就是根结点(因为最大堆的根节点就是最大的,在第0个位置)调换,然后看这个位置的左右子树是否大于这个最后一层的末尾结点,如果大于,则选择最大的结点跟当前这个结点进行调换,然后继续递归比较。这个过程称为下沉。详情见代码注释。
public E extractMax(E e){
E res = lookMax();
data.swap(0,data.size() - 1);
data.remove(data.size() -1);
siftDown(0);
return res;
}
//查看最大元素
public E lookMax(){
return data.get(0);
}
private void siftDown(int k){
//一直下沉,因为完全二叉树的每层结点都完全填满,
//而在最后一层上如果不是满的,就只缺少右边的结点,
//所以就是左孩子:leftchild < data.size(),即下沉的临界值
while(leftchild(k) < data.size()){
int j = leftchild(k);
//j+1就是这个结点的右孩子的结点索引值,如果小于size就说明有右孩子
//那么就可以判断这个右孩子的值跟左孩子的值哪个大,大于0说明右比左大
if(j+1 < data.size() && data.get(j+1).compareTo(data.get(j)) > 0){
//大于则把右孩子的索引赋值给j
//这时候j就是左右孩子中的最大值的索引
j = rightchild(k);
}
if(data.get(k).compareTo(data.get(j)) >= 0){
//如果此时k大于j则什么都不做退出即可
break;
data.swap(k, j); //交换位置
k = j; //把j原先所在位置的索引赋值给k让k下沉,继续去进行比较
}
}
}
小总结:
堆排序:堆排序是一种选择排序。时间复杂度:O(logn)(如果是升序排序则选最大堆,反之最小堆)
1.将无序序列构建成一个堆,也就是add()。
2.将堆顶元素与末尾元素交换,也就是extractMax()。
3.重新调整结构(重建),之后重复进行交换堆顶元素与当前末尾元素,直到整个序列有序。