【數據結構】初入數據結構的堆(Heap)以及Java實現

初入數據結構的堆(Heap)以及Java實現


如果覺得對你有幫助,能否點個贊或關個注,以示鼓勵筆者呢?!博客目錄 | 先點這裏

  • 堆的基本概念

    • 什麼是二叉堆
    • 最大堆和最小堆
    • 注意點
  • 二叉堆

    • 實現基礎
    • 動態數組
    • 上浮
    • 下沉
    • 添加元素
    • 取最大值
    • 取最大值,同時插入新元素
    • 將任意數組堆化
  • 二叉堆Java代碼實現

    • 描述
    • 實現功能
    • 完整代碼
  • 堆排序(補)

    • 描述
    • 代碼實現

堆的基本概念


二叉堆

什麼是堆?

通常我們所說的堆,就是二叉樹的一種變形,所以它本質也是一棵二叉樹,只不過有一些自己的特點,所以我們有叫堆爲二叉堆

  • 堆,也叫二叉堆,它是一棵完全二叉樹
  • 堆的每棵子樹都是一個堆
  • 堆可以分爲最大堆和最小堆
  • 堆中的元素必須可以比較

堆的常用場景:

  • 構建優先隊列(可以參考Java的優先隊列PriorityQueue<E>
  • 支持堆排序
  • 快速找出一個集合中的最小值(或者最大值)

比如我們要實現一個優先隊列的時候,通常會以下幾種底層數據結構

數據結構 入隊 出隊
普通線性結構 O(1)[順序入隊] O(n)[每次都求優先級最高,類似求最大值]
順序線性結構 O(n)[入隊,每次都找到插入的位置] O(1) [因爲已經排好序,直接取優先級最高]
O(logn) O(logn)

最大堆和最小堆

堆分爲兩種:

  • 最大堆(大根堆)
    在最大堆中,父結點的值比所有子結點的值都要大(或相等)
  • 最小堆(小根堆)
    在最小堆中,父結點的值要比所有子結點的值要小(或相等)

注意點

以下是一棵最大堆
在這裏插入圖片描述

  • 從最大堆的特性中,我們知道,父結點的值一定是大於等於孩子結點的值,那麼如果有一個最大堆的高是3,排除根結點,高層級的結點一定是大於低層級結點的值嗎? 這個是不一定的,我們可以看到上面圖中的最大堆,第三層的結點E的值就比第四層結點H的值要小。

最大堆


實現基礎

我們通常說的堆數據結構,就是二叉堆,本質上是一棵完全二叉樹;通常在代碼的實現中,二叉堆的底層數據結構是使用數組而不是二叉鏈,因爲如果使用數組,二叉堆可以存在以下特性:

在這裏插入圖片描述
如果我們以數組來存儲堆的結點元素,從數組的第二個索引1開始存儲,堆中的取任意結點,索引爲n, 那麼可以滿足:

  • 父結點索引爲 n / 2
  • 左孩子索引爲 2 * n
  • 右孩子索引爲(2 * n) + 1

爲什麼要從數組的第二個位置,索引1開始存放元素呢?因爲很多的教材就是從索引開始存放的,公式也簡單好記。只不過在我們自己實現代碼時,可能就要多注意一下地方。

在我們瞭解堆使用數組存儲的特性後,爲了讓代碼更簡潔高效,我們要從數組第一個位置開始存儲,即索引爲0的地方也存放元素,優化一下,所以規律就變成了

  • 父結點索引爲 (n-1)/2
  • 左孩子索引爲 2*n + 1
  • 右孩子索引爲 (2*n + 1) + 1 = 2*n + 2

僅僅是沒這麼好記了罷了,不過代碼上的實現就簡單了,下面我們就來實現一個二叉堆的最大堆, 最小堆差不多的啦,就反過來而已。


動態數組

這裏我們主要是實現堆,所以不想考慮過多的數組細節,就複製了網上的一份動態數組的實現源碼,感覺就類似ArrayList吧,用來代替數組成爲二叉堆的

/**
 * 動態數據
 *
 * @param <E>
 */
public class Array<E> {

    private E[] data;
    private int size;

    public Array(int capacity) {  //  user assign size
        data = (E[]) new Object[capacity];
        size = 0;
    }

    public Array() {
        this(10); // default size
    }

    public Array(E[] arr) {
        data = (E[]) new Object[arr.length];
        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        size = arr.length;
    }


    public int getSize() {
        return size;
    }

    public int getCapacity() {
        return data.length;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Index is Illegal!");
        }
    }

    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
        if (size == data.length) {
            resize(data.length * 2);
        }
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    public void addLast(E e) { //末尾添加
        add(size, e);
    }

    public void addFirst(E e) { //頭部添加
        add(0, e);
    }


    public E get(int index) {
        rangeCheck(index);
        return data[index];
    }

    public E getLast() {
        return get(size - 1);
    }

    public E getFirst() {
        return get(0);
    }

    public void set(int index, E e) {
        rangeCheck(index);
        data[index] = e;
    }

    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }


    public E remove(int index) {  // remove data[index] and return the value
        rangeCheck(index);
        E res = data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        data[size] = null;//loitering objects  != memory  leak
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2); //防止複雜度的震盪
        }
        return res;
    }

    public E removeFirst() {
        return remove(0);
    }

    public E removeLast() {
        return remove(size - 1);
    }


    public void removeElement(E e) { //only remove one(may repetition) and user not know whether is deleted.
        int index = find(e);
        if (index != -1) {
            remove(index);
        }
    }

    // new method
    public void swap(int i, int j) {
        if (i < 0 || i >= size || j < 0 || j >= size) {
            throw new IllegalArgumentException("Index is illegal.");
        }

        E t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array : size = %d, capacity = %d\n", size, data.length));
        res.append("[");
        for (int i = 0; i < size; i++) {
            res.append(data[i]);
            if (i != size - 1) {
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}
  • 用於代替數組

上浮 shift up

  • 新添加的元素放到數組末尾
  • 判斷新添加的元素與其父結點的大小,如果新添元素大於其父結點,則上浮
  • 直到新添元素小於其父結點值,或新添元素已經上浮到根結點位置

    /**
     * index索引的元素執行上浮操作
     *
     * @param index
     */
    private void siftUp(int index) {

        //index不可以是根節點,所以必須>0 ,且上浮結點的值必須大於其父節點的值,只要滿足條件,一直上浮
        while (index > 0 && array.get(index).compareTo(array.get(parent(index))) > 0) {
            //交換位置
            array.swap(index, parent(index));
            //下一個
            index = parent(index);
        }

    }

下沉 shift down

  • 首先判斷下沉元素的孩子結點,看是左孩子大還是右孩子大?取最大的那個孩子與下沉結點比較,如果下沉元素比最大孩子的值還要大,就不需要下沉,已經滿足最大堆特性
  • 如果下沉元素比最大孩子小,則交換位置。
  • 不斷重複判斷是否執行下沉操作,直到下沉元素比孩子結點的值大或者下沉元素已經是葉子結點
 	/**
     * 對索引爲index的元素進行下沉操作
     *
     * @param index
     */
    private void siftDown(int index) {

        /**
         * 下沉同樣是一個循環,只要不是葉子結點不斷循環
         * 1. 只要下沉元素的左孩子的索引小於等於數組的最大索引,就代表下沉元素還不是葉子結點,還可以循環
         */
        while (lchild(index) <= array.getSize() - 1) {

            /**
             * 1. 求左右孩子誰的值大,就取誰的索引
             */
            //獲得左右孩子索引
            int lIndex = lchild(index);
            int rIndex = rchild(index);
            int max = 0;

            //求最大
            //如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
            if (rIndex > array.getSize() - 1) {
                max = lIndex;
                //如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
            } else {
                max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
            }

            /**
             * 下沉元素與最大孩子結點比較
             * 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
             */
            if (array.get(index).compareTo(array.get(max)) >= 0) {
                break;
            }

            //如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
            array.swap(index, max);
            //下一個
            index = max;


        }
    }

添加元素 add

  • 時間複雜度O(logn)
  • 追加元素到數組尾部
  • 對新添元素進行上浮操作,直到滿足最大堆特性
 /**
     * 給堆添加一個元素
     *
     * @param data
     */
    public void add(T data) {
        //動態數組中追加元素
        array.addLast(data);
        //新添元素執行上浮操作,傳入新添元素的索引,即最後一個位置
        siftUp(array.getSize() - 1);

    }

取最大值 extractMax

  • 時間複雜度O(logn)
  • 取出最大值,把堆中(數組)最後的元素與根結點交換位置,刪除最後的元素
  • 對交換後的根結點元素進行下沉操作,直到滿足最大堆特性
/**
     * 取出堆中的最大值
     *
     * @return
     */
    public T extractMax() {

        //找到最大值
        T max = array.get(0);
        //最後元素和根結點交換位置
        array.swap(0, array.getSize() - 1);
        //刪除最後的元素
        array.removeLast();
        //下沉操作
        siftDown(0);
        return max;

    }

取最大值,並插入新元素 replace

  • 原思想,extractMax + add 兩個O(logn)操作
  • 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
/**
     * 取出最大元素,同時插入一個新元素
     * 原思想,extractMax  +  add 兩個O(logn)操作
     * 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
     *
     * @param data
     * @return
     */
    public T replace(T data) {
        //獲得最大值
        T max = array.get(0);
        //根結點替換爲新元素
        array.set(0, data);
        //對新元素進行下沉
        siftDown(0);
        return max;
    }

將任意數組堆化 heapify

重點步驟:

  • 先找到堆的第一個非葉子節點(方式可以通過找到最後一個結點,它的父結點,就是第一個非葉子結點)
  • 所有非葉子結點,逐一下沉,直到根結點也完成下沉,就是整棵完全二叉樹堆化完成

好處:

  • 將n各元素逐個插入到一個空堆中,時間複雜度是O(nlogn)
  • heapify的時間複雜度是O(n), 推算比較複雜,這裏記住就好
 	/**
 	 * 實現構造方法中
     * 將數組堆化,heapify過程
     *
     * @param array
     */
    public MaxHeap(T[] array) {
        this.array = new Array<>(array);
        /**
         * heapify過程
         * 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
         * 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
         * 3. 最後完成堆化
         */
        for (int i = parent(array.length - 1); i >= 0; i--) {
            siftDown(i);
        }
    }

Java代碼實現


描述

  • 以動態數組爲底層數據結構
  • 從數組的第一個位置,即索引爲0的地方開始存放元素
  • 最大堆
  • 堆內元素必須可以比較,即實現了Comparable接口

實現功能

  • 上浮 shift up;
  • 下沉 shift down
  • 添加元素 add
  • 取最大值 extractMax
  • 取最大值,同時插入新元素 replace
  • 將任意數組堆化 heapify

完整代碼

動態數組

package com.snailmann.datastructure.heap;

/**
 * 動態數據
 *
 * @param <E>
 */
public class Array<E> {

    private E[] data;
    private int size;

    public Array(int capacity) {  //  user assign size
        data = (E[]) new Object[capacity];
        size = 0;
    }

    public Array() {
        this(10); // default size
    }

    public Array(E[] arr) {
        data = (E[]) new Object[arr.length];
        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        size = arr.length;
    }


    public int getSize() {
        return size;
    }

    public int getCapacity() {
        return data.length;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Index is Illegal!");
        }
    }

    public void add(int index, E e) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Index is Illegal ! ");
        }
        if (size == data.length) {
            resize(data.length * 2);
        }
        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    public void addLast(E e) { //末尾添加
        add(size, e);
    }

    public void addFirst(E e) { //頭部添加
        add(0, e);
    }


    public E get(int index) {
        rangeCheck(index);
        return data[index];
    }

    public E getLast() {
        return get(size - 1);
    }

    public E getFirst() {
        return get(0);
    }

    public void set(int index, E e) {
        rangeCheck(index);
        data[index] = e;
    }

    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }


    public E remove(int index) {  // remove data[index] and return the value
        rangeCheck(index);
        E res = data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        data[size] = null;//loitering objects  != memory  leak
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2); //防止複雜度的震盪
        }
        return res;
    }

    public E removeFirst() {
        return remove(0);
    }

    public E removeLast() {
        return remove(size - 1);
    }


    public void removeElement(E e) { //only remove one(may repetition) and user not know whether is deleted.
        int index = find(e);
        if (index != -1) {
            remove(index);
        }
    }

    // new method
    public void swap(int i, int j) {
        if (i < 0 || i >= size || j < 0 || j >= size) {
            throw new IllegalArgumentException("Index is illegal.");
        }

        E t = data[i];
        data[i] = data[j];
        data[j] = t;
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array : size = %d, capacity = %d\n", size, data.length));
        res.append("[");
        for (int i = 0; i < size; i++) {
            res.append(data[i]);
            if (i != size - 1) {
                res.append(", ");
            }
        }
        res.append("]");
        return res.toString();
    }
}

