数据结构之堆
共 1798字,需浏览 4分钟
·
2022-05-04 16:32
堆
堆是一种特殊的数据结构,通常可以看作是一颗完全二叉树的数组对象。
堆的特性
它是完全二叉树,除树的最后一层节点不需要是满的,其他的每一层从左到右都是满的,如果最后一层不是满的,则要求左满右不满。
它通常使用数组来实现
具体方法是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子节点的子结点分别在位置4、5、6和7,以此类推。
如果一个结点的位置是k,则它父节点的位置为[k/2],而它两个子结点的位置分别为2k和2k+1。
每个结点都大于它的两个子结点
堆的API设计
堆的实现
insert插入方法的实现
堆插入元素图解(上浮算法):
所以,如果往堆中新插入元素,我们只需要不断的比较新结点a[k]和它父节点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。
删除方法的实现
删除堆元素图解(下沉算法):
所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断拿着当前节点a[k]和它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。
堆的代码实现
public class Heap<T extends Comparable<T>> {
private T[] items;
/**
* 堆大小,索引0处不存元素,从索引1开始
*/
private int size;
/**
* 构造函数
*
* @param capacity
*/
public Heap(int capacity) {
items = (T[]) new Comparable[capacity + 1];
size = 0;
}
/**
* 向堆中插入一个元素
*
* @param item
*/
public void insert(T item) {
//向数组添加一个元素
items[++size] = item;
//使用上浮算法,使得元素处于一个正确位置
swim(size);
}
/**
* 删除堆中最大元素
*
* @return
*/
public T delMax() {
//根据堆的特性,堆中最大的值是根结点的值
T max = items[1];
//先将堆中最后一个结点和根结点交换位置
exch(1,size);
//将最后一个结点置为null,元素个数-1
items[size--] = null;
//使用下沉算法使得更新后的根结点处在正确的位置
sink(1);
return max;
}
/**
* 使用上浮算法,使得索引k处的值处于一个正确的位置
* @param k
*/
private void swim(int k){
while (k > 1){
if(less(k/2,k)){
exch(k/2,k);
k = k/2;
}else {
break;
}
}
}
/**
* 使用下沉算法,使得索引k处的值处于一个正确的位置
* @param k
*/
private void sink(int k){
while (2*k <= size){
//找到两个子结点较大的那个的索引
int max = 2*k;
if(2*k+1 < size){
if(less(2*k,2*k+1)){
max = 2*k + 1;
}
}
//比较当前索引处的值和较大结点处的值,如果当前索引处的值小于较大结点处的值,则交换两处的值
if(!less(k,max)){
break;
}
exch(k,max);
k = max;
}
}
/**
* 比较索引i处和索引j处的值大小,i处小于j处则返回true
*
* @param i
* @param j
* @return
*/
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
public String toString() {
return "Heap{" +
"items=" + Arrays.toString(items) +
", size=" + size +
'}';
}
}
//测试代码
public class HeapTest {
public static void main(String[] args) {
Heap
heap = new Heap<>(6); heap.insert("C");
heap.insert("B");
heap.insert("A");
heap.insert("D");
heap.insert("F");
heap.insert("E");
System.out.println(heap);
String result;
while ((result=heap.delMax()) != null){
System.out.println(result);
}
}
}
堆排序
给定一个数组:
String[] arr = {"S","O","R","T","E","X","A","M","P","L","E"};
请对数组中的字符从小到大排序。
API设计:
类名
HeapSort
成员方法
//对source数组进行从小到大排序
public static void sort(Comparable[] source);
//根据原数组创建heap堆
private static void createHeap(Comparable[] source,Comparable[] heap);
//比较堆中索引i处和索引j处的值,小于返回true
private static boolean less(Comparable[] heap,int i,int j);
//交换heap堆中i索引和j索引处的值
private static void exchange(Comparable[] heap,int i,int j);
//在堆中,对target处的元素进行下沉,范围0-range
private static void sink(Comparable[] heap,int target,int range);
实现步骤
构造堆heap
将原数组复制给一个新数组
对数组中元素进行下沉,让它有序,符合堆的定义
根据堆的特性,根结点的值最大,交换根结点和最大索引处的值,将堆分为两部分,未排序的数组(0-length-2),和已排序的数组(length-1)然后对根节点在未排序数组中进行下沉
继续执行步骤2,直到最后一个元素
将堆heap的元素复制给原数组
代码实现
public class HeapSort<T extends Comparable<T>> {
//对source数组进行从小到大排序
public static void sort(Comparable[] source) {
Comparable[] heap = new Comparable[source.length+1];
createHeap(source,heap);
//记录未排序最大索引
int N = heap.length -1;
while (N!=1){
//交换根结点和最后一个未排序索引处的值
exchange(heap,1,N);
N--;
//对交换过的根结点进行下沉,范围除去已排序的索引
sink(heap,1,N);
}
//把heap中的数据复制回source数组
System.arraycopy(heap,1,source,0,source.length);
}
//根据原数组创建heap堆
private static void createHeap(Comparable[] source, Comparable[] heap) {
//复制原数组到heap
System.arraycopy(source,0,heap,1,source.length);
//对堆中的元素进行下沉,(从长度一半开始,往1处扫描)
for (int i = heap.length/2; i > 0; i--) {
sink(heap,i, heap.length-1);
}
}
//比较堆中索引i处和索引j处的值,小于返回true
private static boolean less(Comparable[] heap, int i, int j) {
return heap[i].compareTo(heap[j]) < 0;
}
//交换heap堆中i索引和j索引处的值
private static void exchange(Comparable[] heap, int i, int j) {
Comparable temp = heap[i];
heap[i] = heap[j];
heap[j] = temp;
}
//在堆中,对target处的元素进行下沉,范围0-range
private static void sink(Comparable[] heap, int target, int range) {
while (2*target <= range){
//找出当前结点较大子结点
int max = 2*target;
if(2*target+1 <= range){
if (less(heap,2*target,2*target+1)){
max = 2*target+1;
}
}
//比较当前结点和较大子结点的值
if (!less(heap, target, max)){
break;
}
exchange(heap,target,max);
target = max;
}
}
}
思考:
上面实现的也叫最大堆,堆中第一个元素总是最大的,还有一种堆叫最小堆,即堆中第一个元素总是最小的,又应该如何实现?
上面的实现都是固定容量大小的,如果需要实现可变容量又应该如何实现?
你知道的越多,不知道的就越多,关注我,我们一起进步。如果觉得文章还可以,请给帮我点个赞,谢谢~我们下期见。