C-keel on programmeerimiskeel, mida kasutatakse laialdaselt süsteemiprogrammeerimises ja manussüsteemide arendamisel. Nende hulgas on „loendistruktuur” väga kasulik tööriist andmete haldamiseks ja töötlemiseks. Selles artiklis selgitatakse üksikasjalikult C-keele loendistruktuuri, alustades põhimõistetest ja lõpetades konkreetsete rakendusnäidetega.
Loendistruktuuri olulisus
Loendistruktuur on andmestruktuur, mis võimaldab hallata andmeid säilitades nende järjekorra. See on eriti kasulik järgmistel juhtudel:
Järjestatud andmete haldamine
Kui on vaja andmeid dünaamiliselt lisada või kustutada
Mälu tõhus kasutamine
Näiteks ülesannete haldamise rakenduses lisatakse ja kustutatakse ülesandeid sageli, mistõttu on paindlik loendistruktuur sobivam kui massiiv.
Artikli eesmärk
Selles artiklis rakendame C-keeles loendistruktuuri ning tutvustame selle kasutamist koos konkreetsete koodinäidetega. Lõpptulemusena mõistab lugeja loendistruktuuri põhikontseptsiooni ja praktilist kasutust ning oskab seda oma programmides rakendada.
Õpieesmärgid
Mõista loendistruktuuri aluseid.
Õppida ühendatud loendi rakendamist C-keeles.
Omandada rakendusoskus praktiliste koodinäidete abil.
2. Mis on loendistruktuur?
Loendistruktuur on üks andmestruktuuridest, mis võimaldab andmeid hallata kindlas järjekorras. Selles jaotises selgitame loendistruktuuri põhimõisteid ja tüüpe.
Loendistruktuuri põhimõisted
Loendistruktuur koosneb omavahel aheldatud andmeüksustest (sõlmedest). Iga sõlm sisaldab kahte tüüpi teavet:
Andmeosa: tegelik väärtus, mida hoitakse.
Lingi osa: viit (pointer) järgmisele sõlmele.
Tänu sellele mehhanismile saab andmeid dünaamiliselt lisada ja kustutada, mis muudab selle mugavaks muutuvpikkusega andmete töötlemisel.
Massiivi ja loendi erinevused
Loendistruktuur ja massiiv erinevad andmete haldamise viisi poolest järgmiselt:
Element
Massiiv
Loendistruktuur
Suurus
Fikseeritud (määratakse deklaratsiooni ajal)
Muudetav (dünaamiline suuruse muutmine võimalik)
Juurdepääs andmetele
Otsene juurdepääs indeksi kaudu
Vajalik järjestikune otsing algusest
Mälu haldus
Kasutab järjestikust mälupiirkonda
Kasutab hajutatud mälupiirkondi
Andmete lisamine / kustutamine
Kõrge kulu (vajalik elementide ümbertõstmine)
Madal kulu (ainult viitade muutmine)
Nagu näha, sobib loendistruktuur olukordadesse, kus on vaja paindlikkust, samas kui massiiv sobib juhtudeks, kus on oluline kiire andmepääs.
Ühendatud loendi ülevaade ja omadused
Loendistruktuuri tuntud näide on „ühendatud loend”, millel on järgmised omadused:
Dünaamiline suuruse muutmine Sõlmi saab lisada ja kustutada vastavalt vajadusele, mis võimaldab tõhusat mäluhaldust.
Lihtne sõlmede lisamine / kustutamine Viitade ümberseadistamisega saab elemente hõlpsalt hallata ka loendi keskel või lõpus.
Otsing on aeglasem Andmetele pääseb ligi järjestikku algusest, mistõttu konkreetse elemendi leidmine võtab rohkem aega.
Ühendatud loendi tüübid
Ühendatud loendil on kolm peamist tüüpi:
Ühesuunaline loend Iga sõlm viitab ainult järgmisele sõlmele.
Lihtne struktuur ja väike mälukasutus.
Liikumine võimalik ainult ühes suunas, tagasi liikumine pole võimalik.
Kahesuunaline loend Iga sõlm viitab nii järgmisele kui ka eelmisele sõlmele.
Võimaldab liikuda edasi-tagasi, pakkudes paindlikke toiminguid.
Kasutab rohkem mälu kui ühesuunaline loend.
Ringloend Viimane sõlm viitab esimesele sõlmele.
Loendi lõppu jõudes saab uuesti algusesse liikuda, sobib tsükliliste protsesside jaoks.
Kasulik spetsiaalsetes rakendustes.
Järgmises jaotises selgitame „Ühendatud loendi rakendamist C-keeles” koos konkreetsete koodinäidetega.
3. Ühendatud loendi rakendamine C-keeles
Selles jaotises selgitame, kuidas rakendada ühendatud loendit C-keeles koos konkreetsete koodinäidetega.
Ühendatud loendi põhistruktuur
Ühendatud loend koosneb järgmistest elementidest:
Sõlme definitsioon
Iga sõlm sisaldab andmeid ja viita järgmisele sõlmele.
Peesõlm (head pointer)
Viitab loendi esimesele elemendile ja haldab kogu loendit.
Koodinäide: Ühendatud loendi põhiteostus
Alljärgnev on näide koodist ühendatud loendi haldamiseks.
#include <stdio.h>
#include <stdlib.h>
// Sõlme definitsioon
typedef struct Node {
int data; // Andmeosa
struct Node* next; // Viit järgmisele sõlmele
} Node;
// Uue sõlme loomine
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // Mälureservatsioon
if (newNode == NULL) {
printf("Mälu broneerimine ebaõnnestus.\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL; // Algseisus ei viita järgmisele sõlmele
return newNode;
}
// Sõlme lisamine (loendi algusesse)
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data); // Uue sõlme loomine
newNode->next = *head; // Uus sõlm viitab praegusele esimesele sõlmele
*head = newNode; // Peaviit uuendatakse uuele sõlmele
}
// Loendi väljastamine
void printList(Node* head) {
Node* temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
// Mälu vabastamine
void freeList(Node* head) {
Node* temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp); // Mälu vabastamine
}
}
int main() {
Node* head = NULL; // Algväärtus: tühi loend
// Andmete lisamine loendisse
insertAtHead(&head, 3);
insertAtHead(&head, 2);
insertAtHead(&head, 1);
// Loendi väljastamine
printList(head);
// Mälu vabastamine
freeList(head);
return 0;
}
Koodi selgitus
Sõlme loomine
Uus sõlm luuakse dünaamilise mälureservatsiooni abil.
Sõlme lisamine
Andmeid saab lisada loendi algusesse või lõppu, et seda laiendada.
Sõlme kustutamine
Kustutab sõlme, millel on konkreetne väärtus, või esimese sõlme.
Mälu haldamine
Kasutamata mälu vabastatakse, et vältida mäluleke.
4. Ühendatud loendi tüübid
Selles jaotises tutvustame peamisi ühendatud loendi tüüpe ja nende omadusi. Selgitame iga loendi eeliseid ja puudusi, et saaksite valida sobiva struktuuri vastavalt kasutusjuhtumile.
1. Ühesuunaline loend
Ühesuunalises loendis viitab iga sõlm ainult järgmisele sõlmele. Omadused:
Elemendid liiguvad ainult ühes suunas.
Väike mälukasutus ja lihtne teostus.
Eelised:
Dünaamiline suurus – paindlik kohanemine.
Elementide lisamine ja eemaldamine on efektiivne.
Puudused:
Tagasiliikumine pole võimalik, mistõttu vastupidine otsing on ebaefektiivne.
2. Kahesuunaline loend
Kahesuunalises loendis viitab iga sõlm nii eelmisele kui ka järgmisele sõlmele. Omadused:
Kahe viida (prev/next) olemasolu.
Liikumine mõlemas suunas.
Eelised:
Lihtsam kustutada või sisestada sõlmi suvalises kohas.
Tagurpidi läbimine on mugav.
Puudused:
Suurem mälukulu kui ühesuunalisel loendil.
Keerukam teostus (viitade hoolikas uuendamine).
3. Ringloend
Ringloendis viitab viimane sõlm esimesel(e), moodustades tsükli. Omadused:
Pole otsest lõppu – läbimine võib jätkuda lõpmatuseni.
Võib olla ühesuunaline või kahesuunaline.
Eelised:
Lihtne liikuda pea ja saba vahel – sobib tsükliliste protsesside jaoks.
Hea järjekordade ja puhvrite rakendustes.
Puudused:
Lõpu puudumine nõuab ettevaatlikkust otsingu ja kustutamise teostamisel.
Võrdlustabel
Tüüp
Omadused
Eelised
Puudused
Ühesuunaline loend
Liikumine ainult edasi
Lihtne, väike mälukulu
Tagasiliikumine puudub
Kahesuunaline loend
Liikumine edasi ja tagasi
Paindlik manipuleerimine ja otsing
Suurem mälukulu, keerukam teostus
Ringloend
Lõpp seostub algusega
Hea tsüklilisteks töötlusteks
Haldus võib olla keerukam
Järgmisena vaatleme „Ühendatud loendi toiminguid” – sisestamine, kustutamine ja otsing koos koodinäidetega.
5. Ühendatud loendi toimingud
Selles jaotises selgitame põhitoiminguid: sisestamine, kustutamine ja otsing, koos C-koodi näidetega.
1. Elementide sisestamine
Ühendatud loendisse saab elemente lisada kolmel tüüpilisel viisil.
(1) Lisamine loendi algusesse
Lisab uue sõlme loendi etteotsa.
void insertAtHead(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = *head; // Uus sõlm viitab praegusele peale
*head = newNode; // Uus sõlm saab peaks
}
(2) Lisamine loendi lõppu
Lisab uue sõlme loendi lõppu.
void insertAtTail(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) { // Tühi loend
*head = newNode;
return;
}
Node* temp = *head;
while (temp->next != NULL) { // Liigu viimase sõlmeni
temp = temp->next;
}
temp->next = newNode; // Lisa lõppu
}
2. Elementide kustutamine
Selgitame, kuidas eemaldada elemente loendist.
(1) Eelemendi (pea) kustutamine
void deleteAtHead(Node** head) {
if (*head == NULL) {
printf("Loend on tühi.\n");
return;
}
Node* temp = *head;
*head = (*head)->next; // Nihuta peaviit järgmisele
free(temp); // Vabasta mälu
}
(2) Konkreetses väärtuses sõlme kustutamine
void deleteNode(Node** head, int key) {
Node* temp = *head;
Node* prev = NULL;
// Kui esimene sõlm on sihtmärk
if (temp != NULL && temp->data == key) {
*head = temp->next;
free(temp);
return;
}
// Otsi kustutatav sõlm
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) {
printf("Väärtust ei leitud.\n");
return;
}
prev->next = temp->next;
free(temp);
}
3. Elementide otsing
Otsi loendist konkreetse väärtusega sõlme.
int search(Node* head, int key) {
Node* temp = head;
int position = 0;
while (temp != NULL) {
if (temp->data == key) {
return position; // Tagasta leitud asukoht
}
temp = temp->next;
position++;
}
return -1; // Mitte leitud
}
Pinu (LIFO) sobib rekursiooni haldamiseks ja avaldiste hindamiseks.
Järk (FIFO) sobib puhverdamiseks ja tööde järjekordadele.
8. Kokkuvõte
Selles artiklis käsitlesime C-keele ühendatud loendeid alustades põhimõistetest ja teostusest kuni rakendusnäideteni.
1. Olulised punktid
Alused
Ühendatud loend on dünaamilise suurusega ja väga paindlik.
Erinevalt massiivist on lisamine/kustutamine lihtne, kuid otsing on järjestikuline.
Teostus ja toimingud
Näitasime sõlme loomist, sisestamist, kustutamist ja otsingut koodinäidetega.
Korralik mäluhaldus tagab stabiilsuse ja jõudluse.
Tüübid ja kasutus
Ühesuunaline – lihtne ja kerge.
Kahesuunaline – mugav edasitagasi liikumiseks.
Ringloend – sobib tsükliliste protsesside jaoks.
Rakendused
Pinu ja järk ühendatud loendiga – kasulikud paljudes süsteemi- ja algoritmitöödes.
2. Omandatud oskused
Andmestruktuuride mõistmine
Praktiline C-programmeerimine
Probleemilahendus oskus
3. Mida edasi õppida
Andmestruktuuride laiendused
Puu-struktuurid (binaarpuu, AVL)
Graafialgoritmid (BFS, DFS)
Täiustatud andmehaldus
Hash-tabelid ja map-struktuurid
Dünaamiliste massiivide ja loendite hübriidid
Algoritmide optimeerimine
Sortimise ja otsingu optimeerimine
Aja- ja ruumikompleksuse analüüs
4. Praktilised projektid
Ülesannete haldus (to-do rakendus loenditega)
Simulatsioonid (sündmuste töötlus järkudega)
Andmeanalüütika tööriistad (puhvrid ja vootöötlus)
5. Lõpetuseks
Ühendatud loendid on baasandmestruktuur, mille rakendusala on lai. Loodetavasti andis see artikkel selge teejuhi alustest praktikani, et saaksite neid struktuure oma C-projektides enesekindlalt kasutada.