堆是完全二叉树的分布逻辑,结构可以用数组或树等实现。在完全二叉树的基础上,加上父节点优先级始终大于子节点优先级的规则,是一种弱序结构。
堆一般用于优先级存储结构,插入和删除根节点比较方便,但是查找、顺序遍历和删除其他关键字节点就不爽了。
堆排序是一种与快排复杂度相同O(NlogN)的排序,效率略低于快排,但是它很稳定,不受数据分布影响,而快排最差效率可降低到O(N*N)。
1,基于数组的堆
import java.util.Random;
class Item {
int data;
public Item(int data) {
this.data = data;
}
}
public class Heap {
private Item[] array;
private int maxSize;
private int curSize;
public Heap(int size) {
maxSize = size;
curSize = 0;
array = new Item[size];
}
public boolean insert(int key) {
if (curSize == maxSize)
return false;
array[curSize] = new Item(key);
trickleUp(curSize++);
return true;
}
private void trickleUp(int index) {
Item bottom = array[index];// 把这个要插入的先提出来
int parent = (index - 1) / 2;// 父节点索引
while (index > 0 && array[parent].data < bottom.data) {
array[index] = array[parent];// 父节点下移
index = parent;
parent = (parent - 1) / 2;
}
array[index] = bottom;
}
public Item remove() {
Item root = array[0];
array[0] = array[--curSize];
trickleDown(0);
return root;
}
private void trickleDown(int index) {
Item top = array[index];
int large;
while (index < curSize / 2) {// 此时至少一个子节点
int left = 2 * index + 1;
int right = left + 1;
// 包含了有无右节点2种情况
if (right < curSize && array[right].data > array[left].data)
large = right;
else
large = left;
if (top.data >= array[large].data)
break;
array[index] = array[large];
index = large;
}
array[index] = top;
}
//改变节点优先级
public boolean change(int index, int newValue) {
if (index < 0 || index >= curSize)
return false;
int oldData = array[index].data;
if (newValue > oldData)
trickleUp(index);
else
trickleDown(index);
return true;
}
//逐层显示,格式没调的好看
public void display() {
int n = 1;
while (true) {
for (int i = n - 1; i < 2 * n - 1; i++) {
if (i >= curSize) {
System.out.println("\n---------------------------------");
return;
}
System.out.print(array[i].data + " ");
}
System.out.println();
n *= 2;
}
}
//堆排序
public void sort(Item[] beforeSort){
array=beforeSort;
maxSize=curSize=beforeSort.length;
for(int i=0;i<maxSize;i++)
System.out.print(array[i].data+"/");
System.out.println();
//对含有子节点的节点向下调整,一直调整到根,就将无序数组转换成了堆
for(int j=maxSize/2-1;j>=0;j--)
trickleDown(j);
//每次将最高优先级的数据按从数组末端到数组开头的顺序放进去,这样排序不需要第二个数组,直接将无序数组转变成有序数组
for(int k=maxSize-1;k>0;k--){
array[k]=remove();
}
//排序后输出
for(int i=0;i<maxSize;i++)
System.out.print(array[i].data+"/");
System.out.println();
}
public static void main(String[] args) {
Heap heap = new Heap(31);
Random rand = new Random();
for (int i = 0; i < 20; i++)
heap.insert(rand.nextInt(100));
heap.display();
heap.remove();
heap.display();
}
}
2,使用树构建堆,称为树堆
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
//问题在于找到树的最后一个节点的位置(插入及删除)
class Node {
Node parent;
Node left;
Node right;
int data;
public Node(int data) {
this.data = data;
parent = null;
left = null;
right = null;
}
}
class TreeHeap {
Node root;
int size;
public TreeHeap() {
root = null;
size = 0;
}
public void insert(int key) {
Node current = root;
Node temp = null;
//得到要插入位置的信息(逆序的):如要插入位置为1011,表示从根节点到插入位置是右左右右的方向
List<Integer> list = sizeToCode(++size);
//从list倒数第二个开始遍历到首个,就是位置的信息
for (int i = list.size() - 2; i >= 0; i--) {
temp = current;
if (list.remove(i) == 1)//1就向右,0向左
current = current.right;
else
current = current.left;
}
if (current == root)//第一次插入
root = new Node(key);
else {
current = new Node(key);
current.parent = temp;
if (size % 2 == 0)
temp.left = current;
else
temp.right = current;
thickleUp(current);//跟第一种方法类似的调整
}
}
// 改变节点数据即可
private void thickleUp(Node node) {
Node temp = node;
while (node != root) {
if (temp.data > node.parent.data) {
node.data = node.parent.data;
node = node.parent;
} else
break;
}
node.data = temp.data;
}
public Node remove() {
if (size == 0)
return null;
Node temp = root;
Node current = root;
List<Integer> list = sizeToCode(size);
for (int i = list.size() - 2; i >= 0; i--) {
if (list.remove(i) == 1)
current = current.right;
else
current = current.left;
}
if (current == root) {// size=1
root = null;
size--;
} else {
root.data = current.data;
current = null;
size--;
thickleDown(root);
}
return temp;
}
private void thickleDown(Node node) {
int key = node.data;
Node large;
while (node != null) {
if (node.left != null && node.right != null) {
if (node.left.data > node.right.data)
large = node.left;
else
large = node.right;
if (key >= large.data)
break;
else {
node.data = large.data;
node = large;
}
} else if (node.left != null) {
if (key >= node.left.data)
break;
else {
node.data = node.left.data;
node = node.left;
}
} else if (node.right != null) {
if (key >= node.right.data)
break;
else {
node.data = node.right.data;
node = node.right;
}
} else
break;
}
node.data = key;
}
// 找出最后一个节点所处位置,通过树的大小,将其转换成二进制编码,
// 如第5个节点,5对应二进制101,去掉开头的1,即左右即是它的位置
// 此时返回的是逆序的,遍历的时候倒过来即可
public List<Integer> sizeToCode(int size) {
List<Integer> list = new ArrayList<Integer>();
while (size >= 1) {
list.add(size % 2);
size = size / 2;
}
return list;
}
public static void main(String[] args) {
TreeHeap heap = new TreeHeap();
Random rand = new Random(137);
int[] array = new int[20];
for (int i = 0; i < 20; i++) {
array[i] = rand.nextInt(1000);
}
for (int a : array)
heap.insert(a);
while (heap.size > 0) {
System.out.print(heap.remove().data + "/");
}
}
}