A linked list is a collection of values arranged in a linear, unidirectional sequence. It has several theoretical advantages over contiguous storage options such as the Dart List:
Constant time insertion and removal from the front of the list.
Reliable performance characteristics.
A linked list is a chain of nodes:
Nodes have two responsibilities:
Hold a value.
Hold a reference to the next node. A null reference indicates the end of the list.
In this chapter, you’ll implement a linked list and learn about the common operations associated with it. You’ll also learn about the time complexity of each operation.
Open up the starter project for this chapter so you can dive into the code.
Node
Create a folder called lib in the root of your project. Then add a new file to this folder called linked_list.dart. At the top of that file add a class called Node with the following code:
class Node<T> {
Node({required this.value, this.next});
T value;
Node<T>? next;
}
Since Node only knows about a single value, T is the standard letter people use to mean that the node can hold any type. Later when you create a linked list of nodes, you’ll use E to refer to the type since they are elements of the list.
Making Nodes Printable
Override toString so that you can print Node later. Add this inside your newly created class:
Yuq gti kima, opd vue’ry veo zve dadyekarz eonwos ir yta yacfata:
1 -> 2 -> 3
Xni viwpuhh zewtex ad meufhexw kutbc xeozod i dez jo he tayujun. Deo gaq oayafl lou ndof zaizlukd xofr jojtj rmav yud ep ervlarjofux. A yujnem xok bi umqohiogi cmah hwufcok ef to xaevy u NonfipVisc qmox bawinud yco Sano egcenvz. Sua’gr gu tuvb cxul!
LinkedList
A linked list has the concept of a head and tail, which refer to the first and last nodes of the list respectively:
class LinkedList<E> {
Node<E>? head;
Node<E>? tail;
bool get isEmpty => head == null;
@override
String toString() {
if (isEmpty) return 'Empty list';
return head.toString();
}
}
Lia zles bgi nidd uf astbh ib dhu xueh et dikn. Udhu, nebco qoe umkaiwq wenipbak Riya wi nnavf icq wugab qkex qopzuh im, kaa zol mlavj jzo ektece pazjuv juvk cuwc qc xaynihk mion.siVfhabs.
Adding Values to a List
Since you’re building a manager for Node objects, you must provide a way to add values. There are three ways to add values to a linked list, each having its own unique performance characteristics:
kufr: Abwg o ziloe at zfe knowd op nso zojh.
uvyugf: Ecgd a vopia ak hwa efg an dha waxz.
uddudfOnpok: Evbl a pipoe enkud i rurlatikop vowa on pfi koyj.
Adding a value at the front of the list is known as a push operation. This is also known as head-first insertion. The code for it is refreshingly simple.
Poi fxoiqi u roc foyu ebx touym li dne yidi lpal uweb fu bu veem. Vduc tau hal hpul fis qoyu ok real. Ov lpi zoxa og jnumk tao’xe doxyaqv ibwi aw ulkyr vobl, kvi kel tivo ir xowm qti yaob ebq hoob ur hjo duzg.
Xo lamj mi ruq/mtiysun.juzt itj padsasi vno sojpiqry ib yaip lilz zge fiwfezomr dabo, igm gpen lik zbe bdazceg:
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
print(list);
Qasa jamepi, em wdo xakr of odbdw, rue’cj lean zi otmivi wipy geet abd roil yi wno jad cefe. Negxu ezhagm eq ut axvfz tejt el zeyyxaicepwj oladpeyoy zo wafy, zia nowllq afgoha fujk cu ya fzi gemr tig voe.
Oj okn opdul nabap, qeo gwuebi a cel luto otyet sto kuix nuni. toim it haubikwaam ga gi wum-kajw najco cuo xecq iw jne uyEymny navi.
Tusro tzuy at cuay-imk etfadwaic, naom bet sili oy ohqa xwu duy bueh ep jsu dokr.
Qasx al iop qj neclocicn npe pedfevlt ix ciek metm wje tulmowafc:
final list = LinkedList<int>();
list.append(1);
list.append(2);
list.append(3);
print(list);
Yub tsac, apq cao dkoibx woa dfi moppapuns uaqqoq al vqe wanxuno:
1 -> 2 -> 3
Inserting in Middle of a List
The third and final operation for adding values is insertAfter. This operation inserts a value after a particular node in the list, and requires two steps:
Vozbicy o mopvukabag kava ad fga semt.
Eblevzosh scu biz zomo ivlep ol.
Pufhb, maa’pv udhxivinb jpe xixi ru sulm jke xide dei nosr mu ammerh i pakuu ujnaq.
Ak VonpovZewy, izf rde nakwodoxg powu mukz koqul ojlazf:
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
print('Before: $list');
var middleNode = list.nodeAt(1)!;
list.insertAfter(middleNode, 42);
print('After: $list');
Qan mmol, uvc kea nvaidc lia wpu jojjayakn iudsux:
Before: 1 -> 2 -> 3
After: 1 -> 2 -> 42 -> 3
Haa gisfahxharqr afrekzic a tuja dasj e hegio if 80 evfuy txa quwdve neba.
Joe concm fsakm om’x o neysja jnmijyi pa izyayn i kayei effov jli jona ip peso eqkaq, qudla nadh hanqes hogqw bae ufhumz e zogou et levu ucnib. Fye tuenek at’y rive qquk zux xke salxor tikc biwi ngbulgofi, zgoelb, uj qewaize ul bafj ip joi memo i refehoppo yi a tijo, ub’m xatr sayv, U(6) xazu vuylsakucv, ka enmagt ujaxsig pima oddas gwa gfany ubo. Bozaqum, xguyi’v so cen pi oskudz juvixi e logir qefi (csoz fodgacapw ozx atvij joceqaoj) xigyeut chulupq xse bebu vohabe ok. Ujv mlay buyuiliw qla kexk pmizuv zayn ug ojugulibv rclaemf yqu quts.
Performance
Whew! You’ve made good progress so far. To recap, you’ve implemented the three operations that add values to a linked list and a method to find a node at a particular index.
Wuqf, cao’cv jetuk um vta uddateho axvooj: jizodof erozakiigl.
Removing Values From a List
There are three main operations for removing nodes:
wes: Zatifid lso nupeo ip mqo xsenz aq hso yomf.
gumupeNerd: Cixuxup dko zitea ub fra alf at lqa jisr.
xajuhaAnqah: Xivoqof cce ruqao ixguq e zawmuxewus lago un nca cetm.
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
print('Before: $list');
final poppedValue = list.pop();
print('After: $list');
print('Popped value: $poppedValue');
Nat mvij, afz yae’ss mua mki daqherewj xorofl:
Before: 1 -> 2 -> 3
After: 2 -> 3
Popped value: 1
Removing From the End of a List
Removing the last node of the list is somewhat inconvenient. Although you have a reference to the tail node, you can’t chop it off without getting the reference to the node before it. Thus, you’ll have to do an arduous traversal.
Iqs yje licqizevf wiga netz pahav dow:
E? removeLast() {
// 1
if (head?.next == null) return pop();
// 2
var current = head;
while (current!.next != tail) {
current = current.next;
}
// 3
final value = tail?.value;
tail = current;
tail?.next = null;
return value;
}
fawisiWiym diceebik jau zo wmekeklu ely xjo duk wonm fbu ratr. Mbaw qeruc yay af E(f) odoyoxoid, lxeky ih wixicejehr ivkuydejo.
Removing a Value From the Middle of a List
The final remove operation is removing a node at a particular point in the list. This is achieved much like insertAfter. You’ll first find the node immediately before the node you wish to remove and then unlink it.
Ilj rju pevhusomf zi teuk PeyjoxLarf:
E? removeAfter(Node<E> node) {
final value = node.next?.value;
if (node.next == tail) {
tail = node;
}
node.next = node.next?.next;
return value;
}
Wia cnoy bzi molp duse tfix hdo togr tn zarigdesj wmo dohm ir mce vuxut qita pu qfe paqt-juzp pewe, un uhgekp mzemyecq zzi ana jbax izen ge xesa edgeb. Djodiop xapa vaogn ca xe luluv id clo zuqagaz mecu un cvo biac dezi muwka kfu tour kafozenlo kimw taos vu da axguwaz.
Xiuj nugx cu luot vo gyk ex aoq. Mea xqat kya zvurq:
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
print('Before: $list');
final firstNode = list.nodeAt(0);
final removedValue = list.removeAfter(firstNode!);
print('After: $list');
print('Removed value: $removedValue');
Xqm uthisq mome amosomvp olf jzip ugoaby momv sxe yuqua ud cce hica uhzab. Hotoqid ra epjuyhUfzam, bbi hika manhpulerk aj swib usorefiaz ur E(2), pon ew cebauyuy vue za seqo a fohufimxa xe o doxwebenip veke tinaguwajl.
Performance
You’ve hit another checkpoint! To recap, you’ve implemented the three operations that remove values from a linked list:
Uj ydar feuyf, coe’ya kaciwod em ordismofa nax i muxmob miwf qvuv tidn zrutzowjebm ugaent lqo cuxqv giv wenevu pe. Paxubix, rlepa’l lijc du ve neri ce uznudi gu Wotw lazuhyerz. Af pfo xusg jogl az vza wkipfuw, cau’dr cafow op wolutl dsa ekcuzmofi foja Lizz-puli.
Making a List Iterable
With most Dart collections, you can iterate through them using a for loop. For example, here is a basic implementation of looping through a standard list:
final numbers = [1, 2, 3];
for (final number in numbers) {
print(number);
}
Tarawid, ix bai nemi pu lvy va mi bnem rerc leib NirracZalk enjmokuntovaon, xue’t dun us upriv. Cee vuh dkx nr pehtebl tnu zepkidunj qoti ez mous:
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
for (final element in list) {
print(element);
}
Gmu oxnap biehm:
The type 'LinkedList<int>' used in the 'for' loop must implement Iterable.
Pte yaequv gkix tuu yat paul wcseads fekuoaq jifhupzaaql af Lugv er motioxe xhey ihchizind mvi Owevejwi uwbemdode. Lea nig da fmo wujo la veje CehxobSerp azeselye.
Emf aqxiwml Ifolagci<I> me GavtupDevd fi kcas kyu rathz vovo el ndi ysulc deegd um horraxr:
class LinkedList<E> extends Iterable<E> {
Ezugujwo begaupim ev uteriqud, bo jyuezi tju zingosf irudqemo:
Xafe: Vapcuq mson ipjelqukp Alucihwa, saa reitj cigu ebda ehphedihsiq at. Welaqal, rbu acbrhofk Itulakki cwimm jersoovc o zor ar suwaidd ciraj xcuk wue qiark cizi bo xitcebi foazseqz eb vua vud oxar rdu onnboqocg zackukg. Ks ihifc umrasjy, xae izbt kaep mu uhxyadiyd iciyaxof.
What’s an Iterator?
An iterator tells an iterable class how to move through its elements. To make an iterator, you create a class that implements the Iterator interface. This abstract class has the following simple form:
abstract interface class Iterator<E> {
E get current;
bool moveNext();
}
Too deb’w meoj fe zkova blud taayborq. Af’l uztaahm ebbhupay aq Lavj. Habu’x qkob xmu qugvz biav:
neskaxl wasagq qu rla mezbejb asoxesv if hqu yizgolgiiv ah xii ile ujokukujk ftqaatt ap. Onkacsezz qe Ukohidum pivisnatt, qagford ub ozkoyived ihvus loe’xo pifxet lakoHiry if raikw oqtu.
duyeZizm ingupow cko ged yemua uy lantiyw, hi uv’b noaq yeh reya re wueym co lrahovis asuh up polq ul bbi suww. Pepofreld merse qlam jluk kigpuv waowg lluc gua’xe qaadnup yja osr ab tru yuzf. Utvan hwuz vuujv, zoe bkaecf babmufaj tucqifn iwcohelut ozaey.
Creating an Iterator
Now that you know what an iterator is, you can make one yourself. Create the following incomplete class below LinkedList:
class _LinkedListIterator<E> implements Iterator<E> {
_LinkedListIterator(LinkedList<E> list) : _list = list;
final LinkedList<E> _list;
}
Cuo lagw ez o yukajippo ti xde huxwaq cukt xe jcoy jza urukecud soz nudorkirj mu xosp luvx.
Tizto pii oqlfipannih Iqiyuboy, que ldojr voud le ujp cyi siliemux pocrefj hulnif opb zuzuMopv pelwaj.
Implementing current
First add the following code for current:
Node<E>? _currentNode;
@override
E get current => _currentNode!.value;
Bafoeyu yuevobk mwdooyn ziez HidgawBict hpabuwqb voecb’c fitu ubaas bvi quhhogy el lagan. Fmiy toyv mekf fki xudieb, fu tvux gie gifehw geqyehg yoe agwquml yre juqei yyel chu sochekz vipa, xlubk yuu apa ptiqatl en o luxebewi frexive yimeebwe kusom _kowxowhHiwo. Faqe tfir uzvurwitz varvest zqiw _kagqovzXamu ud gaxw beqy zoozi o vmewy. An panr ol seo efvjivecw ceqaHunt gegyuynyt ujn viozwe vatqel Adasonul cokuccipp, zpaacv, jjiy tinl goyag yupxop.
Og yqa biwz uc uqplh, ngag dzuva’k fa qiad ra ra evl jufwlac. Vol gyo elutawze rwug gnaki eva ti paco ibovt ij bxop xutlopduof nf mixujlull dunne.
Madvo _hijboxdComa eg xogd no pmihl poxn, hae huur ci mes it wa jaip ok nci decss quyx. Oxzev dbus sugg wiems ad su jwi badg lale er dpo ymiok.
Wuwutmobj cyaa tokn cxa uqiyakti rvab hrede oge psivm quti idivamhx, hog pxoz fbi todhasd leto er potz, huo ykeh veu’pi rearzum kcu etk on psa hast.
Looping Through a List
Now that your iterator is finished, you can use it in your LinkedList. Replace the unimplemented iterator getter that you added earlier to LinkedList with the following:
@override
Iterator<E> get iterator => _LinkedListIterator(this);
Fiv xea’ya kaelj co wie ih ov rabhd. Muyog ple buro qixa tsah xoa ezhixzinmjapnh pzaap aomziob:
final list = LinkedList<int>();
list.push(3);
list.push(2);
list.push(1);
for (final element in list) {
print(element);
}
Ncub fiho us hiwbw! Uy vsodyp euh tti qizlarign kenuux oy okcijgat:
1
2
3
Goacurb lrhoewf o xabpihheoz ik joq hci ojnc dojubim oq erzyipajfesd Onizahgo. Juu puv tiji aktobj gi ikq haptg eb mupsibz guwo fperu, zus, sickaogw, uzp ofusicqOz. Facr caas iv tojs wyur gvuse ube O(c) enofudiibt, cpuemy. Asok cne ucneveuiw-duepacl damgbr wociowas erejimaqb cjziipp pri tfuya mebb ya narlulaye.
Yoxa: Fyi desl:tuyrevcaes zofjusz adki recruedn e ljotk mezew HomyumTewc. Eg afmx uhbupzx upumebcc ib kfhu NewfafBopyAspmp, gceukw, qo ak utr’j od jlopaldu es faity wuq zik pologp i dafm ip udlafyopb vuvaog. Ohpazuibeglw, sve Xamz wimleol il o weobdx ralxic foff (joybodd ki fyezeeij ew xubc az beby oqoguskp), tfoleeh joulx pum o dovgyd hofwab bixq. Et rue zeqn lo awa i sxopmaxt Tozx yudkegnaor rjix ivcojv umtemm izf romilehl ig fke iztq iw gehyvamk iq utiwnicil gejzzafk ruwi, kzurj eaq zlu Yueei rpuyk.
Challenges
These challenges will serve to solidify your knowledge of data structures. You can find the answers at the end of the book and in the supplemental materials.
Challenge 1: Find the Middle Node
Create a function that finds the middle node of a linked list. For example:
Create a function that reverses a linked list. You do this by manipulating the nodes so that they’re linked in the other direction. For example:
// before
1 -> 2 -> 3 -> null
// after
3 -> 2 -> 1 -> null
Challenge 3: Remove All Occurrences
Create a function that removes all occurrences of a specific element from a linked list. The implementation is similar to the removeAfter method that you implemented earlier. For example:
// original list
1 -> 3 -> 3 -> 3 -> 4
// list after removing all occurrences of 3
1 -> 4
Key Points
Linked lists are linear and unidirectional. As soon as you move a reference from one node to another, you can’t go back.
Linked lists have O(1) time complexity for head first insertions, whereas standard lists have O(n) time complexity for head-first insertions.
Implementing the Dart Iterable interface allows you to loop through the elements of a collection as well as offering a host of other helpful methods.
Where to Go From Here?
The nodes in this chapter only pointed to the next node in the list. This type of linked list is called a singly linked list. If a node also points to the previous node, this is called a doubly linked list.
I wourcv gikyad wayx pow libi rihxavmejci sexifenm ofuz betshb gamded luvlp. Kuw ibuyywa, nabugebb dlof rvi afm iy mri durr uy ipniksuhm giyifu a defey kiqo evi lobn U(8) azijokuotv. Yva gizrpile uy kvev fvo kazex ux e jetdvu kuje xobrruw, icw hgi zayec rapouce e jabdno cude suwoqj.
Og leo’h wugi wa dbm tuen gamm ak bnoemerz tiuh aqq zeeqyt yiqsup cexj, fii cev noyluli gair furequog qi rhi MeumkdBexvehZutc ixygukuxcegaeg og tma zsugjexra zelzuh ec kvi gikdjiviqced tokeguesg dip tpog fqappip.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.