最大堆:

package com.snailmann.datastructure.heap;

import java.util.Arrays;
import java.util.Random;

/**
 * 最大堆 | 採用動態數組結構
 * 從索引爲0的地方開始存儲,動態數組
 * 最大堆元素必須可以比較,即實現Comparable接口
 * 父結點: (n - 1)/ 2
 * 左孩子: 2 * n + 1
 * 右孩子: 2 * n + 2
 *
 * @param <T>
 */
public class MaxHeap<T extends Comparable<T>> {


    /**
     * 底層數據結構 | 動態數組
     */
    private Array<T> array;

    public MaxHeap() {
        this.array = new Array<>();
    }

    /**
     * 將數組堆化,heapify過程
     *
     * @param array
     */
    public MaxHeap(T[] array) {
        this.array = new Array<>(array);
        //heapify
        heapify(array);
    }

    /**
     * heapify過程 | 最大堆
     * 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
     * 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
     * 3. 最後完成堆化
     *
     * @param array
     */
    public void heapify(T[] array) {

        for (int i = parent(array.length - 1); i >= 0; i--) {
            siftDown(i);
        }
    }


    /**
     * 返回堆的元素個數
     *
     * @return
     */
    public int size() {
        return array.getSize();
    }

    /**
     * 堆是否爲空
     *
     * @return
     */
    public boolean isEmpty() {
        return array.isEmpty();
    }

