動畫:一篇文章快速學會數據結構-堆

內容介紹

樹結構簡介

樹結構是計算機中經常使用的一種數據結構。咱們先來看一下生活中的樹: java

計算機中的樹和生活中的樹是相似的,只不過是倒着的,樹根在上,樹葉在下。樹上的每一個組成元素都是一個節點,樹根稱爲根節點,樹枝稱爲分支節點,樹葉稱爲葉子節點,以下圖所示: 編程

二叉樹結構簡介

二叉樹是:每一個節點最多隻能有兩個子節點樹。二叉樹的子節點分爲左節點和右節點,以下圖:api

滿二叉樹:若是該二叉樹的全部葉子節點都在最後一層,而且節點總數= 2^n -1 , n 爲層數,則咱們稱爲滿二叉樹,以下圖: 數組

徹底二叉樹:若是該二叉樹的全部葉子節點都在最後一層或者倒數第二層,並且最後一層的葉子節點在左邊連續,倒數第二 層的葉子節點在右邊連續,咱們稱爲徹底二叉樹,以下圖: 微信

咱們這裏注意理解徹底二叉樹,由於堆結構是一種特殊的徹底二叉樹。關於樹的結構,咱們先簡單介紹,之後會專門講解樹結構,咱們這裏主要是講堆結構,因此先簡單說起一下樹結構。數據結構

堆結構簡介

堆(Heap)是一種特殊的樹形數據結構,每一個結點都有一個值。常見的堆有二叉堆、斐波那契堆等,一般咱們所說的堆的數據結構,是指二叉堆。以下圖: 動畫

堆知足下列兩個性質:code

  1. 堆中某個節點的值老是不大於或不小於其父節點的值。
  2. 堆老是一棵徹底二叉樹。 根節點最大的堆叫作最大堆大根堆,根節點最小的堆叫作最小堆小根堆,以下圖所示:

堆的存儲

堆是非線性數據結構,能夠使用一維數組來存儲,將堆中序號對應的數據放到數組對應的索引中,以下圖: blog

堆的一些概念和規律

概念:排序

  1. 某節點左邊的子節點成爲:左孩子。
  2. 某節點右邊的子節點成爲:右孩子。
  3. 某節點的上一個節點成爲:父節點。

規律:假設當前節點的索引爲i

  1. 父節點索引 = (i - 1) / 2 (Java中除以2取整數,好比7/2 = 3)
  2. 左孩子索引 = 2 * i + 1
  3. 右孩子索引 = 2 * i + 2

堆的定義性質:

  1. 最大堆節點的值大於左右孩子的值,也就是知足:arr[i] > arr[2*i+1] && arr[i] > arr[2*i+2],以下圖:

堆獲取最大值

獲取最大堆的最大值,其實就是獲取堆中最前面一個元素。對於堆這種數據結構一般是將最前面的元素和最後面的元素換位置,最大值就到了最後一個位置,而後從堆中排除這個元素,當最後一個元素交換到最前面時,此時就不知足堆的性質了,咱們須要將最前面這個元素經過ShiftDown(下沉)的手段讓堆繼續知足堆的規則。

堆獲取最大值能夠分紅兩個步驟:

  1. 將堆中最前面的最大值和最後一個元素交換位置。
  2. 使用ShiftDown讓最前面的元素下沉到合適的位置,依然知足堆的性質。 動畫演示效果以下:

這裏面重點注意,ShiftDown可讓堆中的一個元素下沉到合適的位置,而且知足堆的規則。後面咱們構建堆就須要使用到ShiftDown操做。

ShiftDown詳細圖解:

最大堆的最後一個非葉子節點

  1. 咱們構建堆時須要從最後一個非葉子節點開始按照規則構建堆,因此咱們須要知道最後一個非葉子節點計算公式:(堆的最大索引-1) / 2。

構建一個堆結構

構建堆實際上是將無序的徹底二叉樹調整爲二叉堆。非葉子節點沒有子節點不須要從新構建,而後自底向上對每個子樹執行SiftDown操做,直到完成二叉堆化。 假設咱們如今有一個數組,內容爲:{6, 3, 7, 5, 8, 2, 1, 4, 9},它是不知足堆的規則,咱們如今將這個數組構建成一個二叉堆,步驟爲:

  1. 找到最後一個非葉子節點,使用ShiftDown下沉,使這個顆樹知足堆的規則。
  2. 找到倒數第二個非葉子節點,使用ShiftDown下沉,使這個顆樹知足堆的規則。
  3. 以此類推,直到找到最前面的一個元素使用ShiftDown下沉,使這顆樹知足堆的規則。 將無序的徹底二叉樹調整爲二叉堆的過程稱爲heapify,動畫以下:

