算法學習(3)——堆排序


轉載自:http://www.cnblogs.com/skywang12345/p/3602162.html


堆排序介紹

堆排序(Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。
因此,學習堆排序之前,有必要了解堆!若讀者不熟悉堆,建議先了解(建議可以通過二叉堆左傾堆斜堆二項堆斐波那契堆等文章進行了解),然後再來學習本章。

我們知道,堆分爲"最大堆"和"最小堆"。最大堆通常被用來進行"升序"排序,而最小堆通常被用來進行"降序"排序。
鑑於最大堆和最小堆是對稱關係,理解其中一種即可。本文將對最大堆實現的升序排序進行詳細說明。

 

最大堆進行升序排序的基本思想:
① 初始化堆:將數列a[1...n]構造成最大堆。
② 交換數據:將a[1]和a[n]交換,使a[n]是a[1...n]中的最大值;然後將a[1...n-1]重新調整爲最大堆。 接着,將a[1]和a[n-1]交換,使a[n-1]是a[1...n-1]中的最大值;然後將a[1...n-2]重新調整爲最大值。 依次類推,直到整個數列都是有序的。

 

下面,通過圖文來解析堆排序的實現過程。注意實現中用到了"數組實現的二叉堆的性質"。
在第一個元素的索引爲 0 的情形中:
性質一:索引爲i的左孩子的索引是 (2*i+1);
性質二:索引爲i的左孩子的索引是 (2*i+2);
性質三:索引爲i的父結點的索引是 floor((i-1)/2);

例如,對於最大堆{110,100,90,40,80,20,60,10,30,50,70}而言:索引爲0的左孩子的所有是1;索引爲0的右孩子是2;索引爲8的父節點是3。

下面演示heap_sort_asc(a, n)對a={20,30,90,40,70,110,60,10,100,50,80}, n=11進行堆排序過程。下面是數組a對應的初始化結構:

1、初始化堆

在堆排序算法中,首先要將待排序的數組轉化成二叉堆。
下面演示將數組{20,30,90,40,70,110,60,10,100,50,80}轉換爲最大堆{110,100,90,40,80,20,60,10,30,50,70}的步驟。

 

1.1 i=11/2-1,即i=4

上面是maxheap_down(a, 4, 9)調整過程。maxheap_down(a, 4, 9)的作用是將a[4...9]進行下調;a[4]的左孩子是a[9],右孩子是a[10]。調整時,選擇左右孩子中較大的一個(即a[10])和a[4]交換。

 

1.2 i=3

上面是maxheap_down(a, 3, 9)調整過程。maxheap_down(a, 3, 9)的作用是將a[3...9]進行下調;a[3]的左孩子是a[7],右孩子是a[8]。調整時,選擇左右孩子中較大的一個(即a[8])和a[4]交換。

 

1.3 i=2


上面是maxheap_down(a, 2, 9)調整過程。maxheap_down(a, 2, 9)的作用是將a[2...9]進行下調;a[2]的左孩子是a[5],右孩子是a[6]。調整時,選擇左右孩子中較大的一個(即a[5])和a[2]交換。

 

1.4 i=1


上面是maxheap_down(a, 1, 9)調整過程。maxheap_down(a, 1, 9)的作用是將a[1...9]進行下調;a[1]的左孩子是a[3],右孩子是a[4]。調整時,選擇左右孩子中較大的一個(即a[3])和a[1]交換。交換之後,a[3]爲30,它比它的右孩子a[8]要大,接着,再將它們交換。

 

1.5 i=0


上面是maxheap_down(a, 0, 9)調整過程。maxheap_down(a, 0, 9)的作用是將a[0...9]進行下調;a[0]的左孩子是a[1],右孩子是a[2]。調整時,選擇左右孩子中較大的一個(即a[2])和a[0]交換。交換之後,a[2]爲20,它比它的左右孩子要大,選擇較大的孩子(即左孩子)和a[2]交換。

調整完畢,就得到了最大堆。此時,數組{20,30,90,40,70,110,60,10,100,50,80}也就變成了{110,100,90,40,80,20,60,10,30,50,70}。

 

2、交換數據

在將數組轉換成最大堆之後,接着要進行交換數據,從而使數組成爲一個真正的有序數組。
交換數據部分相對比較簡單,下面僅僅給出將最大值放在數組末尾的示意圖。

上面是當n=10時,交換數據的示意圖。
當n=10時,首先交換a[0]和a[10],使得a[10]是a[0...10]之間的最大值;然後,調整a[0...9]使它稱爲最大堆。交換之後:a[10]是有序的!
當n=9時, 首先交換a[0]和a[9],使得a[9]是a[0...9]之間的最大值;然後,調整a[0...8]使它稱爲最大堆。交換之後:a[9...10]是有序的!
...
依此類推,直到a[0...10]是有序的。

3、C++代碼實現

/* 
 * (最大)堆的向下調整算法
 *
 * 注:數組實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *     其中,N爲數組下標索引值,如數組中第1個數對應的N爲0。
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     start -- 被下調節點的起始位置(一般爲0,表示從第1個開始)
 *     end   -- 截至範圍(一般爲數組中最後一個元素的索引)
 */
void maxheap_down(int a[], int start, int end)
{
    int c = start;            // 當前(current)節點的位置
    int l = 2*c + 1;        // 左(left)孩子的位置
    int tmp = a[c];            // 當前(current)節點的大小
    for (; l <= end; c=l,l=2*l+1)
    {
        // "l"是左孩子,"l+1"是右孩子
        if ( l < end && a[l] < a[l+1])
            l++;        // 左右兩孩子中選擇較大者,即m_heap[l+1]
        if (tmp >= a[l])
            break;        // 調整結束
        else            // 交換值
        {
            a[c] = a[l];
            a[l]= tmp;
        }
    }
}

/*
 * 堆排序(從小到大)
 *
 * 參數說明:
 *     a -- 待排序的數組
 *     n -- 數組的長度
 */
void heap_sort_asc(int a[], int n)
{
    int i;

    // 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
    for (i = n / 2 - 1; i >= 0; i--)
        maxheap_down(a, i, n-1);

    // 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
    for (i = n - 1; i > 0; i--)
    {
        // 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
        swap(a[0], a[i]);
        // 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
        // 即,保證a[i-1]是a[0...i-1]中的最大值。
        maxheap_down(a, 0, i-1);
    }
}

 

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