A la lliçó anterior vas aprendre a triar estructura amb criteri; en aquesta mirarem enrere amb calma. Repassarem el curs complet explicant-lo com el que realment ha estat: la història de TaskFlow, una aplicació que va començar sent un simple diccionari de Python i ha acabat amb tauler, desfer, notificacions, índexs, jerarquies i un planificador de dependències. Veure tot el camí d'una tirada fixa el mapa mental millor que qualsevol llista de definicions, i el test d'autoavaluació final et dirà amb honestedat què portes ben lligat i què convé repassar abans dels projectes.
Contingut
- L'evolució de TaskFlow, mòdul a mòdul
- Taula-xuleta: tot el que hem construït
- Els cinc conceptes transversals
- Què saps fer ara que no sabies en començar
- Autoavaluació tipus test
L'evolució de TaskFlow, mòdul a mòdul
flowchart LR
V0[v0.1<br/>tasca = dict] --> V1[M1-M2<br/>tauler<br/>llistes] --> V2[M3<br/>desfer<br/>piles] --> V3[M4<br/>notificacions<br/>cues i heaps]
V3 --> V4[M5<br/>índexs<br/>hash] --> V5[M6<br/>jerarquies<br/>arbres] --> V6[M7<br/>planificador<br/>grafs] --> V7[M8<br/>criteri<br/>i projectes]
Mòdul 1 — Els fonaments: TAD, Big O i memòria
TaskFlow va néixer com un dict humil: {"id": 1, "titol": "...", "prioritat": 2, "estat": "pendent"}. Abans de construir res vam aprendre a pensar: la diferència entre el contracte d'un TAD i la seva implementació, la notació Big O d'O(1) a O(n²), la taula de costos de list/dict/set, i com mesurar de debò amb timeit. També vam mirar la memòria de cara: un array és contigüitat pura (base + i × mida, i d'aquí l'accés O(1)), la list de Python és un array dinàmic, i insert(0)/pop(0) amaguen un O(n) que perseguiríem durant tot el curs.
Mòdul 2 — El tauler: llistes enllaçades
Primer problema real: inserir i esborrar tasques del tauler sense pagar desplaçaments. Amb Node i LlistaEnllacada vam construir el tauler de TaskFlow i vam acceptar el tracte de les llistes enllaçades: inserció O(1) al cap a canvi d'accés O(n) per posició. NodeDoble i LlistaDoblementEnllacada van donar l'HistorialTasques navegable en tots dos sentits, i la LlistaCircular el RepartidorTasques round-robin. De torna, els clàssics: invertir la llista, detectar cicles amb Floyd (llebre i tortuga), fusionar ordenades i inserir_ordenat.
Mòdul 3 — Desfer: piles
"Ctrl+Z" és l'operació estrella de qualsevol editor, i la seva estructura és la pila: l'últim que has fet és el primer que es desfà. Sobre Pila i PilaEnllacada vam muntar l'HistorialAccions i el GestorDesferRefer amb dues piles (desfer empeny a refer, i viceversa). Vam veure que la pila també valida (filtre_balancejat), calcula (avaluar_postfixa, infixa_a_postfixa), recorda extrems (PilaAmbMinim) i sosté la mateixa execució del programa: la pila de crides explica la recursió, el RecursionError i com convertir recursió en iteració amb una pila explícita.
Mòdul 4 — Notificacions: cues
Les notificacions exigien justícia FIFO: Cua sobre LlistaEnllacada va donar la CuaNotificacions, i pel camí vam demostrar que dues piles fan una cua (CuaAmbDuesPiles). La CuaCircular (ring buffer) va resoldre el RegistreEsdeveniments amb memòria fixa. Quan "per ordre d'arribada" va deixar de bastar, van arribar les cues de prioritat: de la versió ingènua a heapq amb tuples (prioritat, comptador, tasca) a la SafataUrgencies. I collections.deque va demostrar ser la navalla suïssa dels extrems: HistorialAmbLimit amb maxlen, FinestraProductivitat de mitjana mòbil i el deque monòton.
Mòdul 5 — Índexs: taules hash
Cercar una tasca per id recorrent llistes era O(n); la màgia O(1) del dict va deixar de ser màgia quan vam construir una taula hash des de zero: TaulaHashIngenua, hash polinòmic, col·lisions per encadenament, factor de càrrega 0.75 i rehashing a TaulaHash. A la pràctica: claus hashables, defaultdict, Counter, l'índex invertit etiqueta→ids del CercadorEtiquetes, memoïtzació i two-sum. I el límit honest que va obrir el mòdul següent: el hash no coneix l'ordre ni entén d'intervals.
Mòdul 6 — Jerarquies: arbres
Els projectes contenen tasques que contenen subtasques: jerarquia pura. NodeArbre va modelar l'arbre general i pressupostar va mostrar el poder del postordre (els fills abans que el pare). Amb NodeBinari van arribar els tipus d'arbre, la representació en array (fills a 2i+1/2i+2) i els quatre recorreguts, també iteratius amb pila. L'ArbreCerca (ABC) va resoldre per fi "prioritat entre 1 i 3" amb interval, l'ArbreAVL va garantir O(log n) amb rotacions, els arbres B/B+ van explicar els índexs de les bases de dades, i el Monticle propi (flotar/enfonsar) va reconstruir heapq per dins per a la SafataUrgencies 2.0. Clàssics: es_abc, reconstruir, top_k.
Mòdul 7 — Dependències: grafs
"La tasca B no pot començar fins a acabar l'A" no és jerarquia: és un graf. La classe Graf (llista d'adjacència com a dict de dicts, amb la convenció "aresta A→B = B depèn d'A") va modelar el DAG de TaskFlow. BFS amb deque, DFS recursiu i iteratiu, hi_ha_cicle amb colors blanc/gris/negre i l'ordre_topologic de Kahn van ordenar la feina; components_connexes va agrupar; dijkstra, bellman_ford i la menció a Floyd-Warshall van mesurar camins; prim, kruskal i UnionFind van teixir xarxes mínimes. La cirereta: el planificador complet amb nivells paral·lels i ruta_critica, més suggerir_collaboradors, pagerank i els grafs implícits en graella.
Mòdul 8 — El criteri
Aquest mòdul: el mètode de les set preguntes, l'arbre de decisió, la gran taula, i els projectes que vindran.
Taula-xuleta: tot el que hem construït
El teu índex de referència ràpida. Si algun nom no t'evoca immediatament la seva idea, aquella és la lliçó a repassar.
| Identificador | Mòdul | Què fa |
|---|---|---|
Node / LlistaEnllacada |
2 | Llista enllaçada simple; el primer tauler de TaskFlow |
NodeDoble / LlistaDoblementEnllacada |
2 | Enllaços en tots dos sentits; base de l'HistorialTasques |
LlistaCircular / RepartidorTasques |
2 | Round-robin: repartir tasques per torns |
Pila / PilaEnllacada |
3 | LIFO sobre list i sobre nodes |
HistorialAccions / GestorDesferRefer |
3 | Desfer/refer amb dues piles |
filtre_balancejat, avaluar_postfixa, infixa_a_postfixa |
3 | Validació i avaluació d'expressions amb pila |
PilaAmbMinim |
3 | Mínim en O(1) amb pila auxiliar |
Cua / CuaNotificacions |
4 | FIFO sobre llista enllaçada |
CuaAmbDuesPiles |
4 | Una cua construïda amb dues piles |
CuaCircular / RegistreEsdeveniments |
4 | Ring buffer de memòria fixa |
SafataUrgencies |
4 i 6 | Cua de prioritat amb heapq; 2.0 amb monticle propi |
HistorialAmbLimit / FinestraProductivitat |
4 | deque amb maxlen i mitjana mòbil |
TaulaHashIngenua / TaulaHash |
5 | Hash des de zero: encadenament, càrrega 0.75, rehashing |
CercadorEtiquetes |
5 | Índex invertit etiqueta→ids |
NodeArbre / pressupostar |
6 | Arbre general de projectes; agregat en postordre |
NodeBinari |
6 | Arbre binari i recorreguts |
ArbreCerca (+ interval) |
6 | ABC amb consulta per intervals |
ArbreAVL |
6 | ABC autoequilibrat: O(log n) garantit |
Monticle |
6 | Monticle binari propi: flotar/enfonsar |
es_abc, reconstruir, top_k |
6 | Clàssics d'arbres |
Graf |
7 | Llista d'adjacència (dict de dicts) |
hi_ha_cicle, ordre_topologic |
7 | Colors blanc/gris/negre; algorisme de Kahn |
dijkstra, bellman_ford |
7 | Camins mínims (sense/amb pesos negatius) |
prim, kruskal, UnionFind |
7 | Arbres d'expansió mínima |
ruta_critica, planificador per nivells |
7 | Planificació del DAG de TaskFlow |
NucliTaskFlow |
8 | Combinació dict + índexs + monticle |
Els cinc conceptes transversals
Més importants que qualsevol estructura concreta, perquè són els que continuaràs fent servir quan apareguin estructures que aquest curs no cobreix:
- TAD davant d'implementació. La pila és un contracte (push/pop/peek);
list,PilaEnllacadao undequesón maneres de complir-lo. Programa contra el contracte i podràs canviar la implementació sense tocar la resta del codi. - Big O com a idioma. No és matemàtica decorativa: és la manera de predir si un codi que funciona amb 100 elements sobreviurà a 100 000. La diferència entre
insobrelist(O(n)) i sobreset(O(1)) decideix si el teu bucle és lineal o quadràtic. - Recursió ↔ iteració. Tota recursió és una pila implícita; tota recursió es pot reescriure amb una pila explícita (i de vegades convé, recorda el
RecursionError). Recorreguts d'arbres i DFS són el mateix patró amb dos vestits. - Estructures que construeixen estructures. La cua sobre llista enllaçada, la cua amb dues piles, la pila amb mínim, el monticle sobre array, el graf sobre dict de dicts, la memòria cau LRU amb dict + llista doble... Compondre és l'habilitat; memoritzar, només la drecera.
- Mesurar en lloc de suposar.
timeitva ser la primera eina del curs i ha de ser l'última paraula en qualsevol discussió de rendiment. La teoria orienta; les dades reals decideixen.
Què saps fer ara que no sabies en començar
Llista honesta: tot això ho has fet, no només llegit.
- Llegir i escriure anàlisis Big O del teu propi codi, i verificar la teoria amb
timeit. - Implementar des de zero, en Python, llistes enllaçades (simple, doble, circular), piles, cues, ring buffers, taules hash amb col·lisions i rehashing, ABC, AVL, monticles i grafs.
- Triar entre
list,dict,set,dequeiheapqsabent què hi ha a sota i què costa cada operació. - Reconèixer patrons: LIFO → pila, FIFO → cua, "el més urgent" → monticle, clau exacta → hash, intervals → arbre, dependències → graf.
- Aplicar els algorismes clàssics: Floyd, recorreguts d'arbres, BFS/DFS, detecció de cicles, ordre topològic, Dijkstra, Bellman-Ford, Prim, Kruskal.
- Combinar estructures coordinades per complir diversos requisits de cost alhora.
- I el que encara no saps (ordenació en profunditat, programació dinàmica, tries...): ho tens localitzat, i la lliçó següent et dona el mapa per arribar-hi.
Errors Comuns i Consells
- Confondre "em sona" amb "ho sé". El test de sota només és útil si el fas sense mirar; el repàs passiu produeix una falsa sensació de domini.
- Repassar estructures com fitxes aïllades. Repassa problemes: "com faria el desfer?", "com detectaria el cicle?". L'estructura ha de venir a tu des del problema, no a l'inrevés.
- Saltar-se els perquès. Saber que el
dictés O(1) sense recordar el hash polinòmic i el rehashing és coneixement fràgil: cau a la primera pregunta de seguiment d'una entrevista. - No reimplementar de memòria. Abans dels projectes, prova de reescriure sense mirar una
Pila, unaCuaCirculari el BFS. Allà on t'encallis, allà hi ha el teu buit real.
Exercicis
Autoavaluació tipus test. Respon sense consultar les lliçons i després contrasta amb les solucions raonades.
- En una
listde Python amb 1 000 000 d'elements, quina d'aquestes operacions és la més cara? (a)llista[500000](b)llista.append(x)(c)llista.insert(0, x)(d)llista.pop() - El
GestorDesferReferfa servir dues piles. En executar "desfer", què passa exactament? - Per què la
SafataUrgenciesencua tuples(prioritat, comptador, tasca)i no(prioritat, tasca)? - Un company guarda els ids de tasques completades en una
listi comprovaid in completadesdins d'un bucle sobre 50 000 tasques. Quin és el cost total i com ho arregles? - Quina estructura respon "dona'm totes les tasques amb prioritat entre 1 i 3" en O(log n + k), i per què un
dictno pot? - Insereixes en un ABC les claus 1, 2, 3, 4, 5, en aquest ordre. Quina forma té l'arbre i quin cost té ara cercar el 5? Quina estructura ho evita?
- Quin recorregut va fer servir
pressupostarper sumar el cost de projectes → tasques → subtasques, i per què precisament aquell? - Al DAG de dependències de TaskFlow, quin algorisme et dona un ordre vàlid d'execució i quin senyal t'avisa que no existeix tal ordre?
- Vertader o fals? "Un monticle manté tots els seus elements completament ordenats."
- Quines dues estructures combinava la memòria cau del "mode repàs" (exercici 3 de la lliçó anterior) i què hi aporta cadascuna?
Solucions
- (c)
insert(0, x)és O(n): desplaça el milió d'elements una posició. (a) és O(1) per contigüitat (base + i × mida); (b) i (d) són O(1) (amortitzat en l'append) perquè toquen només el final. - Es fa
popde la pila de desfer (l'acció més recent), se'n reverteix l'efecte i aquella acció es fapusha la pila de refer. Si després l'usuari executa una acció nova, la pila de refer es buida: la història alternativa deixa de ser vàlida. - Dues raons: el comptador desempata prioritats iguals garantint l'ordre d'arribada (FIFO dins de la mateixa prioritat) i, a més, evita que
heapqintenti comparar els dicts de tasca entre si (elsdictno són comparables i llançaria unTypeError). insobrelistés O(n); dins del bucle, O(n·m) — amb 50 000 tasques, de l'ordre de milers de milions de comparacions. Converteixescompletadesensetun sol cop (O(m)) i cada consulta passa a O(1): el total queda en O(n + m).- Un ABC/AVL (o una llista ordenada amb
bisect): baixes fins a l'inici de l'interval en O(log n) i recorres en inordre els k resultats. Eldictno pot perquè la funció hash dispersa a propòsit: destrueix tota relació d'ordre entre claus per aconseguir l'O(1) per clau exacta. - Degenera en una "llista" inclinada a la dreta: cada clau és més gran que l'anterior, així que tots pengen del fill dret. Cercar el 5 costa O(n). Ho evita l'AVL, que rota en detectar desequilibri i garanteix alçada O(log n) arribi com arribi l'entrada.
- Postordre: processa els fills abans que el pare, de manera que quan toca calcular el pressupost d'un node, els pressupostos de tots els seus subarbres ja estan calculats i n'hi ha prou de sumar-los.
- L'ordre topològic (Kahn: anar extraient nodes amb grau d'entrada 0). Si l'algorisme acaba sense haver processat tots els vèrtexs —o si el DFS de colors troba una aresta cap a un node gris—, hi ha un cicle de dependències i no existeix ordre vàlid.
- Fals. El monticle només garanteix la propietat de monticle: cada pare ≤ els seus fills (en un min-heap). El mínim és a l'arrel, però els germans no guarden ordre entre si; per això inserir i extreure són O(log n) i no O(n) — és un ordre "suficient", no total.
- Un
dictid→node (localitzar en O(1)) i una llista doblement enllaçada amb l'ordre d'ús (moure un node al cap i expulsar per la cua en O(1)). El dict no sap d'ordre; la llista no sap cercar: juntes, totes les operacions són O(1).
Interpretació: 9-10 encerts, a punt per als projectes; 6-8, repassa les lliçons de les preguntes fallades; menys de 6, torna als exercicis dels mòduls fluixos abans de continuar — els projectes finals assumeixen tot això.
Conclusió
TaskFlow ha passat d'un dict solt a un sistema amb set famílies d'estructures treballant coordinades, i tu has passat de fer servir list "perquè sí" a justificar cada tria amb operacions i costos. El mapa és complet: els fonaments (mòdul 1), les estructures lineals (2-4), els índexs (5), les jerarquies (6), les xarxes (7) i el criteri (8). A la lliçó següent et deixo la biblioteca: els recursos amb què continuar creixent pel teu compte quan aquest curs s'acabi.
Curs d'Estructures de Dades
Mòdul 1: Introducció a les Estructures de Dades
- Què són les Estructures de Dades?
- Importància de les Estructures de Dades en la Programació
- Tipus d'Estructures de Dades
- Complexitat Algorísmica i Notació Big O
- Arrays i Memòria: la Base de les Estructures de Dades
Mòdul 2: Llistes
- Introducció a les Llistes
- Llistes Enllaçades
- Llistes Doblement Enllaçades
- Llistes Circulars
- Exercicis amb Llistes
Mòdul 3: Piles
- Introducció a les Piles
- Operacions Bàsiques amb Piles
- Implementació de Piles
- Aplicacions de les Piles
- Exercicis amb Piles
Mòdul 4: Cues
- Introducció a les Cues
- Operacions Bàsiques amb Cues
- Cues Circulars
- Cues de Prioritat
- Cues Dobles (Deques)
- Exercicis amb Cues
Mòdul 5: Taules Hash i Diccionaris
- Introducció a les Taules Hash
- Funcions Hash i Resolució de Col·lisions
- Diccionaris i Conjunts a la Pràctica
- Exercicis amb Taules Hash
Mòdul 6: Arbres
- Introducció als Arbres
- Arbres Binaris
- Recorreguts d'Arbres
- Arbres Binaris de Cerca
- Arbres AVL
- Arbres B
- Monticles (Heaps)
- Exercicis amb Arbres
Mòdul 7: Grafs
- Introducció als Grafs
- Representació de Grafs
- Algorismes de Cerca en Grafs
- Algorismes de Camins Mínims
- Arbres d'Expansió Mínima
- Aplicacions dels Grafs
- Exercicis amb Grafs