堆添加數據

往堆中插入一個元素,是在數組的最末尾插入新的數據,此時可能不知足堆的特性,咱們須要進行自下而上調整子節點和父節點,不知足堆性質則交換父子元素,直到當前子樹知足堆的性質。動畫效果以下:

代碼以下:

public class Heap {
    public static void main(String[] args) {
        int[] arr = {6, 3, 7, 5, 8, 2, 1, 4, 9};
        heapify(arr);

        System.out.println("構建堆後:" + Arrays.toString(arr));
        arr = insert(arr, 11);
        System.out.println("堆中插入數據後:" + Arrays.toString(arr));
    }

    // 往數組中添加一個數據
    public static int[] insert(int[] arr, int element) {
        arr = Arrays.copyOf(arr, arr.length + 1);
        // 複製以前的數組數據到新數組中
        arr[arr.length-1] = element;
        
        shiftUp(arr, arr.length-1);
        return arr;
    }

    // 上浮操做,將i索引元素上浮到合適位置,保證知足堆的兩個特性
    private static void shiftUp(int[] arr, int i) {
        // (i-1) / 2: 是i的父節點
        while ((i-1) / 2 >= 0 && arr[(i-1) / 2] < arr[i]) {
            swap(arr, (i-1) / 2, i);
            i = (i-1) / 2;
        }
    }

    // heapify將無序的徹底二叉樹調整爲二叉堆
    private static void heapify(int[] arr) {
        // 從非葉子節點開始,Shift Down將每一個子樹構建成最大堆
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(arr, i);
        }
    }

    // 下沉操做,將指定元素下沉到子樹的合適位置,使這個顆樹知足堆的規則。
    private static void shiftDown(int[] arr, int i) {
        // 循環找子孩子交換位置。左孩子不能越界
        while (2*i + 1 < arr.length) {
            // 假設要交換的是左孩子
            int j = 2*i + 1;
            // 判斷是否有有孩子,而且右孩子是否大於左孩子
            if (j+1 < arr.length && arr[j+1] > arr[j]) {
                j++; // 若是是,和右孩子交換
            }

            // 若是當前節點大於兩個孩子,就不須要交換
            if (arr[i] > arr[j])
                break;

            // 當前節點小於子孩子,將當前節點和較大的子孩子交換
            swap(arr, i, j);
            // 在判斷下一層
            i = j;
        }
    }

    public static void swap(int[] arr, int start, int end) {
        if (start == end) return;

        int temp = arr[start];
        arr[start] = arr[end];
        arr[end] = temp;
    }
}

添加元素放在堆的最後面,使用ShiftUp讓元素上浮,讓添加的元素找到一個合適的位置,讓堆中的數據依然知足堆的特性。

堆的做用

  1. 用做優先隊列,咱們知道堆的最前一個元素就是堆的最大值。咱們能夠依據優先級來構建堆,每次都取出堆中優先級最高的那個數據。
  2. 用做堆排序。
  3. 查找第N大(小)元素。
  4. 查找前N大(小)元素。

後續咱們會選擇合適的時間來完成上面這些功能。

總結

  1. 堆是一種特殊的徹底二叉樹,堆中某個節點的值老是不大於或不小於其父節點的值。堆是非線性數據結構,使用數組來存儲,操做堆其實就是操做數組中的數據。
  2. 假設當前節點的索引爲i,父節點索引 = (i - 1) / 2 (Java中除以2取整數,好比7/2 = 3),左孩子索引 = 2 * i + 1,右孩子索引 = 2 * i + 2
  3. 使用ShiftDown讓堆中最前面的元素下沉到合適的位置,讓堆依然知足堆的性質。
  4. 添加元素放在堆的最後面,使用ShiftUp讓元素上浮,讓添加的元素找到一個合適的位置,讓堆中的數據依然知足堆的特性。

原創文章和動畫製做真心不易,您的點贊就是最大的支持! 想了解更多文章請關注微信公衆號:表哥動畫學編程

相關文章
相關標籤/搜索