【資料結構】——堆及其應用
一、堆
先說說堆概念:如果有一個關鍵碼的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉樹的順序儲存方式儲存在一個一維陣列中,並滿足:Ki <= K2*i+1 且 Ki<= K2*i+2 (Ki >=K2*i+1 且 Ki >= K2*i+2) i =0,1,2…,則稱為小堆(或大堆)。
小堆(大堆)中:任一結點的關鍵碼均小於(大於)等於它的左右孩子的關鍵碼,位於堆頂結點的關鍵碼最小(最大),從根節點到每個結點的路徑上陣列元素組成的序列都是遞增(遞減)的堆儲存在下標為0開始的陣列中,因此在堆中給定下標為i的結點時:如果i=0,結點i是根節點,沒有雙親節點;否則結點i的雙親結點為結點(i-1)/2如果2 * i + 1 <= n - 1,則結點i的左孩子為結點2 * i + 1,否則結點i無左孩子如果2 * i + 2 <= n - 1,則結點i的右孩子為結點2 * i + 2,否則結點i
①大小堆的構建
將二叉樹調整為最小堆的原理:
從最後一個非葉子結點開始調整,一直到根節點為止,將每個結點及其子樹調整到滿足小堆的性質即可。
程式碼如下:
void AdjustDown(DataType* a, size_t n, int root) //向下調整
{
int parent = root;
int child = parent*2 + 1;
while (child<(int)n)
{
if(a[child]>a[child+1] && child+1 <(int)n)
++child;
if (a[child]<a[parent])
Swap(&a[child],&a[parent]);
else
break;
parent = child;
child = parent*2 + 1;
}
}
void MakeSmallHeap(DataType* a, size_t n) //構建小堆
{
int i = (n-2)>>1;
for (; i >= 0; --i)
{
AdjustDown(a,n,i);
}
}
大堆與小堆原理相同,程式碼相似,此處不再贅述。
②堆的插入和刪除
插入
其實在一個堆中是可以在任意位置插入和刪除結點的,為了高效起見我們在插入一個結點時我們將該結點尾插到儲存堆結構的順序表中,如果我們插入的結點比原來的大堆中的所有資料都大的話我們就破壞了原來的大頂堆的結構了,此時我們就需要調整新堆的,在這裡用的是向上調整的演算法.
插入資料的時間複雜度為O(lgn).
向上調整程式碼:
void AdjustUp(DataType* a,int child) //向上調整
{
int parent = (child-1)>>1;
while (child >0)
{
if (a[parent] > a[child] && parent >= 0)
Swap(&a[child],&a[parent]);
else
break;
child = parent;
parent = (child-1)>>1;
}
}
刪除
1).將最後一個結點的資料域與堆頂的元素交換.
2).刪除最後一個結點,此時刪除的就是原來的堆頂元素
3).向下調整刪除之後的堆,使其繼續滿足大頂堆的定義.
刪除資料的時間複雜度為O(lgn).
插入和刪除的演算法會在堆的應用中寫道,此處不再贅述。
堆的應用
①優先順序佇列
我們知道佇列的特性是先進先出,那什麼是優先順序佇列呢?在某一情況下佇列的先進先出並不能滿足我們的需求,我們需要優先順序高的先出佇列,這就類似VIP之類的.
下面給出實現優先順序佇列的兩種思路:
想法一:
Push:在需求的優先順序的位置插入資料,時間複雜度為O(n).
Pop:直接從隊頭刪除資料,時間複雜度為O(1).
想法二:
Push:直接插在隊尾,時間複雜度為O(1).
Pop:找到優先順序最高的元素刪除,時間複雜度為O(n).
在實際應用中第一種想法是優於第二種想法的,但是其實還有一種更加高效的方法,那就是用堆實現優先順序佇列
函式程式碼:
void PriorityQueuePush(PriorityQueue* q, DataType x)
{
assert(q);
if (q->_size == N)
return;
q->_a[q->_size] = x;
q->_size++;
AdjustUp(q->_a,q->_size-1);
}
void PriorityQueuePop(PriorityQueue* q)
{
assert(q);
if (q->_size == 0)
return;
q->_a[0] = q->_a[q->_size-1];
q->_size--;
AdjustDown(q->_a,q->_size,0);
}
DataType PriorityQueueTop(PriorityQueue* q)
{
if (PriorityQueueEmpty(q))
return q->_a[0];
}
size_t PriorityQueueSize(PriorityQueue* q)
{
assert(q);
return q->_size;
}
size_t PriorityQueueEmpty(PriorityQueue* q)
{
assert(q);
if (q->_size > 0)
return 1;
else
return 0;
}
標頭檔案和測試程式碼在結尾給出。
②topk問題(構建相反堆找出前k個數) 在大規模資料處理中,經常會遇到的一類問題:在海量資料中找出出現頻率最好的前k個數,或者從海量資料中找出最大的前k個數,這類問題通常被稱為top K問題。例如,在搜尋引擎中,統計搜尋最熱門的10個查詢詞;在歌曲庫中統計下載最高的前10首歌等。
維護一個K個資料的小頂堆,遍歷元素,若元素大於堆頂元素,則將堆頂元素移除,當前元素插入堆頂,並進行調整。
程式碼實現
void TopK(DataType* a, size_t n, size_t k) //topk問題
{
size_t i = k;
MakeSmallHeap(a,k); //構建小堆
for (i=k; i<n; i++) //遍歷剩下的數
{
if (a[i]>a[0])
{
a[0] = a[i];
AdjustDown(a,k,0);//向下調整
}
}
for (i=0; i<k; i++)
{
printf("%d ",a[i]);
}
printf("\n");
}
標頭檔案和測試程式碼在結尾給出。
③堆排序(升序 — 構建大堆 降序 — 構建小堆)
堆排序:先建立一個最大堆。然後將最大堆的a[0]與a[n]交換,然後從堆中去掉這個節點n,通過減少n的值來實現。剩餘的節點中,新的根節點可能違背了最大堆的性質,因此需要呼叫向下調整函式來維護最大堆。
函式程式碼:
void HeapSort(DataType* a, size_t n) //堆排序
{
MakeBigHeap(a,n); //構建大堆
while (n>0)
{
Swap(&a[0],&a[n-1]);
n--;
AdjustDown(a,n,0);
}
}
標頭檔案和測試程式碼在結尾給出。
Head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include<string.h>
typedef int DataType;
//構建大小堆
void AdjustDown(DataType* a, size_t n, int root);
void MakeBigHeap(DataType* a, size_t n);
void MakeSmallHeap(DataType* a, size_t n);
void AdjustUp(DataType* a,int child);
// topk 最大的前K
void TopK(DataType* a, size_t n, size_t k);
//優先順序佇列問題
#define N 1000
typedef struct PriorityQueue
{
DataType _a[N];
size_t _size;
}PriorityQueue;
void PriorityQueueInit(PriorityQueue* q); //初始化
void PriorityQueuePush(PriorityQueue* q, DataType x); //入隊
void PriorityQueuePop(PriorityQueue* q); //出隊
DataType PriorityQueueTop(PriorityQueue* q);
size_t PriorityQueueSize(PriorityQueue* q);
size_t PriorityQueueEmpty(PriorityQueue* q);
void HeapSort(DataType* a, size_t n); //堆排序
#endif //__HEAD_H__
Head.c
#include "Heap.h"
static void Swap(int *child,int *parent) //交換函式
{
int tmp = *child;
*child = *parent;
*parent = tmp;
}
void AdjustDown(DataType* a, size_t n, int root) //向下調整
{
int parent = root;
int child = parent*2 + 1;
while (child<(int)n)
{
if(a[child]<a[child+1] && child+1 <(int)n)
++child;
if (a[child]>a[parent])
Swap(&a[child],&a[parent]);
else
break;
parent = child;
child = parent*2 + 1;
}
}
void MakeBigHeap(DataType* a, size_t n) //構建大堆
{
int i = (n-2)>>1;
for (; i >= 0; --i)
{
AdjustDown(a,n,i);
}
}
void MakeSmallHeap(DataType* a, size_t n) //構建小堆
{
int i = (n-2)>>1;
for (; i >= 0; --i)
{
AdjustDown(a,n,i);
}
}
void AdjustUp(DataType* a,int child) //向上調整
{
int parent = (child-1)>>1;
while (child >0)
{
if (a[parent] > a[child] && parent >= 0)
Swap(&a[child],&a[parent]);
else
break;
child = parent;
parent = (child-1)>>1;
}
}
void TopK(DataType* a, size_t n, size_t k) //topk問題
{
size_t i = k;
MakeSmallHeap(a,k);
for (i=k; i<n; i++)
{
if (a[i]>a[0])
{
a[0] = a[i];
AdjustDown(a,k,0);
}
}
for (i=0; i<k; i++)
{
printf("%d ",a[i]);
}
printf("\n");
}
void PriorityQueueInit(PriorityQueue* q)
{
assert(q);
memset(q->_a,0,sizeof(DataType)*N);
q->_size = 0;
}
void PriorityQueuePush(PriorityQueue* q, DataType x)
{
assert(q);
if (q->_size == N)
return;
q->_a[q->_size] = x;
q->_size++;
AdjustUp(q->_a,q->_size-1);
}
void PriorityQueuePop(PriorityQueue* q)
{
assert(q);
if (q->_size == 0)
return;
q->_a[0] = q->_a[q->_size-1];
q->_size--;
AdjustDown(q->_a,q->_size,0);
}
DataType PriorityQueueTop(PriorityQueue* q)
{
if (PriorityQueueEmpty(q))
return q->_a[0];
}
size_t PriorityQueueSize(PriorityQueue* q)
{
assert(q);
return q->_size;
}
size_t PriorityQueueEmpty(PriorityQueue* q)
{
assert(q);
if (q->_size > 0)
return 1;
else
return 0;
}
void HeapSort(DataType* a, size_t n) //堆排序
{
MakeBigHeap(a,n);
while (n>0)
{
Swap(&a[0],&a[n-1]);
n--;
AdjustDown(a,n,0);
}
}
Test.c
#include "Heap.h"
void Test1()
{
int i = 0;
DataType a[] = {16, 18, 15, 17, 14, 19,10,11, 13, 12};
MakeSmallHeap(a, sizeof(a)/sizeof(DataType));
MakeBigHeap(a, sizeof(a)/sizeof(DataType));
DataType NArray[1000];
srand((int)time(0));
for (i = 0; i < 1000; ++i)
{
NArray[i] = rand()%10000;
}
NArray[30] = 10001;
NArray[350] = 10002;
NArray[999] = 10003;
NArray[158] = 10004;
NArray[334] = 10005;
TopK(NArray, 1000, 5);
HeapSort(a,sizeof(a)/sizeof(DataType));
}
void TestPriorityQueue()
{
PriorityQueue q;
PriorityQueueInit(&q);
PriorityQueuePush(&q, 5);
PriorityQueuePush(&q, 2);
PriorityQueuePush(&q, 3);
PriorityQueuePush(&q, 7);
PriorityQueuePush(&q, 6);
PriorityQueuePush(&q, 1);
PriorityQueuePush(&q, 4);
while (PriorityQueueEmpty(&q) != 0)
{
printf("%d ", PriorityQueueTop(&q));
PriorityQueuePop(&q);
}
printf("\n");
}
int main()
{
Test1();
TestPriorityQueue();
return 0;
}
topk問題測試時要巧妙構建測試案例。
相關文章
- 堆疊的應用——用JavaScript描述資料結構JavaScript資料結構
- 【資料結構】堆的建立以及其他操作!!!資料結構
- 前端資料結構(1)之棧及其應用前端資料結構
- 前端資料結構(3)之連結串列及其應用前端資料結構
- 前端資料結構(2)之佇列及其應用前端資料結構佇列
- 《資料結構》實驗08--樹及其應用資料結構
- 資料結構-堆資料結構
- 資料結構——堆資料結構
- 資料結構 - 堆資料結構
- [資料結構]堆資料結構
- 資料結構 - 堆(Heap)資料結構
- 資料結構之堆(Heap)資料結構
- C++ 資料結構-堆C++資料結構
- 資料結構與演算法--簡單棧實現及其應用資料結構演算法
- 資料結構之堆(c++)資料結構C++
- 資料結構之索引堆(IndexHeap)資料結構索引Index
- 資料結構和演算法-堆資料結構演算法
- 演算法(4)資料結構:堆演算法資料結構
- 資料結構與演算法-堆資料結構演算法
- 高階資料結構-可並堆資料結構
- 使用C#實現資料結構堆C#資料結構
- 資料結構-二叉樹、堆、圖資料結構二叉樹
- 資料結構 - 概述及其術語資料結構
- 【JVM】堆體系結構及其記憶體調優JVM記憶體
- 資料結構筆記-棧的應用資料結構筆記
- 資料結構實驗:連結串列的應用資料結構
- 高階資料結構---堆樹和堆排序資料結構排序
- 資料結構&堆&heap&priority_queue&實現資料結構
- 【資料結構】回顧優先佇列(堆)資料結構佇列
- C#資料結構篇(二 堆疊) (轉)C#資料結構
- 資料結構與演算法——常用高階資料結構及其Java實現資料結構演算法Java
- 資料結構 9 基礎資料結構 二叉堆 瞭解二叉堆的元素插入、刪除、構建二叉堆的程式碼方式資料結構
- 【資料結構】用C語言實現單連結串列及其常見操作資料結構C語言
- Redis的資料結構與應用場景Redis資料結構
- Redis的資料結構及應用場景Redis資料結構
- 併發應用中不可變資料結構資料結構
- 資料結構之堆:初學只需一文資料結構
- 資料結構之堆 → 不要侷限於堆排序資料結構排序