二叉树的一个实际应用-堆(Java)

认识了二叉树,也学会了一些遍历算法(DFS、BFS),可是二叉树可以干什么呢,那么其中一个实际应用他来了-堆(Heap).

堆的特点

  1. 堆是完全二叉树(逻辑上)。
  2. 堆的底层实现,其实是顺序表(元素顺序为二叉树的层序遍历,可以抽象看成一颗树)。
  3. 分为大顶堆/小顶堆两种。
  • 大顶堆:树中的每一个元素都是≥他的孩子们的。
  • 小顶堆:树中的每一个元素都是≤他的孩子们的。

大顶堆和小顶堆

应用: 由于在大/小顶堆的每一个节点都是≥/≤他们的孩子们的。因此,堆可以在一组元素中,快速的定位最值(堆顶元素),大顶堆定位最大值,小顶堆定位最小值。

构建一个堆

小顶堆-向下调整:

private static void shiftDownForSmall(long[] array, int size, int index) {
	while (true) {
		int leftIndex = 2 * index + 1;
		if (leftIndex >= size) {
			return;//要调整的位置已经是叶子了,直接返回
		}

		int rightIndex = 2 * index + 2;//int rightIndex = leftIndex + 1
		int minIndex = leftIndex;
		if (rightIndex < size && array[rightIndex] < array[leftIndex]) {
			minIndex = rightIndex;
		}

		if (array[index] <= array[minIndex]) {
			return;//要调整的位置,已经符合小顶堆的特点,不需要调整了
		}

		long temp = array[index];
		array[index] = array[minIndex];
		array[minIndex] = temp;
		index = minIndex;
	}
}
  • 完全二叉树的性质:按照层序遍历的顺序,依次给节点标号为0,1,2……,如果双亲节点序号为i,那么左孩子序号为2*i+1,右孩子序号为2*i+2;如果孩子序号为i(不区分左右孩子),双亲的序号就为(i-1)/2(其实是进行了向下取整)。
  • 思路
  • 首先是,按照要调整的位置的下标,计算出该节点的左孩子所在的下标,然后判断是否合法(越界),如果不合法,说明没有左孩子(左孩子都没有,右孩子一定也是没有的),那么,就说明要调整的位置为叶子节点,不需要调整,直接return就好。
  • 如果左孩子存在,那么就在左孩子和右孩子中,挑选二者中最小的那个。①如果要调整的节点比两个孩子中最小的还小(或者等于),那么这个节点已经满足小顶堆的情况了,也是不需要调整的。②如果两个孩子中最小的那个比要调整的节点还要小,那么交换他俩,把要调整的位置,更改为刚刚最小的下标(因为,这个位置的节点更改过了,所以也要进行向下调整的判断,继续while循环),直到全部调整结束。

大顶堆-向下调整:

private static void shiftDownForBig(long[] array, int size, int index) {
    while (true) {
        int leftIndex = 2 * index + 1;
        if (leftIndex >= size) {
            return;
        }

        int rightIndex = leftIndex + 1;
        int maxIndex = leftIndex;
        if (rightIndex < size && array[rightIndex] > array[leftIndex]) {
            maxIndex = rightIndex;
        }

        if (array[index] >= array[maxIndex]) {
            return;
        }

        long temp = array[index];
        array[index] = array[maxIndex];
        array[maxIndex] = temp;
        index = maxIndex;
    }
}
  • 思路和小顶堆的完全一致,只是在比较的时候是挑出一个最大的值,然后进行比较。

现在我们可以调整一个节点了,那么我们如果从完全二叉树的底部开始,一路往上,不断进行向下调整,最终,就会构建出一个堆了。

public static void creatHeap(long[] array, int size) {
	for (int i = (size - 2) / 2; i >= 0; i--) {//从底向上不断调整
		//shiftDownForSmall(array, size, i);
        shiftDownForBig(array,size,i);
    }
}
  • 我们其实只需要找到,整棵树的最后一个叶子的双亲节点就好了,从这个双亲节点开始,一路向上,不断向下调整即可。
  • 因为,我们所使用的是层序遍历,所以,恰好是调整的范围是[最后一个有孩子的双亲节点,0]
  • 最后一个有孩子的双亲节点的下标就是((size-1)-1)/2(size-1)是最后一个节点的下标。

PriorityQueue

