堆和堆排序
数据结构中的堆是一颗完全二叉树,一般由数组实现(也有优先队列),这篇主要讲一下用数组实现的堆。
1: 对一个普通数组进行堆排序(尤其是原地排序,不能占用额外空间的)其实这样的堆排序我很难说他实现了堆,它只是对普通数组使用了堆的特性 (并没有堆的数据结构的实现)
这种一般要经过两个步骤:
1 建堆 把整个初始数组进行变化成一个符合大顶堆(小顶堆)的数组,一般来说升序用大顶堆降序用小顶堆。
下面是一个简单的实现 直接调用sort就行
public static void sort(int[] arr) {
//建大顶堆 对所有的非叶子节点 对每一个节点进行adjustHeap
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
/**
* 调整堆得过程是这样的 我们建立大顶堆 保证了堆顶是目前最大的元素
* 然后将堆顶和堆最后一个值交换 这样最后一个值已经是最大值,那么我们在调整堆得时候就可以忽略这个值
* 同理数组第二大的值会被放到堆的倒数第二个位置 。。。。
* 每一次调整之后 只有堆顶元素需要调整 所以执行一次 adjustHeap()
* 这样就可以完成排序
*/
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i);
adjustHeap(arr, 0, i);
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 这个函数的目的是将数组节点i进行调整
* 调整过程其实是这样的
* 假设堆顶元素是 1 要调整经过的路径是 1(堆顶元素) 11 8 6 0
* 第一次i = 0比较后变为 11(因为执行了这一句 arr[i] = arr[largeChild]) 11 8 6 0
* 第二次i = 2 比较后变为 11 8 8 6 0
* 。。。
* 最后一次比较 1>0 循环结束 将 11 8 6 6 0 变为 11 8 6 1 0
* 调整结束(temp一直表示的是堆顶元素的值)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int[] arr, int i, int length) {
int largeChild;
int temp = arr[i];
// length/2是第一个叶子节点的下标 所以判断范围是小于这个值
while (i < length / 2) {
//左孩子节点
int left = i * 2 + 1;
//右孩子节点
int right = left + 1;
//因为要构建大顶堆 所以要和左右节点中较大的值(如果有)交换
if (right < length && arr[left] < arr[right]) {
largeChild = right;
} else {
largeChild = left;
}
//如果当前值小于堆顶值 结束循环(找到了要插入的位置)
if (temp >= arr[largeChild]) {
break;
}
arr[i] = arr[largeChild];
i = largeChild;
}
arr[i] = temp;
}
而我们要实现一种堆的数据结构,最基本的这个堆至少有添加数据和删除数据的功能。下面我以n个排序数组合并为例实现一下堆的数据结构
class Node {
private int value;
private int index;
public Node(int value, int index) {
this.value = value;
this.index = index;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
private Node[] nodes;
//堆当前元素数量
private int curSize;
public Heap(int maxSize) {
//初始化堆
this.nodes = new Node[maxSize];
this.curSize = 0;
}
public int getCurSize() {
return curSize;
}
public Node[] getNodes() {
return nodes;
}
public boolean isEmpty() {
return this.curSize == 0;
}
public void insert(Node node) {
nodes[curSize] = node;
up(curSize++);
}
public void remove() {
nodes[0] = nodes[--curSize];
down(0);
}
/**
* 这个函数的功能是将nodes[index]为根的二叉树构建为小顶堆
* 其实有点像插入排序(将一个数插入一个有序数组中)
*
* @param index
*/
public void down(int index) {
int largeChild;
Node top = nodes[index];
//不用判断叶子节点
while (index < (curSize) / 2) {
int left = 2 * index + 1;
int right = 2 * index + 2;
//找出左右孩子节点中较小的那一个 显然如果没有右节点为左节点
if (right < curSize && nodes[left].getValue() >= nodes[right].getValue()) {
largeChild = right;
} else {
largeChild = left;
}
//如果top值小于孩子节点 直接结束循环(可以理解为找到了插入的位置)
if (top.getValue() <= nodes[largeChild].getValue()) {
break;
}
nodes[index] = nodes[largeChild];
index = largeChild;
}
nodes[index] = top;
}
public void up(int index) {
int parent = (index - 1) / 2;
Node temp = nodes[index];
while (index > 0 && nodes[parent].getValue() >= temp.getValue()) {
nodes[index] = nodes[parent];
index = parent;
parent = (parent - 1) / 2;
}
nodes[index] = temp;
}

浙公网安备 33010602011771号