n個關鍵字序列kl,k2,…,kn稱為(heap),當且僅當該序列滿足如下性質(簡稱為堆性質):
(1)ki<=k(2i)且ki<=k(2i+1)(1≤i≤ n),當然,這是小根堆,大根堆則換成》=號。//k(i)相當於二叉樹的非葉結點,k(2i)則是左孩子,k(2i+1)是右孩子
若將此序列所儲存的向量r[1..n]看做是一棵完全二叉樹的儲存結構,則堆實質上是滿足如下性質的完全二叉樹:
樹中任一非葉結點的關鍵字均不大於(或不小於)其左右孩子(若存在)結點的關鍵字。
堆可以被看成是一棵樹,結點在堆中的高度可以被定義為從本結點到葉子結點的最長簡單下降路徑上邊的數目;定義堆的高度為樹根的高度。我們將看到,堆結構上的一些基本操作的執行時間至多是與樹的高度成正比,為o(lgn)。
堆排序利用了大根堆(或小根堆)堆頂記錄的關鍵字最大(或最小)這一特徵,使得在當前無序區中選取最大(或最小)關鍵字的記錄變得簡單。
(1)用大根堆排序的基本思想
① 先將初始檔案r[1..n]建成乙個大根堆,此堆為初始的無序區
② 再將關鍵字最大的記錄r[1](即堆頂)和無序區的最後乙個記錄r[n]交換,由此得到新的無序區r[1..n-1]和有序區r[n],且滿足r[1..n-1].keys≤r[n].key
③由於交換後新的根r[1]可能違反堆性質,故應將當前無序區r[1..n-1]調整為堆。然後再次將r[1..n-1]中關鍵字最大的記錄r[1]和該區間的最後乙個記錄r[n-1]交換,由此得到新的無序區r[1..n-2]和有序區r[n-1..n],且仍滿足關係r[1..n-2].keys≤r[n-1..n].keys,同樣要將r[1..n-2]調整為堆。
……直到無序區只有乙個元素為止。
(2)大根堆排序演算法的基本操作:
① 初始化操作:將r[1..n]構造為初始堆;
② 每一趟排序的基本操作:將當前無序區的堆頂記錄r[1]和該區間的最後乙個記錄交換,然後將新的無序區調整為堆(亦稱重建堆)。
注意:①只需做n-1趟排序,選出較大的n-1個關鍵字即可以使得檔案遞增有序。
②用小根堆排序與利用大根堆類似,只不過其排序結果是遞減有序的。堆排序和直接選擇排序相反:在任何時刻堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往
堆排序的時間,主要由建立初始堆和反覆重建堆這兩部分的時間開銷構成,它們均是通過呼叫heapify實現的。
堆排序的最壞時間複雜度為o(nlogn)。堆序的平均效能較接近於最壞效能。
由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的檔案。
堆排序是就地排序,輔助空間為o(1),
它是不穩定的排序方法。
要將初始檔案r[l..n]調整為乙個大根堆,就必須將它所對應的完全二叉樹中以每一結點為根的子樹都調整為堆。
顯然只有乙個結點的樹是堆,而在完全二叉樹中,所有序號大於n/2的結點都是葉子,因此以這些結點為根的子樹均已是堆。這樣,我們只需依次將以序號為n/2,…,1的結點作為根的子樹都調整為堆即可。
#include using namespace std;
const int max = 100;
typedef struct sqlist
sqlist;
typedef sqlist heaptype;
void heapadjust(heaptype &h, int s, int m)
if (rc >= h.r[j])
h.r[s] = h.r[j];
s = j;
} h.r[s] = rc;//插入
}void heapsort(heaptype &h)
for (int i = h.length; i > 1; -- i) }
int main()
cout << endl;
return 0;
}
結果:(小頂堆)
如果將if (j < m && h.r[j] < h.r[j+1])和if (rc >= h.r[j])改為if (j < m && h.r[j] > h.r[j+1])和if (rc <= h.r[j])輸出將變為6 5 4 3 2 1(大頂堆)
佇列的特點是先進先出。通常都把佇列比喻成排隊買東西,大家都很守秩序,先排隊的人就先買東西。但是優先佇列有所不同,它不遵循先進先出的規則,而是根據佇列中元素的優先權,優先權最大的先被取出。通常把優先佇列比喻成現實生活中的列印。乙個列印店裡有很多印表機,每台機器的效能不一樣,有的印表機列印很快,有的印表機列印速度很慢。當這些印表機陸陸續續列印完自己的任務時進入排隊等候狀態。如果我這個時候要列印乙份檔案,我選的不是第乙個排隊的印表機,而是效能最好,列印最快的印表機。
重點:優先順序佇列,是要看優先順序的,誰的優先順序更高,誰就先得到許可權。不分排隊的順序!
基本操作:
empty() 如果隊列為空返回真
pop() 刪除對頂元素
push() 加入乙個元素
size() 返回優先佇列中擁有的元素個數
top() 返回優先佇列對頂元素
在預設的優先佇列中,優先順序高的先出隊。在預設的int型中先出隊的為較大的數。
下面是最小堆實現的優先佇列:
#include #include using namespace std;
class heap
;heap::heap(int isize = 100)//注意這裡是從0開始,所以如果根是i,那麼左孩子是2*i+1,右孩子是2*i+2
heap::~heap()
int heap::enqueue(int ival)//進入堆
m_pdata[m_iamount ++] = ival;
int iindex = m_iamount - 1;
while (m_pdata[iindex] < m_pdata[(iindex - 1) /2])//上浮,直到滿足最小堆
return 1;
}int heap::dequeue(int &ival)//出堆
ival = m_pdata[0];//出堆的資料
m_pdata[0] = m_pdata[m_iamount - 1];//最後乙個資料放到第乙個根上面
-- m_iamount;//總數減1
int rc = m_pdata[0];
int s = 0;
for (int j = 2*s +1; j < m_iamount; j *= 2)//最後乙個數放到第乙個位置以後,開始下沉,來維持堆的性質
if (rc < m_pdata[j])//rc應該插入在s位置上
m_pdata[s] = m_pdata[j];
s = j;
} m_pdata[s] = rc;
return 1;
}int heap::getmin(int &ival)
ival = m_pdata[0];
return 1;
}void heap::printqueue()
cout << endl;
}int main()
使用方法:
標頭檔案:
#include
宣告方式:
1、普通方法:
priority_queueq;
//通過操作,按照元素從大到小的順序出隊
2、自定義優先順序:
struct cmp
};
//其中,第二個引數為容器型別。第三個引數為比較函式。結構體宣告方式:
struct node
}; priority_queueq;//定義方法
//在該結構中,y為值, x為優先順序。
//通過自定義operator《操作符來比較元素中的優先順序。
//在過載」」,可能會發生編譯錯誤
首先優先佇列是由堆來實現的,所以以後用到優先佇列的地方,可以直接用c++中的stl來直接實現,但是最好還是自己實現幾次,否則我們只會成為傻瓜,什麼東西就知道怎麼用,不知道是如何實現的。
優先佇列適用的範圍很廣,比如:
構造哈夫曼編碼是找到節點集合中頻率最小的兩個點,然後合併節點在插入到集合中,再迴圈。。。
比如作業系統的執行緒的排程演算法,有的是按照優先順序來排程的,每次都執行優先順序較高的執行緒
首先把n個有序檔案的第乙個元素取出來,放到優先佇列裡面,然後取最小值,然後再插入元素導優先佇列,取最小值。。。
比如排序,找中位數,找最大的k個數
堆(優先佇列,即最大堆,最小堆)
優先佇列 priority queue 特殊的 佇列 取出元素的順序是 依照元素的優先權 關鍵字 大小,而不是元素進入佇列的先後順序。堆的兩個特性 結構性 用陣列表示的完全二叉樹 有序性 任一結點的關鍵字是其子樹所有結點的最大值 或最小值 最大堆 maxheap 也稱 大頂堆 最大值 最小堆 min...
C 優先佇列的過載(最小堆 最大堆)
c 優先佇列預設是最大堆,所以如果我們要用到最小堆,就需要進行過載來使用。priority queue的標頭檔案是.1.less和greater,不利用struct進行過載。priority queue,less s less表示按照遞減 從大到小 的順序插入元素 priority queue,gr...
最大堆 最小堆
堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於 或不小於 其左孩子和右孩子節點的值。最大堆和最小堆是 二叉堆的兩種形式。最大堆 根結點的鍵值是所有堆結點鍵值中最大者。最小堆 根結點的鍵值是所有堆結點鍵值中最小者。而最大 最小堆集結了最大堆和最小堆的優點,這也是其名字的由來。最大 最...