淺析堆的基本操作以及堆排序

一、堆的基本介紹

1、堆的概念
堆是一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵樹的數組的對象。堆滿足以下的性質
(1)堆中某個節點的值總是不大於或不小於其父節點的值
(2)堆總是一棵完全二叉樹

2、堆的分類
(1)最小堆:任一節點的關鍵碼均小於等於它的左右孩子的關鍵碼,位於堆頂的節點的關鍵碼最小(向上調整)
(2)最大堆:任一節點的關鍵碼均大於等於它的左右孩子的關鍵碼,位於堆頂的節點的關鍵碼最大(向下調整)

3、堆中下標爲i的節點的特點
因爲堆是存儲在下標爲0 開始計數的數組中,因此在堆中給定下標爲i的節點時:
(1)如果i=0,節點i爲根節點, 沒有雙親節點; 否則節點i的雙親節點爲節點(i-1)/2
(2)如果2*i+1>n-1,則節點i無左孩子,否則節點i的左孩子爲節點2*i+1
(3)如果2*i+2>n-1,則節點i無右孩子,否則節點i的右孩子爲節點2*i+2

二、堆的基本操作
1、堆的創建

給定數組:int array = {23,14,8,90,45,70,66}
先將數組用二叉樹表示出來:
這裏寫圖片描述
因爲現在還是二叉樹,不算意義上的堆,所以我們需要把它調整成爲最小堆or最大堆
(1)向下調整:(最大堆)
我們可以將調整一整棵樹細化成爲調節樹的左右子樹爲最小堆,再去調節以左右子樹爲雙親節點的左右子樹……,一直到整棵都被調整完。具體過程如下:

* a、找到樹中最後一個非葉子節點,然後開始調整
* b、比較其左右孩子節點的大小,找出較大的孩子節點
* c、用較大的孩子節點去和它的雙親作比較。倘若孩子節點大於雙親節點,則交換雙親和孩子節點,並更新雙親節點和孩子節點;若小於,則結束此次的調整。

調整上圖的二叉樹爲最大堆,如下圖所示:
這裏寫圖片描述

//向下調整(最大堆)
      void _AdjustDown(size_t parent)
      {
            //用child標記左右孩子中最小的孩子,默認左孩子是最小的
            size_t child = parent * 2 + 1;
            size_t size = _array.size();
            Compare com;   //比較器                             //(小於的參數放在前面)
            while (child < size)
            {
                  //找左右孩子中最小的
                  if (child + 1 < size && com(_array[child + 1], _array[child]))   //左右孩子之間比較
                  {
                        child = child + 1;
                  }
                  //用大孩子和雙親去比較
                  if (com(_array[child], _array[parent]))
                  {
                        swap(_array[parent], _array[child]);
                        //調整子樹
                        parent = child;
                        child = parent * 2 + 1;
                  }
                  else
                        break;
            }
      }
注:這裏也可不用比較器

(2)向上調整(最小堆)
向上調整的過程基本上和向下調整差不多,就只是比較大小的時候是相反的,這裏就不過多贅述了。直接上代碼吧!!!

//向上調整(最小堆)
      void _AdjustUp(size_t child)
      {
            size_t parent = (child - 1) / 2;
            while (child > 0)
            {
                  if (_array[child] > _array[parent])
                  {
                        swap(_array[child], _array[parent]);
                        parent = child;
                        child = (parent - 1) / 2;
                  }
                  else
                        break;
            }
      }

2、插入
往堆裏插入元素就是在已經調整好的最大堆or最小堆的最後面插入元素,但插入之後可能會破會堆的結構,因此需要將堆重新調整,使其滿足最大堆or最小堆。
這裏寫圖片描述

//尾插
      void Push(const T& data)
      {
            _array.push_back(data);
            if (_array.size()>1)
            {
                  _AdjustUp(_array.size()-1);
            }
      }

3、堆的刪除
堆的刪除就是從堆中刪除堆頂元素。刪除堆頂元素之後,用堆的最後一個葉子結點去填補剛被刪除的堆頂元素,並將堆的實際元素減一。但用最後一個元素取代堆頂的元素之後可能會破壞堆的平衡,因此需要將堆重新調整,使其滿足最大堆or最小堆。
這裏寫圖片描述

//刪除堆中元素
      void Pop()
      {
            if (Empty())
            {
                  return;
            }
            size_t last = _array.size() - 1;
            swap(_array[0], _array[last]);
            _array.pop_back();
            if (_array.size()>1)
            {
                  _AdjustDown(0);
            }
      }

4、堆排序(一種不穩定的排序算法)
(1)先創建好堆(見上述具體過程)。升序排列時創建大堆,降序排列時創建小堆
(2)排序(就是讓剛纔建好的大堆or小堆變得有序起來)。具體過程如下

* a、把堆頂元素array[0]和堆的最後一個元素交換
* b、最堆元素個數減一(這樣就是爲了避開剛交換的元素去調整剩下的元素)
* c、當交換元素之後,堆肯定會不滿足最堆的定義,此次需要調整節點

這裏寫圖片描述
**

注:重複上圖的過程一直到堆排列有序

**

//調整堆
      void AdjustHeap(int array, size_t size, size_t parent)
      {
            //標記最大的孩子,默認左孩子最大
            size_t child = parent * 2 + 1;
            while (child < size)
            {
                  //找左右孩子中最大的孩子
                  if (child + 1 < size && array[child + 1] > array[child])
                  {
                        child = child + 1;
                  }
                  //用最大的孩子去檢測雙親
                  if (array[parent] < array[child])
                  {
                        swap(array[parent], array[child]);
                        parent = child;
                        child = parent * 2 + 1;
                  }
                  else
                        return;
            }
      }

//堆排序
      void HeapSort(int* array, size_t size)
      {
            //創建堆
            //倒數第一個非葉子節點的位置
            int root = (size - 2) / 2;
            for (; root >= 0; --root)
            {
                  AdjustHeap(array, size, root);
            }
            //堆排序
            for (int i = 0; i < size; i++)
            {
                  swap(array[0], array[size - i - 1]);
                  AdjustHeap(array[0], size - i - 1, 0)
            }
      }

5、堆的應用
(1)堆的應用之大數據處理:從1000億個數據中找出最大的前k個數
思路:

* a、先取出k個數據
* b、將去取出的數據建爲小堆(建立小堆的話,堆頂元素就是最大的,這樣      就可以用比堆頂大的元素去交換,但倘若建大堆的話,堆頂元素就是最大的,這樣後面的數據元素就會進不去,這樣只會找到一個最大的元素)
* c、將剩下的數據依次和堆頂比較,若比堆頂大,則去替換堆頂,然後調整堆。週而復始,依次進行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章