    /**
     * 獲取某個結點的父結點索引
     *
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            throw new RuntimeException("根結點沒有父結點");
        }

        return (index - 1) / 2;
    }

    /**
     * 獲取某個結點的左孩子索引
     *
     * @param index
     * @return
     */
    private int lchild(int index) {
        return (2 * index) + 1;
    }

    /**
     * 獲取某個結點的右孩子索引
     *
     * @param index
     * @return
     */
    private int rchild(int index) {
        return (2 * index) + 2;
    }

    /**
     * 給堆添加一個元素 | 時間複雜度O(logn)
     *
     * @param data
     */
    public void add(T data) {
        //動態數組中追加元素
        array.addLast(data);
        //新添元素執行上浮操作,傳入新添元素的索引,即最後一個位置
        siftUp(array.getSize() - 1);

    }

    /**
     * index索引的元素執行上浮操作
     *
     * @param index
     */
    private void siftUp(int index) {

        //index不可以是根節點,所以必須>0 ,且上浮結點的值必須大於其父節點的值,只要滿足條件,一直上浮
        while (index > 0 && array.get(index).compareTo(array.get(parent(index))) > 0) {
            //交換位置
            array.swap(index, parent(index));
            //下一個
            index = parent(index);
        }

    }

    /**
     * 取出堆中的最大值 | 時間複雜度O(logn)
     *
     * @return
     */
    public T extractMax() {

        //找到最大值
        T max = array.get(0);
        //最後元素和根結點交換位置
        array.swap(0, array.getSize() - 1);
        //刪除最後的元素
        array.removeLast();
        //下沉操作
        siftDown(0);
        return max;

    }

