堆-数据结构-Java实现

堆-数据结构-Java实现

目录




内容

1、基础理论

关于堆的概念、性质、结构等相关基本理论知识,可自行百度或者参考如下文章:

  1. 堆与C++实现
  2. 堆-百度百科
  3. 基本数据结构――堆的基本概念及其操作

数据结构与算法动态演示网址:
1.https://visualgo.net/zh/heap
2. https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

等等很多文章关于堆的介绍,这里我主要用Java语言,参考ArrayList源码,实现堆的基本操作:插入、上浮、弹出、下沉、打印等

2、基本操作实现

这里以最小堆为例。

2.1、插入与上浮

  • 上浮:默认新元素插入最后
    • 算法
      1. 以指定元素为启点
      2. 比较当前节点元素与其父元素大小
      3. 如果当前节点元素小于其父节点元素,则交互2个节点元素的值,把其父节点置为当前节点;否则算法结束。
      4. 重复步骤2~3,直到当前节点为根元素。
    • 代码:
/**
 * 上浮
 * @param index 其实元素索引
 */
public void floatUp(int index) {
	int i = index;
	for (int p = parentIndex(index); p >= 0; i = p, p = parentIndex(p)) {
		if (((E) elementData[p]).compareTo((E) elementData[i]) <= 0)
			return;
		Object o = elementData[p];
		elementData[p] = elementData[i];
		elementData[i] = o;

	}
}
  • 插入
    1. 新元素默认插入末尾
    2. 指定最后一个元素为当前元素,执行上浮操作
    3. 堆大小加1

2.2、取最小值、弹出、下沉

  • 下沉
    • 算法
      1. 以指定元素为启点
      2. 比较当前节点元素与其子节点元素大小,取最小值
      3. 如果当前节点元素为最小元素,则结束算法;如果左子节点为最小元素,则交互2个节点元素的值,把其左子节点节点置为当前节点;如果右子节点为最小元素,则交互2个节点元素的值,把其右子节点节点置为当前节点;
      4. 重新获取当前节点的左右子节点
      5. 重复步骤2~4,直到当前节点的左右子节点都为空。.
    • 代码:
 /**
 * 下沉
 * @param index     起始节点索引
 */
