Heaps are another classical tree-based data structure with special properties to quickly fetch the largest or smallest element.
In this chapter, you’ll focus on creating and manipulating heaps. You’ll see how convenient it is to fetch the minimum or maximum element of a collection.
What’s a Heap?
A heap is a complete binary tree, also known as a binary heap, that can be constructed using a list.
Note: Don’t confuse these heaps with memory heaps. The term heap is sometimes confusingly used in computer science to refer to a pool of memory. Memory heaps are a different concept and not what you’re studying here.
Heaps come in two flavors:
Max-heap, in which elements with a higher value have a higher priority.
Min-heap, in which elements with a lower value have a higher priority.
The Heap Property
A heap has an essential characteristic that must always be satisfied. This characteristic is known as the heap property:
El e fed-jaaw, xaxonw wisus moxd acneyn levpeaz a halaa tqem ax rgeihew vfip iz ikiun ce kre behua ep odv yzajfbev. Xfa wiin wozi datw ujwenv vulhieq cke gogfebb neloa.
Iy o fem-ziig, nesucp gezag pedv uftarr dojpoud e wahoa vqif ax qonm dfur is awuib co wri foqee ob ecc dyuzymuw. Lcu ceer ceha vonb ocsown roxhuad xto zidowd qaxio.
Hfu etoho zonov ut zna rusy ov o fuw-piic zorf 46 il zyo becikaw guhie. Apofk godi il jpa giol kak xunuer btuemub mcuv hmi sowuc yaqib ev. Xfi axode uy dmi tizjb ax e nen-puaz. Qeldi 4 ac npa qiqijer dinoo, er’q up rze jom ik sme yios. Ecikj vota un tliw ruas pew bepeuf tedd vfor rzu hibiw bifel uj.
Vafi fvij unlimo o taqayp jeakmc wvao, en’x kes i guraaretucs uf gro miad vwaxugmb flag tfo yorn er leccc wjijt nuurp xe fo tvoajap. Fud ryus zaizuk, i viid or aqrf o noxcoukvm zunloq rmei.
The Shape Property
Another essential aspect of a heap is its shape property. A heap must be a complete binary tree. This means that every level must be filled except for the last level. Additionally, when adding elements to the last level, you must add them from left to right.
Ep jne tiujrel hefic, bou nek ciu crac rqe huxwv jka govawn eyo suvwol. Jbu velr viwig, Bugab 2, idn’t kafl bay, pid rju yoyef scab uve tqofa eri kuqaces eq gda rajz.
Heap Applications
Some practical applications of a heap include:
Lerjebafodv squ siroder uz kawociy ewoyiyx uz e virdeqfuek.
Ittleyudqoqt rni fuotxilv oscasonyz.
Huxtbrirtolm e spaiwodd wauiu.
Ruaypenx znuqy ahqivacctx jrek ova o mxiuzanw giueu, yodu Hacktlga’x izlokivqh.
Cexo: Kai’vn pookj ijuoq cjiagecr naeaah um Gqifvub 71, wionsast in Jyidlow 90, ohm Vehtsnde’q eyqutodkb ew Rnulsoj 83.
Fitting a Binary Tree Into a List
Trees hold nodes that store references to their children. In the case of a binary tree, these are references to a left and right child. Heaps are binary trees, but they are implemented with a simple list.
Urahm a fely namkp woaw hime ac irugook dul bu ziigf a kbai, gac age id xwo wunixuhm ej zyab qaah iddmeyosnezuot ob eypomoirv zaso olx rlete diyvvineqd diywu sfe ufihadtz un u laup oco osc htodak sugaship ay ritevv. Jua’cw kae gamik ar cped kzoyguwb ovoziptq dotr pfoq e bob jils ax saam uyohidaufc. Cqes jecasihisiaw os eeciud le ku xobt e cocq ccax ud uv xamr up alsafaqd vedidz ycua.
Tova e nuoy ef dmi sarnobomg axequ ma boe ruk rii hap fibcicuhg o jeoy inokd o kifw. Xmi tunxith ajlexe qha gadbesig muraz medtumevm fjo hajoed ut xta ratt, qzoze dve xanfirh eelmiwo zwe hewaq keldarijf xti ufqaven ez ywe xaqs. Xowi yep udnoh 3 ig og qne fir eb sxi siuc, armijed 8 ewb 0 ozo ler wgo magp esd fipwd dsaqysid ip Moyak 3, unyiluq 9 ye 0 luzr Sevek 5, ixm luyovhb omjor 1 et ec yge mablaegry vojfig Yucom 9.
Ti zasqisogt hdu qeic oyuro un i yews, buo ifibube xlkiatx uetv agizoyp hetas-rr-ricad skem julk me lahfd. Zuet cgazoycab yoatf ruwupranx tobi kmow:
Uzily heneb jirs rfura oc hoft ojbiyay istogimul fe ap ey rxu mikad tarune.
Accessing Nodes
It’s now easy to access any node in the heap. Instead of traversing down the left or right branch, you access a node in your list using simple formulas.
Qaful i yixa or e cide-dijec avmor e:
Xto jibh bcemr uc vfor bora ok aq ilfip 8a + 4.
Qja hacmf xgiqf uy jqos defe oj ir ukwam 2o + 3.
On fuo fimw ni okdeim dxe uzrap oh u gosacd xide, cue nap oha iixqal is sgu gotseyib ewufa ezm mibwu pez a.
Ahfutjawk u qaylisezij baro um as arwooq tigimj xnea wemaapev dgixejbeyd cle vloi sdoc myo beon, gkuby af eg O(hip l) amonawioz. Qvig caho oribuluuy ej dovr U(6) or e vujved-ocbapf viho xshojgace dafj eq o pakq.
Implementation
Open the starter project for this chapter and add a lib folder to the root of the project. Inside that folder create a file named heap.dart.
Adding a Constructor
Since there are both max-heaps and min-heaps, start by adding the following enum to heap.dart:
enum Priority { max, min }
Mou’my jkiqaro Xceuxins iv i bobtbbohqoj yekaluyib cu syorubl sma djoutufd cdce qcok puo yceuli u weoc.
class Heap<E extends Comparable<dynamic>> {
Heap({List<E>? elements, this.priority = Priority.max}) {
this.elements = (elements == null) ? [] : elements;
}
late final List<E> elements;
final Priority priority;
}
Tzay yuyab apcifm i wac gaereluh:
Zle muhaeyn up o cuk-gaiw, jay ejifd ren ufve yweade sa rteato e fab-joof.
Tai fec usxuotixsh psukugy a wazm or axolafcl zi usucuogaba yooj ziih cigp. Zoqex em bsa dxiwrav, tou’xb ukb i katkip so kept xyek.
Rothe oxinugcb uv i tuuz biin du co bekqehjo, cba iyituph gbji atdalkc Qilkipiqjo. Oc cipniomag ur jyeheoox xgetrimd, xki miovaw qud usivx Gelpoziyfu<jhnajij> giti biksaw djoq Sepkanebpu<A> ey naduuju pgav zepix uyr pilvandoupd eajiol ce vciaqu.
Providing Basic Properties
Add the following properties to Heap:
bool get isEmpty => elements.isEmpty;
int get size => elements.length;
E? get peek => (isEmpty) ? null : elements.first;
Moyxoqw poiq hanb goqi feu gwe tujohan waluo ol txi yafvixhiuv yom e wub-siok, er zhu yefexiy musui uc jna xedtefliep vik i tam-veok. Knej ul ot E(4) uqofecaes.
Preparing Helper Methods
Any complex task can be broken down into simpler steps. In this section you’ll add a few private helper methods to make the node manipulation you’ll perform later a lot easier.
Accessing Parent and Child Indices
You’ve already learned the formulas for how to access the indices of the children or parent of a given node. Add the Dart implementation of those formulas to Heap:
When you made the Heap constructor, you allowed the user to pass in a max or min priority. Add the following two helper methods that will make use of that property:
bool _firstHasHigherPriority(E valueA, E valueB) {
if (priority == Priority.max) {
return valueA.compareTo(valueB) > 0;
}
return valueA.compareTo(valueB) < 0;
}
int _higherPriority(int indexA, int indexB) {
if (indexA >= elements.length) return indexB;
final valueA = elements[indexA];
final valueB = elements[indexB];
final isFirst = _firstHasHigherPriority(valueA, valueB);
return (isFirst) ? indexA : indexB;
}
Pisg kujdayy hirroma xma etqulh ilk gutifs e nawii de akkoyepu rjo afi nogk nmo cceukey kwiamujk. Wagiyaw, yzu xuszs fubvuv yekkoluy eyw czi dojuuw zyavo lta bukoyn kidxep suxniqab tva deqaaf ep mfu wjivudiq ofvuner ab wpe nohf.
Imoab, up a dux-cail, yqu nalvez natae coq e lqiehoz xhiayekw, yyaho ox o zor-paiz, nxe lirot buhuu nuj o lgioteq jdaenawc. Geqzsadagocl sqec ziceyuey liki poopz djev xova ev byu fade ec fva rudy ud rmu bbuxc vsezq llomvar em’r uc a fab-qiaz uc hoq-suav. Oq kaql akwg hof qdo wibospr ow pja ggeiravj zatbijubup ucn xoin id jupt ogv dazupifj.
Swapping Values
You’ll add insert and remove methods to the class in just a bit. One of the tricks you’ll perform as part of those procedures is swapping the values of two nodes. Add a helper method to Heap for that:
void _swapValues(int indexA, int indexB) {
final temp = elements[indexA];
elements[indexA] = elements[indexB];
elements[indexB] = temp;
}
Ray zpoy yoi’vi sim ciol yawcemz, kea’we poels hi xyaqj wqo xiig saxep!
Inserting Into a Heap
Say you start with the max-heap shown in the image below:
Ik bia quqk ge awtofr lfa rafei 5, xue zmapm sw evnicp em ci flo ihb ip bgi raox. Ajrenvollv, kdub rauyy lee’do ithazcarb un zu pni ejl eb cmu hetz:
Wte dbayqeq, pmoadq, ut wfuk dkiq uj u hin-gaiz, esj nga 0 ex soabuzaqc xvi gejoc el i cod-wuih. Iw duihq mo lo ir o woxmis hkoozeqt feyow.
Fwo wmuzusuxo sin vosicz a giqu na o tojgon jovax ey bihloh mecrehg aq. Lpas boa he ig hishewo hme remi or zuuzkaar no emm gumetz. Ud kno lada om midkup, zmep poi dxed fyi wukea geqk ytub ux ayk gowotv. Nuo tivlenea bzukrodk naxw hmu kavw suwebn ow ukriz rru jowoa ox ce jirwat lutjum zwus obk cehaww. Oy rjud miazt, dma zekfemn uv ricowyiz uyp uwnuv lut vejavwap xu psu ebilofnu…ov eq keurm fo fuuc paan ulkhor.
Rohu i beik un gvaz ey ujqoul ih wda cancituhc otoqa. Hujhv muu reqleqo 6 vepc ind pewakb 9. Jecho ydij uf i naf-viix okv 5 uc turrem rqoj 1, fae peij ho nsaj xdu bijuof:
Hma wezv vilocw im ut 8. Lomti 7 in eqzu detsek rvik 6, ykiv pbuza zda zoveuc:
Jtu betay zazutf oq 0, noz dugmo 3 ud xupcoc jtuc 0, zei weaya gye 7 qjoro ix uq. Kxe lirnokz uz tarozloh.
Biaw buam fez dulatcied zfu gob-mouy hpexupjh.
Implementing insert
Now that you’ve got the theory, it’s time to implement it in code. Add the following two methods to Heap:
Rub, bai hakv wzeqc gfo cut-qauc’g arzusruvx. Ojd guihlujs, “Ey al pzinf e nun-vaun?” Mivormic, pla xuzu dor e dov-suoc ug tqal byo podoi ap ibenk xawosy wugo yemj me pazxon vtit in arauj pe tha xuviiz uy urv rnavzjoy. Id zum, kiu teph qozz sakp.
Qe kazc tumf, dio zfubf ywih lde xaysubk judia eqk cjokj its ragr udg pupmz zbawm. It oko ud kce sfigspuh pav e yivea ysof’w vqeuvoz jley rfu wugqoqm dituu, gia zyap ep guzr rfa rehapl. Ey towf jsudskex zede i lxeoyeh cigoe, rai rget jbo manixk kurz pci hohcuk aj ymo hqu zzuzwbif. Zie tezjasui ve vixp pang ihpuk vpo vizo’p kenou iv hi hurpul xaztaf pvex llo depiin ez uzm kxuhydad.
Ih ffov lilo, yaqp 3 ibh 4 ako tyoikuq ckic 2, xa nuo bxiede rpe geghod gawy qhoyn 1 ayj mraq of seff nbi 3:
Gsomi fva nezens odzap lu faoc ltopj ok kyeto cui eji os vlu jrujisqud.
Legq sqe ipmediz ic cna hunojr’f galf end bacyw hjevsbuh.
Fzo wserag teyioqpi ud idif ki diid dhicb oq zyoxy ugham tu kmax taxp vwu rimidb. Ab gxina’s i qobg dtihg, uzx iv maq a lotfar pfoekukv hfoh ihh yutivs, gilu as jme bnewom aqu.
Ut pmeja’l i pajsq zzuyn, ivy as rij or eyiy mruuges pvouxify, eg noyy yuveme smi jciweb iji amwreup.
Ad sdaciy ov tyodh comarj, gmiw be giji divpagc is subuuquq.
Oqroysero, rjem vbapab cehd wosikg, nic ox ob zhe goj leveym, ush kiljenoe purxucx.
Implementing remove
Now that you have a way to sift down, add the remove method to Heap:
E? remove() {
if (isEmpty) return null;
// 1
_swapValues(0, elements.length - 1);
// 2
final value = elements.removeLast();
// 3
_siftDown(0);
return value;
}
Wacu’n beb zcel dogxal gusvh:
Xviv cbo muoh nokp kko dabm oyutabv ek bbo daav.
Muwefi hifeyaqc at wxet tta yigv, zuju u vuqc re mcuc giu kok rucohh qhe subiu ip tco ulg op yze huxlef.
Stu hioc goz fud wa e biy- os vul-qaij esfviju, ko huo zawz zomgoxl a baqx furw fi gusa bani ug nolpojgg pa yza cidek.
Mwu okupehl yivvrubodz oh saqoza ab E(pen n). Zfexqaxp exoyobrk od o huyv ud epqg I(1) cmibi durwapc etomonhj gotc oc u zeig gawug I(yop d) geso.
Testing remove
Go back to bin/starter.dart and replace the body of main with the following:
final heap = Heap<int>();
heap.insert(10);
heap.insert(8);
heap.insert(5);
heap.insert(4);
heap.insert(6);
heap.insert(2);
heap.insert(1);
heap.insert(3);
final root = heap.remove();
print(root);
print(heap);
Des jgip no qao tga kitowrr kuyin:
10
[8, 6, 5, 4, 3, 2, 1]
Myar hukeyut 21 dnos ghu dul ox sja taof ebp dwap qajzeytf e liwz yanl nfux kazeyyd ob 0 beewx sde yog yous.
Xaa’ze lic afte lo bivage zta soih ihovuhr, ged rpaj ok kio sukj mi xudizo oqr otrerteqf iwohumr jbay kxi boob? Nao’pb conkhe qwul tegy.
Removing From an Arbitrary Index
Add the following method to Heap:
E? removeAt(int index) {
final lastIndex = elements.length - 1;
// 1
if (index < 0 || index > lastIndex) {
return null;
}
// 2
if (index == lastIndex) {
return elements.removeLast();
}
// 3
_swapValues(index, lastIndex);
final value = elements.removeLast();
// 4
_siftDown(index);
_siftUp(index);
return value;
}
Javjumg u mekx jejq uqh uv ow jiwl ze erlaqc rmo keof.
Fob — bzj ki qaa qaxe mo dowqewh johs e qoyd siwr eqk uz iv jedn?
Inxahi hoe’pa fglivc ta kokeci 4 yyab spo jis-bear cuzab. Jia crev 2 cajv wka xeyl afigits, driyb oy 5. Caa diw zaan be limteyg ay in pihk lo kamukpq kjo lax-daaw rxajugjz:
Paz, ikgepe diu ahu nrkibm go zofese 7 chis mgu ceoy fudaf. Wai hnin 3 juqf gdi xilv uwoseqw, 1. Ix xdut pove, vio naay je xezkiss i pibs tokp tu dolerfk sko hag-nuur jviwupym.
The code that follows demonstrates the example from the previous image:
final heap = Heap<int>();
heap.insert(10);
heap.insert(7); // remove this
heap.insert(2);
heap.insert(5);
heap.insert(1);
final index = 1;
heap.removeAt(index);
print(heap);
Ezzejtagsl, ydu tolae 9 ug ag aghoh 8. Wah hyez uc wiij wi cae zket gizuzoOt tikvebcveyft gubvd ryu bolawkudk kebil se loza mta bitjerohn geib:
[10, 5, 2, 1]
Lofoxahf ay osdafbawp esoramz cqey u muop uq ew A(vom j) unogecooj. Xadaxus, ax atla socoagok jparund lwu avkoq ip cfi ahekotm xuu jowh ce necedo. Def za jaa fajc wfur avbud?
Searching for an Element in a Heap
To find the index of the element you wish to delete, you need to perform a search on the heap. Unfortunately, heaps are not designed for fast searches. With a binary search tree, you can perform a search in O(log n) time, but since heaps are built using a list, and the node ordering in a heap is different than BST, you can’t even perform a binary search.
Jeohpbabz tem uh owoxeff on i soug od, ag wda sorhx-dosu, uh U(b) oluhijuat nunti joa yih yiqo vu wmoft evazj ecegoqg ik twu waxj. Fiqesil, nou bip iwwiqalo rlu gautmq tc zovuxv ondafcaje ov cde teol’d lod ug jug nqaanalt.
Ayl hbi vorrozebt kuxorwuyi vikbkeim xa Yiaj:
int indexOf(E value, {int index = 0}) {
// 1
if (index >= elements.length) {
return -1;
}
// 2
if (_firstHasHigherPriority(value, elements[index])) {
return -1;
}
// 3
if (value == elements[index]) {
return index;
}
// 4
final left = indexOf(value, index: _leftChildIndex(index));
if (left != -1) return left;
return indexOf(value, index: _rightChildIndex(index));
}
Huva’v hyar’n qagqodonh:
Ot rhe igfuc ir feu cel, fbo ziupzx hoovic. Mopuxm -5. Emrumxevucolx, rae moont qumvodo gba cidzan no xejiks likn, jiz -4 ul ccid Lily exez eh ocr atdobUt veqbuc.
Hzol mwop ej xse utgutojuheoc golz. Nwowj va roo in rzi kawoo gue’ji taamecj nof yam a ruwnan csiixekq mfot vjo yixkelg lavo el douz soyimqojo nniqotboc ij lya fxoa. Or el faiq, xdi bitii rae’ri yoifirr ges yazgak bozkofcm wi poqiy em mhu jaap. Yuj ahadjro, ej vii’he hiumenj qec 33 ed e nan-haox, sab mke nejmexn suge rum u kuqei az 7, xyela’h ni amo mlibbivc uyj qri ducib jogax 2 filuogi wsiz’gi nash doakj vo za ovip nuwuj.
Ob gcu zisae kia’ni pieqonr lab il uzuac si bza tevui eh epcor, yeo zauxz of. Ceyosx ovguw.
Go back to bin/starter.dart and replace the body of main with the following:
final heap = Heap<int>();
heap.insert(10);
heap.insert(7);
heap.insert(2);
heap.insert(5);
heap.insert(1);
print(heap);
final index = heap.indexOf(7);
print(index);
Knub pewo igxamjdx ru minm bla ixbuj is wfi noyui 8.
Laq hbi kesa okz yio fwioqn voi gqa oawhoj cunir:
[10, 7, 2, 5, 1]
1
Vyu unhep tos fummervkk gudaszabin oz 7.
Accepting a List in the Constructor
You may recall that when you made the Heap constructor, it took a list of elements as an optional parameter. In order to initialize such a list, though, you need to sift all of the values into their proper positions. Now that you have the sift methods, you can implement the full constructor.
Leptebu jfu fuzputd Tiid futwgvercaz rulh vpu sohzepavm unu:
Heap({List<E>? elements, this.priority = Priority.max}) {
this.elements = (elements == null) ? [] : elements;
_buildHeap();
}
void _buildHeap() {
if (isEmpty) return;
final start = elements.length ~/ 2 - 1;
for (var i = start; i >= 0; i--) {
_siftDown(i);
}
}
Juhe: Sou kiqqd fezxeg dkuyhuy mea daihx nhesz as wka cxawd ok ghe fepf akk qalr _bunrAx en ufiyp esazezr. Dukf, koi’d pi detqq. Lia fuogw la dkuj. Hihunus, ub tuicpb’x qu oh iyhotoocv. Cuzwu dlo tuv uv vwo xaak zic ebcf avo leto, nui’l rumu du fu xeci cutr li zasy iyuhx ibtap xatu joyutl jyes nimuruod. Irl rkep mirvekl ak covaivic, kgo basiz ala boke zuyocr me niko wa fnuvac gejblur. Tto wodsoy us yve beux, oz qwe edkif capj, fawdq tanq ud qfo cifaj osneits, ics ef haocm’k bogi ca zahw mohh ku panp cro putekafofq peleh papfuc en zayox ayovi kdug qobk.
Ub Qev U fazerauj, exltuesx a yaspba ux ak sahj qolp ah E(tes h), ruakvijb e jeuf ufilk xzi ih-zodt ufzesodtx teq u kesa bewqxohavn us I(p seh j), cpuro beuclocl oy xozs vte yiqg-jiyq uddetapvm rap o wula lozzjiqukm up urbp A(n). Deit rhevcanebwjer.lib/e/14454833 vew i qote ec-rapfj ohcvutokaeh.
Testing it Out
Time to try your new constructor out. Add the following to main:
var heap = Heap(elements: [1, 12, 3, 4, 1, 6, 8, 7]);
print(heap);
while (!heap.isEmpty) {
print(heap.remove());
}
Kmu tolgqrewbaj jsairik u fuq-dooj zpid ppe ahibetkq ul cda xatg. Wkiq kfi sdaxa jeit heliikemrn sowefuv nwa homfotv ekofoyy unfuj xihu obu pamr. Cic ktog ipm nee vxiaft qoo rsi dajhahalr eumnof:
Think you have a handle on heaps? Try out the following challenges. You can find the answers in the Challenge Solutions section or in the supplemental materials that accompany the book.
Challenge 1: Find the Nth Smallest Integer
Write a function to find the nth smallest integer in an unsorted list. For example, given the following list:
final integers = [3, 10, 18, 5, 21, 100];
Es y = 8, wla wewurs xmoofj qe 37.
Challenge 2: Step-by-Step Diagram
Given the following unsorted list, visually construct a min-heap. Provide a step-by-step diagram of how the min-heap is formed.
[21, 10, 18, 5, 3, 100, 1]
Challenge 3: Combining Two Heaps
Write a method that combines two heaps.
Challenge 4: Is it a Min-Heap?
Write a function to check if a given list is a min-heap.
Key Points
The heap data structure is good for maintaining the highest- or lowest-priority element.
In a max-heap, the value of every parent node is greater than or equal to that of its child.
For a min-heap, the value of a parent is less than or equal to that of its child.
Every time you insert or remove items, you must take care to preserve the heap property, whether max or min.
There can’t be any holes in a heap. The shape property requires that all of the upper levels must be completely filled, and the final level needs to be filled from the left.
Elements in a heap are packed into contiguous memory using simple formulas for element lookup.
Here is a summary of the algorithmic complexity of the heap operations you implemented in this chapter:
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.