    /**
     * 對索引爲index的元素進行下沉操作
     *
     * @param index
     */
    private void siftDown(int index) {

        /**
         * 下沉同樣是一個循環,只要不是葉子結點不斷循環
         * 1. 只要下沉元素的左孩子的索引小於等於數組的最大索引,就代表下沉元素還不是葉子結點,還可以循環
         */
        while (lchild(index) <= array.getSize() - 1) {

            /**
             * 1. 求左右孩子誰的值大,就取誰的索引
             */
            //獲得左右孩子索引
            int lIndex = lchild(index);
            int rIndex = rchild(index);
            int max = 0;

            //求最大
            //如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
            if (rIndex > array.getSize() - 1) {
                max = lIndex;
                //如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
            } else {
                max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
            }

            /**
             * 下沉元素與最大孩子結點比較
             * 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
             */
            if (array.get(index).compareTo(array.get(max)) >= 0) {
                break;
            }

            //如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
            array.swap(index, max);
            //下一個
            index = max;


        }
    }

    /**
     * 取出最大元素,同時插入一個新元素
     * 原思想,extractMax  +  add 兩個O(logn)操作
     * 但是,我們直接把要插入的元素替換到根結點位置,再下沉,就只需要一個O(logn)了
     *
     * @param data
     * @return
     */
    public T replace(T data) {
        //獲得最大值
        T max = array.get(0);
        //根結點替換爲新元素
        array.set(0, data);
        //對新元素進行下沉
        siftDown(0);
        return max;
    }
    