PriorityQueue是Java中的一个使用到堆(小顶堆)的一个类,名为优先级队列。

  • 接下来简单实现一个MyPriorityQueue
  • 自己实现的这个简单的优先级队列,没有使用泛型,而是仅仅使用了String,也没有使用扩容函数,采用了默认20个元素,传入优先级队列的元素必须具备比较能力,由于String已经具备比较能了,如果本身的比较不适用于你,那么你可在new对象的时候传入构造方法一个Comparator
  • Java官方文档

在这里插入图片描述

import java.util.*;

/**
 * 优先级队列为小顶堆
 */
public class MyPriorityQueue implements Iterable<String> {
    private String[] heap = new String[20];
    private int size;
    private Comparator<String> comparator;

    public MyPriorityQueue() {
        //无参构造
    }

    public MyPriorityQueue(Comparator<String> comparator) {
        this.comparator = comparator;//传入comparator比较器
    }

    /**
     * 插入函数
     *
     * @param e 要插入的元素
     * @return 如果插入,则返回true,否则返回false
     */
    public boolean add(String e) {
        heap[size++] = e;
        shiftUp(size - 1);//调整刚刚插入的那个元素
        return true;
    }

    /**
     * 删除队列的头,即堆顶元素
     *
     * @return 如果存在则返回删除的元素,否则抛出异常
     */
    public String remove() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        return poll();
    }

    /**
     * 删除队列的头,即堆顶元素
     *
     * @return 如果存在则返回删除的元素,否则返回null
     */
    public String poll() {
        if (isEmpty()) {
            return null;
        }
        String remove = heap[0];
        heap[0] = heap[size - 1];
        heap[size - 1] = null;
        size--;
        shiftDown(0);
        return remove;
    }

    /**
     * 返回堆顶元素,但是不删除
     * @return 如果存在,则返回堆顶元素,否则返回null
     */
    public String peek() {
        if (isEmpty()) {
            return null;
        }
        return heap[0];
    }

    /**
     * 返回堆顶元素,但是不删除
     * @return 如果存在,则返回堆顶元素,否则抛出异常
     */
    public String element() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        return heap[0];
    }

    /**
     * 最简单的小顶堆向下调整
     *
     * @param index 要调整的下标
     */
    private void shiftDown(int index) {
        while (true) {
            int leftIndex = 2 * index + 1;
            if (leftIndex >= size) {
                return;
            }

            int rightIndex = leftIndex + 1;
            int minIndex = leftIndex;
            if (rightIndex < size && compare(heap[rightIndex], heap[leftIndex]) < 0) {
                minIndex = rightIndex;
            }

            if (compare(heap[index], heap[minIndex]) <= 0) {
                return;
            }

            String temp = heap[index];
            heap[index] = heap[minIndex];
            heap[minIndex] = temp;
            index = minIndex;
        }
    }

    /**
     * 向上调整函数
     *
     * @param index 从这个下标开始向上调整
     */
    private void shiftUp(int index) {
        while (index > 0) {
            int parentIndex = (index - 1) / 2;//向下取整
            int compareResult = compare(heap[index], heap[parentIndex]);
            if (compareResult >= 0) {
                return;
            }
            String temp = heap[index];
            heap[index] = heap[parentIndex];
            heap[parentIndex] = temp;
            index = parentIndex;
        }
    }

    /**
     * 为了方便比较,单独拉出一个函数
     *
     * @param insert 刚刚插入的节点的值
     * @param parent 该节点的双亲节点的值
     * @return 比较结果,如果前者小,返回负数,如果两者相等,返回0,如果前者大,返回正数
     */
    private int compare(String insert, String parent) {
        if (comparator == null) {
            return insert.compareTo(parent);
        } else {
            return comparator.compare(insert, parent);
        }
    }

    /**
     * 返回优先级队列的元素个数
     *
     * @return int元素个数
     */
    public int size() {
        return size;
    }

    /**
     * 优先级队列是否为空
     *
     * @return 如果为空,返回true,否则返回false
     */
    public boolean isEmpty() {
        return size == 0;
    }


    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            private int nowIndex = 0;

            @Override
            public boolean hasNext() {
                return nowIndex < size;
            }

            @Override
            public String next() {
                return heap[nowIndex++];
            }
        };
    }

    @Override
    public String toString() {
        return "MyPriorityQueue{" +
                "heap=" + Arrays.toString(heap) + '}';
    }
}

得益于堆优秀的插入性能(O(log(n))),堆的使用很是广泛,优先级队列的只是其中的一个。还有堆排序等等……

文章中如有任何错误,请提出来,谢谢了😜。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值