堆
1. 堆的基本实现
堆的时间复杂度为O(NlogN),利用二叉堆实现
最大堆特性:堆中的某个节点值总是不大于其父节点的值,堆总是一棵完全二叉树
用数组存放二叉堆,元素从1开始存放
1.1 Shift Up操作
上面执行的是先插入操作:先count++,然后把插入的元素放在最后面,然后比较该元素和父节点的大小,如果比父节点大,则此时不满足堆的性质,交换,直到该元素的值比父节点的值小为止。
public void insert(Item item) {
assert count + 1 <= capacity;
data[count + 1] = item;
count++;
shifUp(count);
}
private void shifUp(int k) {
while (k > 1 && data[k / 2].compareTo(data[k]) < 0) {
swap(k, k / 2);
k /= 2;
}
}
1.2 Shift Down操作
取出最大的元素(数组中的第一个),将堆中的最后一个元素和第一个元素交换,然后比较它的左节点和右节点,选择较大的一个元素交换,这样才能保证交换上来的元素,依然可以保证是一个最大堆。重复此步骤,直到他的孩子节点都比它小时停止。
/**
* 取出当前堆中的最大值
* @return
*/
public Item extractMax() {
assert count > 0;
Item ret = data[1];
swap(1, count);
count--;
shiftDown(1);
return ret;
}
public void shiftDown(int k) {
while (2 * k <= count) {
int j = 2 * k;
//选择两个子树中较大的那个
if (j + 1 <= count && data[j + 1].compareTo(data[j]) > 0) {
j++;
}
// 如果当前节点比两个子树节点都打,则退出
if (data[k].compareTo(data[j]) >= 0) {
break;
}
swap(k, j);
k = j;
}
}
1.3 Heapify
Heapify操作:我们可以看到最开始所有的叶子节点都可以看成一个堆,然后从第一个不是叶子节点(count/2)
开始,然后从该节点开始进Shift Down操作,即从最开始的22开始进行Shift Down操作。此时索引5这个位置元素也满足了这个性质。重复此操作,直到所有的都满足。
public Heapify(Item arr[]){
int n=arr.length;
data= (Item[]) new Comparable[n+1];
capacity=n;
// 现将arr中的元素复制到data中
for (int i = 0; i < n; i++) {
data[i+1]=arr[i];
}
count=n;
// 从第一个非叶子节点开始Shift Down操作
for (int i = count/2; i >= 1 ; i--) {
shiftDown(i);
}
}
2. 原地堆排序
将当前最大的元素与最后的元素交换,然后从前开始进行shift down操作,重复此操作,直到最大的元素依次被从前到后放好在数组中了。
package com.liuyao.heap;
import com.liuyao.utils.SortHelper;
/**
* Created By liuyao on 2018/4/27 15:49.
*/
public class HeapSort {
private HeapSort(){}
public void sort(Comparable[] arr){
int n=arr.length;
// 注意,此时我们的堆是从0开始索引的
// 从(最后一个元素的索引-1)/2开始
// 最后一个元素的索引=n-1
for (int i = (n-1-1)/2; i >=0 ; i--) {
shiftDown2(arr,n,i);
}
for (int i = n-1; i >0 ; i--) {
swap(arr,0,i);
shiftDown2(arr,i,0);
}
}
// 交换堆中索引为i和j的两个元素
private static void swap(Object[] arr, int i, int j){
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 优化的shiftdown过程,使用赋值的方式取代不断的swap ,注释的语句
// 该优化思想和我们之前对插入排序进行优化的思路是一致的。
public void shiftDown2(Comparable[] arr,int n,int k){
// Comparable e=arr[k];
while (2*k+1<n){
int j=2*k+1;
if (j+1<n && arr[j+1].compareTo(arr[j])>0){
j+=1;
}
if (arr[k].compareTo(arr[j])>0){
break;
}
swap(arr,k,j);
// arr[k]=arr[j];
k=j;
}
// arr[k]=e;
}
public static void main(String[] args) {
Integer[] a={2,3,6,8,1,4,5,7};
new HeapSort().sort(a);
SortHelper.print(a);
}
}
3. 索引堆
一是:如果我们的堆中的元素是字符串的话,那么构建堆的时候,交换元素的时间花费将是巨大的,二是:如果我们对建成的话,在堆中很难索引到它。
构建索引堆:每个元素对应一个索引值
堆构建完成后,形成的索引堆是:可见数据的位置没有发生改变,而是根据数据改变的是索引值而形成的堆
package com.liuyao.heap;
/**
* Created By liuyao on 2018/4/18 20:38.
*/
public class IndexMaxHeap<Item extends Comparable> {
public Item[] data; //最大索引堆中的数据
public int[] indexes; //最大索引堆中的索引
public int count; //数组中的元素个数
public int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public IndexMaxHeap(int capacity) {
data = (Item[]) new Comparable[capacity + 1]; //索引是从1开始的
indexes = new int[capacity + 1];
count = 0;
this.capacity = capacity;
}
// 返回索引堆中的元素个数
public int size() {
return count;
}
// 返回一个布尔值, 表示索引堆中是否为空
public boolean isEmpty() {
return count == 0;
}
// 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
// 传入的i对用户而言,是从0索引的
public void insert(int i, Item item) {
assert count + 1 <= capacity;
assert i + 1 >= 1 && i + 1 <= capacity;
i+=1;
data[i] = item;
indexes[count+1]=i;
count++;
shifUp(count);
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shifUp(int k) {
while (k > 1 && data[indexes[k / 2]].compareTo(data[indexes[k]]) < 0) {
swapIndexes(k, k / 2);
k /= 2;
}
}
private void swap(int i, int j) {
Item item = data[i];
data[i] = data[j];
data[j] = item;
}
// 交换索引堆中的索引i和j
private void swapIndexes(int i, int j){
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
}
// 获取最大索引堆中的堆顶元素,不删除
public Item getMax() {
assert count > 0;
return data[indexes[1]];
}
// 获取最大索引堆中的堆顶元素的索引,不删除
public int getMaxIndex(){
assert count>0;
return indexes[1]-1;
}
public Item getItem(int i){
assert i+1 >=1 && i+1 <=capacity;
return data[i+1];
}
// 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据,删除元素
public Item extractMax() {
assert count > 0;
Item ret = data[indexes[1]];
swapIndexes(1, count);
count--;
shiftDown(1);
return ret;
}
// 从最大索引堆中取出堆顶元素的索引,删除
public int extractMaxIndex(){
assert count>0;
int ret=indexes[1]-1;
swapIndexes(1,count);
count--;
shiftDown(1);
return ret;
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
public void shiftDown(int k) {
while (2 * k <= count) {
int j = 2 * k;
//选择两个子树中较大的那个
if (j + 1 <= count && data[indexes[j + 1]].compareTo(data[indexes[j]]) > 0) {
j++;
}
// 如果当前节点比两个子树节点都打,则退出
if (data[indexes[k]].compareTo(data[indexes[j]]) >= 0) {
break;
}
swapIndexes(k, j);
k = j;
}
}
public void change(int i,Item newItem){
i+=1;
data[i]=newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 之后shiftUp(j), 再shiftDown(j)
for (int j = 1; j <=count ; j++) {
if (indexes[j]==i){
shifUp(j);
shiftDown(j);
return;
}
}
}
public static void main(String[] args) {
IndexMaxHeap<Integer> maxHeap = new IndexMaxHeap<>(100);
int N = 15;
int M = 100;
for (int i = 0; i < N; i++) {
maxHeap.insert(i,new Integer((int) (Math.random() * M)));
}
Integer[] arr = new Integer[N];
for (int i = 0; i < N; i++) {
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
}
}
使用reverse数组反向查找:
package com.liuyao.heap;
/**
* Created By liuyao on 2018/4/18 20:38.
*/
public class IndexAndReverseMaxHeap<Item extends Comparable> {
public Item[] data; //最大索引堆中的数据
public int[] indexes; //最大索引堆中的索引
public int[] reverse; //最大索引堆中的反向索引
public int count; //数组中的元素个数
public int capacity;
// 构造函数, 构造一个空堆, 可容纳capacity个元素
public IndexAndReverseMaxHeap(int capacity) {
data = (Item[]) new Comparable[capacity + 1]; //索引是从1开始的
indexes = new int[capacity + 1];
reverse=new int[capacity+1];
for (int i = 0; i <=capacity; i++) {
reverse[i]=0;
}
count = 0;
this.capacity = capacity;
}
// 返回索引堆中的元素个数
public int size() {
return count;
}
// 返回一个布尔值, 表示索引堆中是否为空
public boolean isEmpty() {
return count == 0;
}
// 向最大索引堆中插入一个新的元素, 新元素的索引为i, 元素为item
// 传入的i对用户而言,是从0索引的
public void insert(int i, Item item) {
assert count + 1 <= capacity;
assert i + 1 >= 1 && i + 1 <= capacity;
i+=1;
data[i] = item;
indexes[count+1]=i;
reverse[i]=count+1;
count++;
shifUp(count);
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
private void shifUp(int k) {
while (k > 1 && data[indexes[k / 2]].compareTo(data[indexes[k]]) < 0) {
swapIndexes(k, k / 2);
k /= 2;
}
}
private void swap(int i, int j) {
Item item = data[i];
data[i] = data[j];
data[j] = item;
}
// 交换索引堆中的索引i和j
private void swapIndexes(int i, int j){
int t = indexes[i];
indexes[i] = indexes[j];
indexes[j] = t;
reverse[indexes[i]]=i;
reverse[indexes[j]]=j;
}
// 获取最大索引堆中的堆顶元素,不删除
public Item getMax() {
assert count > 0;
return data[indexes[1]];
}
// 获取最大索引堆中的堆顶元素的索引,不删除
public int getMaxIndex(){
assert count>0;
return indexes[1]-1;
}
public Item getItem(int i){
assert (contain(i));
assert i+1 >=1 && i+1 <=capacity;
return data[i+1];
}
// 从最大索引堆中取出堆顶元素, 即索引堆中所存储的最大数据,删除元素
public Item extractMax() {
assert count > 0;
Item ret = data[indexes[1]];
swapIndexes(1, count);
reverse[indexes[count]]=0;
count--;
shiftDown(1);
return ret;
}
// 从最大索引堆中取出堆顶元素的索引,删除
public int extractMaxIndex(){
assert count>0;
int ret=indexes[1]-1;
swapIndexes(1,count);
reverse[indexes[count]] = 0;
count--;
shiftDown(1);
return ret;
}
// 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
public void shiftDown(int k) {
while (2 * k <= count) {
int j = 2 * k;
//选择两个子树中较大的那个
if (j + 1 <= count && data[indexes[j + 1]].compareTo(data[indexes[j]]) > 0) {
j++;
}
// 如果当前节点比两个子树节点都打,则退出
if (data[indexes[k]].compareTo(data[indexes[j]]) >= 0) {
break;
}
swapIndexes(k, j);
k = j;
}
}
public boolean contain(int i){
return reverse[i+1]!=0;
}
public void change(int i,Item newItem){
assert (contain(i));
i+=1;
data[i]=newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 之后shiftUp(j), 再shiftDown(j)
// for (int j = 1; j <=count ; j++) {
// if (indexes[j]==i){
// shifUp(j);
// shiftDown(j);
// return;
// }
// }
shifUp(reverse[i]);
shiftDown(reverse[i]);
}
public static void main(String[] args) {
IndexAndReverseMaxHeap<Integer> maxHeap = new IndexAndReverseMaxHeap<>(100);
int N = 15;
int M = 100;
for (int i = 0; i < N; i++) {
maxHeap.insert(i,new Integer((int) (Math.random() * M)));
}
Integer[] arr = new Integer[N];
for (int i = 0; i < N; i++) {
arr[i] = maxHeap.extractMax();
System.out.print(arr[i] + " ");
}
}
}