    public static void main(String[] args) {
        int len = 100;
        Random random = new Random();
        MaxHeap<Integer> maxHeap = new MaxHeap<>();

        for (int i = 0; i < len; i++) {
            maxHeap.add(random.nextInt(100));
        }

        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = maxHeap.extractMax();
        }

        System.out.println(Arrays.toString(arr));
    }

}

  • 同一個無序數組,通過一個一個add出來的堆結構和heapify出來的堆結構,實際的結點位置是會有偏差的,不過都滿足堆的特性

堆排序


描述

什麼是堆排?
本來這裏主要是講一下堆的結構和實現,但是其實堆排序其實也是一個挺重要的數據結構知識,畢竟屬於基本的八大排序之一嘛,所以這裏就再補充一些堆排的知識

我們知道,堆的底層結構就是一個數組,所以我們就可以利用這個數組同時是堆的底層結構的特性,利用堆的特性,對這個數組進行排序,得到一個有序數組。 利用堆的特性對底層無序數組排序的過程就是堆排

堆排的特性:

堆排不跟其他排序算法一樣,不依賴什麼東西,堆排的實現非常的依賴堆這個數據結構,所以無堆則無堆排,所以如果我們要對一個無序數組進行排序,首先就要將該無序數組構造成一個最大堆或最小堆,不同種的堆也會造成不同的順序排序

  • 最大堆堆排後的結果是升序序列
  • 最小堆堆排後的結果是降序序列

堆排的時間複雜度是:

在這裏插入圖片描述

基本思想和實現步驟

堆排是對一個無序數組堆化,再排的過程,所以它的核心思想就是:

  • 看無序數組是想進行什麼排序?升序還是降序?
  • 如果升序,就先將無序數組堆化成最大堆,反之則最小堆。通過heapify去實現
  • 堆化後,將堆頂元素與末尾元素交換,堆的結點長度減1,對當前堆進行重建,下沉,直到重新滿足堆特性
  • 遍歷堆中未交換過的結點,遍歷交換完畢後,就是數組排序的結束

代碼實現

package com.snailmann.datastructure.heap;

/**
 * 堆排 | 升序 |O(nlogn)
 * 最大堆 -> 升序排序
 *
 * @param <T>
 */
public class MaxHeapSort<T extends Comparable<T>> {


    /**
     * 底層數據結構 | 動態數組
     */
    private Array<T> array;

