C Language
Länkade listor
Sök…
Anmärkningar
C-språket definierar inte en länkad datastruktur. Om du använder C och behöver en länkad lista måste du antingen använda en länkad lista från ett befintligt bibliotek (t.ex. GLib) eller skriva ditt eget länkade gränssnitt. Det här ämnet visar exempel på länkade listor och dubbellänkade listor som kan användas som utgångspunkt för att skriva dina egna länkade listor.
Singellänkad lista
Listan innehåller noder som består av en länk som heter nästa.
Datastruktur
struct singly_node
{
struct singly_node * next;
};
Dubbel länkad lista
Listan innehåller noder som består av två länkar som kallas föregående och nästa. Länkarna avser normalt en nod med samma struktur.
Datastruktur
struct doubly_node
{
struct doubly_node * prev;
struct doubly_node * next;
};
Topoliges
Linjär eller öppen
Cirkulär eller ring
Rutiner
Binda
void doubly_node_bind (struct doubly_node * prev, struct doubly_node * next)
{
prev->next = next;
next->prev = prev;
}
Gör cirkulärt länkad lista
void doubly_node_make_empty_circularly_list (struct doubly_node * head)
{
doubly_node_bind (head, head);
}
Skapa linjärt länkad lista
void doubly_node_make_empty_linear_list (struct doubly_node * head, struct doubly_node * tail)
{
head->prev = NULL;
tail->next = NULL;
doubly_node_bind (head, tail);
}
Införande
Låt oss anta att en tom lista alltid innehåller en nod istället för NULL. Då behöver insättningsförfaranden inte ta hänsyn till NULL.
void doubly_node_insert_between
(struct doubly_node * prev, struct doubly_node * next, struct doubly_node * insertion)
{
doubly_node_bind (prev, insertion);
doubly_node_bind (insertion, next);
}
void doubly_node_insert_before
(struct doubly_node * tail, struct doubly_node * insertion)
{
doubly_node_insert_between (tail->prev, tail, insertion);
}
void doubly_node_insert_after
(struct doubly_node * head, struct doubly_node * insertion)
{
doubly_node_insert_between (head, head->next, insertion);
}
Infoga en nod i början av en enskilt länkad lista
Koden nedan frågar efter nummer och fortsätter att lägga till dem i början av en länkad lista.
/* This program will demonstrate inserting a node at the beginning of a linked list */
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
void insert_node (struct Node **head, int nodeValue);
void print_list (struct Node *head);
int main(int argc, char *argv[]) {
struct Node* headNode;
headNode = NULL; /* Initialize our first node pointer to be NULL. */
size_t listSize, i;
do {
printf("How many numbers would you like to input?\n");
} while(1 != scanf("%zu", &listSize));
for (i = 0; i < listSize; i++) {
int numToAdd;
do {
printf("Enter a number:\n");
} while (1 != scanf("%d", &numToAdd));
insert_node (&headNode, numToAdd);
printf("Current list after your inserted node: \n");
print_list(headNode);
}
return 0;
}
void print_list (struct Node *head) {
struct node* currentNode = head;
/* Iterate through each link. */
while (currentNode != NULL) {
printf("Value: %d\n", currentNode->data);
currentNode = currentNode -> next;
}
}
void insert_node (struct Node **head, int nodeValue) {
struct Node *currentNode = malloc(sizeof *currentNode);
currentNode->data = nodeValue;
currentNode->next = (*head);
*head = currentNode;
}
Förklaring för införing av noder
För att förstå hur vi lägger till noder i början, låt oss ta en titt på möjliga scenarier:
- Listan är tom, så vi måste lägga till en ny nod. I vilket fall ser vårt minne så här ut där
HEAD
är en pekare till den första noden:
| HEAD | --> NULL
Linjen currentNode->next = *headNode;
kommer att tilldela värdet på currentNode->next
NULL
eftersom headNode
ursprungligen börjar med ett värde på NULL
.
Nu vill vi ställa in huvudnodpekaren så att den pekar på vår nuvarande nod.
----- -------------
|HEAD | --> |CURRENTNODE| --> NULL /* The head node points to the current node */
----- -------------
Detta görs med *headNode = currentNode;
- Listan är redan befolkad; vi måste lägga till en ny nod till början. För enkelhets skull, låt oss börja med 1 nod:
----- -----------
HEAD --> FIRST NODE --> NULL
----- -----------
Med currentNode->next = *headNode
ser datastrukturen så här:
--------- ----- ---------------------
currentNode --> HEAD --> POINTER TO FIRST NODE --> NULL
--------- ----- ---------------------
Vilket måste naturligtvis ändras eftersom *headNode
ska peka på currentNode
.
---- ----------- ---------------
HEAD -> currentNode --> NODE -> NULL
---- ----------- ---------------
Detta görs med *headNode = currentNode;
Sätta i en nod i n: e position
Hittills har vi tittat på att infoga en nod i början av en enskilt länkad lista . Men de flesta gånger vill du också kunna infoga noder på andra håll. Koden nedan visar hur det är möjligt att skriva en insert()
-funktion för att infoga noder var som helst i de länkade listorna.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* insert(struct Node* head, int value, size_t position);
void print_list (struct Node* head);
int main(int argc, char *argv[]) {
struct Node *head = NULL; /* Initialize the list to be empty */
/* Insert nodes at positions with values: */
head = insert(head, 1, 0);
head = insert(head, 100, 1);
head = insert(head, 21, 2);
head = insert(head, 2, 3);
head = insert(head, 5, 4);
head = insert(head, 42, 2);
print_list(head);
return 0;
}
struct Node* insert(struct Node* head, int value, size_t position) {
size_t i = 0;
struct Node *currentNode;
/* Create our node */
currentNode = malloc(sizeof *currentNode);
/* Check for success of malloc() here! */
/* Assign data */
currentNode->data = value;
/* Holds a pointer to the 'next' field that we have to link to the new node.
By initializing it to &head we handle the case of insertion at the beginning. */
struct Node **nextForPosition = &head;
/* Iterate to get the 'next' field we are looking for.
Note: Insert at the end if position is larger than current number of elements. */
for (i = 0; i < position && *nextForPosition != NULL; i++) {
/* nextForPosition is pointing to the 'next' field of the node.
So *nextForPosition is a pointer to the next node.
Update it with a pointer to the 'next' field of the next node. */
nextForPosition = &(*nextForPosition)->next;
}
/* Here, we are taking the link to the next node (the one our newly inserted node should
point to) by dereferencing nextForPosition, which points to the 'next' field of the node
that is in the position we want to insert our node at.
We assign this link to our next value. */
currentNode->next = *nextForPosition;
/* Now, we want to correct the link of the node before the position of our
new node: it will be changed to be a pointer to our new node. */
*nextForPosition = currentNode;
return head;
}
void print_list (struct Node* head) {
/* Go through the list of nodes and print out the data in each node */
struct Node* i = head;
while (i != NULL) {
printf("%d\n", i->data);
i = i->next;
}
}
Omvänd en länkad lista
Du kan också utföra denna uppgift rekursivt, men jag har valt att i det här exemplet använda en iterativ strategi. Denna uppgift är användbar om du sätter in alla dina noder i början av en länkad lista . Här är ett exempel:
#include <stdio.h>
#include <stdlib.h>
#define NUM_ITEMS 10
struct Node {
int data;
struct Node *next;
};
void insert_node(struct Node **headNode, int nodeValue, int position);
void print_list(struct Node *headNode);
void reverse_list(struct Node **headNode);
int main(void) {
int i;
struct Node *head = NULL;
for(i = 1; i <= NUM_ITEMS; i++) {
insert_node(&head, i, i);
}
print_list(head);
printf("I will now reverse the linked list\n");
reverse_list(&head);
print_list(head);
return 0;
}
void print_list(struct Node *headNode) {
struct Node *iterator;
for(iterator = headNode; iterator != NULL; iterator = iterator->next) {
printf("Value: %d\n", iterator->data);
}
}
void insert_node(struct Node **headNode, int nodeValue, int position) {
int i;
struct Node *currentNode = (struct Node *)malloc(sizeof(struct Node));
struct Node *nodeBeforePosition = *headNode;
currentNode->data = nodeValue;
if(position == 1) {
currentNode->next = *headNode;
*headNode = currentNode;
return;
}
for (i = 0; i < position - 2; i++) {
nodeBeforePosition = nodeBeforePosition->next;
}
currentNode->next = nodeBeforePosition->next;
nodeBeforePosition->next = currentNode;
}
void reverse_list(struct Node **headNode) {
struct Node *iterator = *headNode;
struct Node *previousNode = NULL;
struct Node *nextNode = NULL;
while (iterator != NULL) {
nextNode = iterator->next;
iterator->next = previousNode;
previousNode = iterator;
iterator = nextNode;
}
/* Iterator will be NULL by the end, so the last node will be stored in
previousNode. We will set the last node to be the headNode */
*headNode = previousNode;
}
Förklaring till metoden för omvänd lista
Vi startar previousNode
nod ut som NULL
, eftersom vi vet om den första iterationen av slingan, om vi letar efter noden före den första huvudnoden, kommer den att vara NULL
. Den första noden blir den sista noden i listan, och nästa variabel bör naturligtvis vara NULL
.
I princip är konceptet att vända den länkade listan här att vi faktiskt vänder länkarna själva. Varje nodes nästa medlem blir noden före den, så:
Head -> 1 -> 2 -> 3 -> 4 -> 5
Där varje nummer representerar en nod. Denna lista skulle bli:
1 <- 2 <- 3 <- 4 <- 5 <- Head
Slutligen bör huvudet peka på den 5: e noden istället, och varje nod bör peka på den nod som föregående av den.
Nod 1 bör peka på NULL
eftersom det inte fanns något förut. Nod 2 ska peka på nod 1, nod 3 ska peka på nod 2, et cetera.
Det finns emellertid ett litet problem med den här metoden. Om vi bryter länken till nästa nod och ändrar den till föregående nod, kommer vi inte att kunna gå till nästa nod i listan eftersom länken till den är borta.
Lösningen på detta problem är att helt enkelt lagra nästa element i en variabel ( nextNode
) innan du ändrar länken.
En dubbel länkad lista
Ett exempel på kod som visar hur noder kan sättas in i en dubbel länkad lista, hur listan lätt kan vändas och hur den kan skrivas ut i omvänd riktning.
#include <stdio.h>
#include <stdlib.h>
/* This data is not always stored in a structure, but it is sometimes for ease of use */
struct Node {
/* Sometimes a key is also stored and used in the functions */
int data;
struct Node* next;
struct Node* previous;
};
void insert_at_beginning(struct Node **pheadNode, int value);
void insert_at_end(struct Node **pheadNode, int value);
void print_list(struct Node *headNode);
void print_list_backwards(struct Node *headNode);
void free_list(struct Node *headNode);
int main(void) {
/* Sometimes in a doubly linked list the last node is also stored */
struct Node *head = NULL;
printf("Insert a node at the beginning of the list.\n");
insert_at_beginning(&head, 5);
print_list(head);
printf("Insert a node at the beginning, and then print the list backwards\n");
insert_at_beginning(&head, 10);
print_list_backwards(head);
printf("Insert a node at the end, and then print the list forwards.\n");
insert_at_end(&head, 15);
print_list(head);
free_list(head);
return 0;
}
void print_list_backwards(struct Node *headNode) {
if (NULL == headNode)
{
return;
}
/*
Iterate through the list, and once we get to the end, iterate backwards to print
out the items in reverse order (this is done with the pointer to the previous node).
This can be done even more easily if a pointer to the last node is stored.
*/
struct Node *i = headNode;
while (i->next != NULL) {
i = i->next; /* Move to the end of the list */
}
while (i != NULL) {
printf("Value: %d\n", i->data);
i = i->previous;
}
}
void print_list(struct Node *headNode) {
/* Iterate through the list and print out the data member of each node */
struct Node *i;
for (i = headNode; i != NULL; i = i->next) {
printf("Value: %d\n", i->data);
}
}
void insert_at_beginning(struct Node **pheadNode, int value) {
struct Node *currentNode;
if (NULL == pheadNode)
{
return;
}
/*
This is done similarly to how we insert a node at the beginning of a singly linked
list, instead we set the previous member of the structure as well
*/
currentNode = malloc(sizeof *currentNode);
currentNode->next = NULL;
currentNode->previous = NULL;
currentNode->data = value;
if (*pheadNode == NULL) { /* The list is empty */
*pheadNode = currentNode;
return;
}
currentNode->next = *pheadNode;
(*pheadNode)->previous = currentNode;
*pheadNode = currentNode;
}
void insert_at_end(struct Node **pheadNode, int value) {
struct Node *currentNode;
if (NULL == pheadNode)
{
return;
}
/*
This can, again be done easily by being able to have the previous element. It
would also be even more useful to have a pointer to the last node, which is commonly
used.
*/
currentNode = malloc(sizeof *currentNode);
struct Node *i = *pheadNode;
currentNode->data = value;
currentNode->next = NULL;
currentNode->previous = NULL;
if (*pheadNode == NULL) {
*pheadNode = currentNode;
return;
}
while (i->next != NULL) { /* Go to the end of the list */
i = i->next;
}
i->next = currentNode;
currentNode->previous = i;
}
void free_list(struct Node *node) {
while (node != NULL) {
struct Node *next = node->next;
free(node);
node = next;
}
}
Observera att det ibland är användbart att lagra en pekare till den sista noden (det är mer effektivt att helt enkelt kunna hoppa rakt till slutet av listan än att behöva iterera till slutet):
struct Node *lastNode = NULL;
I vilket fall behövs uppdatering av den efter ändringar i listan.
Ibland används en nyckel också för att identifiera element. Det är helt enkelt en medlem av nodstrukturen:
struct Node {
int data;
int key;
struct Node* next;
struct Node* previous;
};
Nyckeln används sedan när några uppgifter utförs på ett specifikt element, till exempel att ta bort element.