public void sink(int index) {
	int p = index;
	int l = leftChildIndex(p);
	int r = rightChildIndex(p);
	// 当当前节点没有子节点的时候,结束循环
	while (l < size || r < size) {
		// 取当前节点、左子树、右子树最小元素
		Object o = elementData[p];
		int f = 0;
		if (l < size) {
			if (((E) elementData[l]).compareTo((E) o) < 0) {
				o = elementData[l];
				f = 1;
			}
		}
		if (r < size) {
			if (((E) elementData[r]).compareTo((E)o) < 0) {
				o = elementData[r];
				f = 2;
			}
		}
		// 如果当前节点元素不大于其左右子树元素,则结束。
		if (f == 0) return;

		if (f == 1) {
			// 如果左子树为最小元素,交换元素,把其左子树节点置为当前节点
			elementData[l] = elementData[p];
			elementData[p] = o;
			p = l;
		}
		else {
			// 如果右子树为最小元素,交换元素,把其右子树节点置为当前节点
			elementData[r] = elementData[p];
			elementData[p] = o;
			p = r;
		}
		l = leftChildIndex(p);
		r = rightChildIndex(p);
	}
  • 弹出、取最小值
    1. 因为是最小堆,取最小值与弹出操作都是取堆顶元素
    2. 取根元素
    3. 把最后一个元素赋值为根节点
    4. 指定根节点为起始节点,执行下沉操作
    5. 堆大小减1

2.3、完整代码

  • 完整代码:
package com.gaogzhen.datastructure;

import java.util.Arrays;

public class Heap<E extends Comparable<E>> {
    // 数组默认容量大小
    private static final int DEFAULT_CAPACITY = 10;
    // 默认数组为空
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //默认数组为空
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 堆默认内置容器数组
    transient Object[] elementData;
    // 堆大小
    private int size;

    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    // 操作计数器-实现迭代器时使用
    protected transient int modCount = 0;

    /**
     * 构造器
     * @param initialCapacity       初始容量
     */
    public Heap(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: " +
                    initialCapacity);
        }
    }

    /**
     * 使用默认容量构造堆
     */
    public Heap() {
        this.elementData = new Object[DEFAULT_CAPACITY];
        ;
    }

    /**
     * 确保内置数组容量
     * @param minCapacity       最小容量
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                // any size if not default element table
                ? 0
                // larger than default for default empty table. It's already
                // supposed to be at default size.
                : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    /**
     * 扩容
     * @param minCapacity   最小容量
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    /**
     * 最大容量
     * @param minCapacity   最小容量
     * @return
     */
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

 /**
     * 批量添加新元素
     * @param c     目标元素集合
     * @return      添加新元素是否成功
     */
    public boolean addAll(E[] c) {
        int l = c.length;
        for (E e : c) add(e);
        return true;
    }

    /**
     * 添加新元素
     * @param e     新元素
     * @return      添加新元素是否成功
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size] = e;
        // 向上调整
        if (size > 0)
            floatUp(size);
        size++;
        return true;
    }

    /**
     * 上浮
     * @param index 其实元素索引
     */
    public void floatUp(int index) {
        int i = index;
        for (int p = parentIndex(index); p >= 0; i = p, p = parentIndex(p)) {
            if (((E) elementData[p]).compareTo((E) elementData[i]) <= 0)
                return;
            Object o = elementData[p];
            elementData[p] = elementData[i];
            elementData[i] = o;

        }
    }

    /**
     * 获取左子树元素索引
     * @param index     目标元素索引
     * @return          左子树元素索引
     */
    private int leftChildIndex(int index) {
        return index * 2 + 1;
    }

    /**
     * 获取右子树元素索引
     * @param index     目标元素索引
     * @return          右子树元素索引
     */
    private int rightChildIndex(int index) {
        return index * 2 + 2;
    }

    /**
     * 获取父节点元素索引
     * @param index     目标元素索引
     * @return          父节点元素索引
     */
    private int parentIndex(int index) {
        // 如果节点为根节点,则返回-1
        return index <= 0 ? -1 : (index - 1) / 2;
    }

    /**
     * 下沉
     * @param index     起始节点索引
     */
    public void sink(int index) {
        int p = index;
        int l = leftChildIndex(p);
        int r = rightChildIndex(p);
        // 当当前节点没有子节点的时候,结束循环
        while (l < size || r < size) {
            // 取当前节点、左子树、右子树最小元素
            Object o = elementData[p];
            int f = 0;
            if (l < size) {
                if (((E) elementData[l]).compareTo((E) o) < 0) {
                    o = elementData[l];
                    f = 1;
                }
            }
            if (r < size) {
                if (((E) elementData[r]).compareTo((E)o) < 0) {
                    o = elementData[r];
                    f = 2;
                }
            }
            // 如果当前节点元素不大于其左右子树元素,则结束。
            if (f == 0) return;

            if (f == 1) {
                // 如果左子树为最小元素,交换元素,把其左子树节点置为当前节点
                elementData[l] = elementData[p];
                elementData[p] = o;
                p = l;
            }
            else {
                // 如果右子树为最小元素,交换元素,把其右子树节点置为当前节点
                elementData[r] = elementData[p];
                elementData[p] = o;
                p = r;
            }
            l = leftChildIndex(p);
            r = rightChildIndex(p);
        }
    }


    /**
     * 弹栈、弹出
     * @return
     */
    public E pop() {
        if (size <= 0) return null;

        Object item = elementData[0];
        elementData[0] = elementData[size -1];
        size--;
        sink(0); // 下沉
        return (E) item;
    }

	/**
     * 获取堆顶元素
     * @return      堆顶元素
     */
    public E peek() {
        return size == 0 ? null: (E) elementData[0];
    }
    
    /**
     * 判断堆是否空
     * @return      空,返回true;不空,返回false
     */
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 获取堆大小
     * @return  堆大小
     */
    public int size() {
        return size;
    }

    /**
     * 打印堆
     * @return  打印堆,例如[1, 2, 3]
     */
    @Override
    public String toString() {
        if (size <= 0) return "[]";
        int iMax = size - 1;
        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(elementData[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }
}

完整代码包括测试、其他数据结构与算法等等可查看下面仓库。

后记

  欢迎交流,本人QQ:806797785

项目源代码地址:https://gitee.com/gaogzhen/algorithm
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值