    public MaxHeapSort() {
        this.array = new Array<>();
    }


    /**
     * heapify過程 | 最大堆
     * 1. i 初始化爲 第一個非葉子結點的索引,通過最後一個元素的父結點的方式確定
     * 2. i之後每次減1,就是上一個非葉子結點,直到根結點也完成下沉化
     * 3. 最後完成堆化
     *
     * @param array
     */
    public void heapify(T[] array) {
        for (int i = parent(array.length - 1); i >= 0; i--) {
            siftDown(i, array.length);
        }
    }


    /**
     * 獲取某個結點的父結點索引
     *
     * @param index
     * @return
     */
    private int parent(int index) {
        if (index == 0) {
            throw new RuntimeException("根結點沒有父結點");
        }

        return (index - 1) / 2;
    }

    /**
     * 獲取某個結點的左孩子索引
     *
     * @param index
     * @return
     */
    private int lchild(int index) {
        return (2 * index) + 1;
    }

    /**
     * 獲取某個結點的右孩子索引
     *
     * @param index
     * @return
     */
    private int rchild(int index) {
        return (2 * index) + 2;
    }


    /**
     * 對索引爲index的元素進行下沉操作
     *
     * @param index 下沉元素索引
     * @param len   要重建的堆的結點個數
     */
    private void siftDown(int index, int len) {

        /**
         * 下沉同樣是一個循環,只要不是葉子結點不斷循環
         * 1. 只要下沉元素的左孩子的索引小於整個數組的長度,就代表下沉元素還不是葉子結點,還可以循環
         */
        while (lchild(index) <= len - 1) {

            /**
             * 1. 求左右孩子誰的值大,就取誰的索引
             */
            //獲得左右孩子索引
            int lIndex = lchild(index);
            int rIndex = rchild(index);
            int max = 0;

            //求最大
            //如果其右孩子索引大於數組的最大索引,則越界,不存在右孩子 | while循環已經保證了肯定有左孩子 |右孩子索引沒有越界,就代表有右孩子
            if (rIndex > len - 1) {
                max = lIndex;
                //如果有右孩子,則比較左右孩子的大小,取最大的孩子的索引
            } else {
                max = array.get(lIndex).compareTo(array.get(rIndex)) > 0 ? lIndex : rIndex;
            }

            /**
             * 下沉元素與最大孩子結點比較
             * 1. 如果下沉元素比最大的孩子結點都要大,那麼這就代表下沉已經結束,堆結構特性已經滿足
             */
            if (array.get(index).compareTo(array.get(max)) >= 0) {
                break;
            }

            //如果下沉元素沒有最大孩子結點大,則交換位置,繼續下沉
            array.swap(index, max);
            //下一個
            index = max;


        }
    }

    /**
     * 堆排序 | 最大堆 -> 升序 | 時間複雜度O(nlogn)
     * 1. 把無序數組堆化,構建二叉堆
     * 2. 將堆頂元素與末尾元素交換,循環下沉直至重新滿足最大堆結構,待所有元素都交換完畢後,排序完成
     *
     * @param arary
     */
    public void heapSort(T[] arary) {
        //將無序數組構建成一個最大堆
        this.array = new Array<>(arary);
        heapify(arary);
        System.out.println(this.array.toString());

        //循環交換,重建的過程
        for (int i = this.array.getSize() - 1; i > 0; i--) {
            //交換位置
            this.array.swap(0, i);
            //每交換一次,實際的堆結構減少一個長度,因爲交換到尾部的大數值,已經排序完畢
            int len = i - 1;
            //重建,下沉
            siftDown(0, len);
        }
        System.out.println(this.array.toString());
    }

    @Override
    public String toString() {
        return this.array.toString();
    }


    public static void main(String[] args) {
        Integer[] nums = new Integer[]{3, 4, 1, 3, 0, 4, 7, 9};
        MaxHeapSort<Integer> maxHeapSort = new MaxHeapSort<>();
        maxHeapSort.heapSort(nums);
    }


}


參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章