Как реализовать связанный список в C?
Я создаю список ссылок как и в предыдущем вопросе я спросил. Я обнаружил, что лучший способ разработать связанный список-это иметь голову и хвост в другой структуре. Моя структура продуктов будет вложена в эту структуру. И я должен передать список функции для добавления и удаления. Я нахожу эту концепцию запутанной.
я реализовал инициализацию, добавление и clean_up. Однако я не уверен, что сделал это. правильно.
когда я добавляю продукт в список, я объявляю некоторую память с помощью calloc. Но я думаю, не должен ли я объявлять память для продукта вместо этого. Я действительно смущен этим добавлением.
большое спасибо за любые предложения,
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRODUCT_NAME_LEN 128
typedef struct product_data
{
int product_code;
char product_name[PRODUCT_NAME_LEN];
int product_cost;
struct product_data_t *next;
}product_data_t;
typedef struct list
{
product_data_t *head;
product_data_t *tail;
}list_t;
void add(list_t *list, int code, char name[], int cost);
void initialize(list_t *list);
void clean_up(list_t *list);
int main(void)
{
list_t *list = NULL;
initialize(list);
add(list, 10, "Dell Inspiron", 1500);
clean_up(list);
getchar();
return 0;
}
void add(list_t *list, int code, char name[], int cost)
{
// Allocate memory for the new product
list = calloc(1, sizeof(list_t));
if(!list)
{
fprintf(stderr, "Cannot allocated memory");
exit(1);
}
if(list)
{
// First item to add to the list
list->head->product_code = code;
list->head->product_cost = cost;
strncpy(list->head->product_name, name, sizeof(list->head->product_name));
// Terminate the string
list->head->product_name[127] = '/0';
}
}
// Initialize linked list
void initialize(list_t *list)
{
// Set list node to null
list = NULL;
list = NULL;
}
// Release all resources
void clean_up(list_t *list)
{
list_t *temp = NULL;
while(list)
{
temp = list->head;
list->head = list->head->next;
free(temp);
}
list = NULL;
list = NULL;
temp = NULL;
}
============================== отредактировано ============================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PRODUCT_NAME_LEN 64
// typedef struct product_data product_data_t;
typedef struct product_data
{
int product_code;
char product_name[PRODUCT_NAME_LEN];
int product_cost;
}product_data_t;
typedef struct list
{
struct list *head;
struct list *tail;
struct list *next;
struct list *current_node;
product_data_t *data;
}list_t;
void add(list_t *list, int code, char name[], int cost);
int main(void)
{
list_t *list = NULL;
list = initialize(list);
add(list, 1001, "Dell Inspiron 2.66", 1299);
add(list, 1002, "Macbook Pro 2.66", 1499);
clean_up(list);
getchar();
return 0;
}
void add(list_t *list, int code, char name[], int cost)
{
/* Allocate memory for the new product */
product_data_t *product = (product_data_t*) calloc(1, sizeof(*product));
if(!product)
{
fprintf(stderr, "Cannot allocate memory.");
exit(1);
}
/* This is the first item in the list */
product->product_code = code;
product->product_cost = cost;
strncpy(product->product_name, name, sizeof(product->product_name));
product->product_name[PRODUCT_NAME_LEN - 1] = '';
if(!list->head)
{
/* Assign the address of the product. */
list = (list_t*) product;
/* Set the head and tail to this product */
list->head = (list_t*) product;
list->tail = (list_t*) product;
}
else
{
/* Append to the tail of the list. */
list->tail->next = (list_t*) product;
list->tail = (list_t*) product;
}
/* Assign the address of the product to the data on the list. */
list->data = (list_t*) product;
}
14 ответов
в вашем случае голова и хвост может просто указывать на начало и конец связанного списка. С одиночным связанным списком действительно нужна только голова. В самом основном, связанный список может быть сделан с помощью такой структуры, как:
typedef struct listnode
{
//some data
struct listnode *next;
}listnodeT;
listnodeT *list;
listnodeT *current_node;
list = (listnodeT*)malloc(sizeof(listnodeT));
current_node = list;
и пока list всегда указывает на начало списка, а последний элемент имеет следующий набор NULL, вы в порядке и можете использовать current_node для обхода списка. Но иногда, чтобы сделать переход по списку проще и сохранить любой другие данные о списке, маркер head и tail используются и завернуты в свою собственную структуру, как вы это сделали. Таким образом, ваши функции добавления и инициализации будут чем-то вроде (минус обнаружение ошибок)
// Initialize linked list
void initialize(list_t *list)
{
list->head = NULL;
list->tail = NULL;
}
void add(list_t *list, int code, char name[], int cost)
{
// set up the new node
product_data_t *node = (product_data_t*)malloc(sizeof(product_data_t));
node->code = code;
node->cost = cost;
strncpy(node->product_name, name, sizeof(node->product_name));
node->next = NULL;
if(list->head == NULL){ // if this is the first node, gotta point head to it
list->head = node;
list->tail = node; // for the first node, head and tail point to the same node
}else{
tail->next = node; // append the node
tail = node; // point the tail at the end
}
}
в этом случае, поскольку это один связанный список, хвост действительно полезен только для добавления элементов в список. Чтобы вставить элемент, вам придется пройти по списку, начиная с головы. Там, где хвост действительно пригодится, - это список с двойной связью, он позволяет вы должны пройти по списку, начиная с обоих концов. Вы можете пройти этот список, как
// return a pointer to element with product code
product_data_t* seek(list_t *list, int code){
product_data_t* iter = list->head;
while(iter != NULL)
if(iter->code == code)
return iter;
iter = iter->next;
}
return NULL; // element with code doesn't exist
}
часто голова и хвост полностью построены сами узлы, используемые в качестве часового для обозначения начала и конца списка. Они не хранят данные сами (скорее, их данные представляют собой маркер sentinel), они просто держатели места для передней и задней частей. Это может упростить кодирование некоторых алгоритмов, связанных со связанными списками, за счет необходимости иметь два дополнительных элементы. В целом, связанные списки представляют собой гибкие структуры данных с несколькими способами их реализации.
О да, и Ник прав, игра со связанными списками-отличный способ получить хорошие указатели и косвенность. И они также отличный способ практиковать рекурсию тоже! После того, как вы хорошо справились с linked-list, попробуйте построить дерево рядом и использовать рекурсию для ходьбы по дереву.
возможно, вы хотите, чтобы структура данных списка была внешней по отношению к данным, которые она хранит.
скажите, что у вас есть:
struct Whatever { int x_; }
тогда ваша структура списка будет выглядеть так:
struct Whatever_Node { Whatever_Node* next_ Whatever* data_ }
Райан Оберой прокомментировал аналогично, но без примера.
Если вы изучаете теорию указателей C, это хорошее упражнение. В противном случае это слишком много косвенности для кода, который не является общим (как в библиотеке).
вместо того, чтобы выделять статическую 128-байтовую символьную строку, вы можете сделать еще одну практику указателя и использовать выделенную строку точной длины, которую вы очищаете при выходе.
академически, kungfucraig
s' структура выглядит более общей, чем та, которую вы определили.
вы вызываете пространство для своей структуры list_t, просто указатели на список head и tail, что не то, что вы хотите сделать.
при добавлении в связанный список выделите место для фактического узла в списке, который является вашей структурой product_data_t.
вы выделяете неправильный кусок памяти. Вместо выделения памяти для каждого элемента списка, вы выделяете для списка head и tail.
для простоты избавьтесь от отдельной структуры для головы и хвоста. Сделайте их глобальными переменными (в той же области, что и сейчас) и измените их на listhead и listtail. Это сделает код намного более читаемым (вы не будете без необходимости проходить через отдельную структуру), и вы не сделаете ошибку выделение для неправильной структуры.
вам не нужен указатель хвоста, Если вы не собираетесь сделать дважды связанный список. Это не основной элемент для добавления после создания связанного списка, но и не обязательно.
Я не пишу код здесь, но вам нужно сделать следующее:
- Create и объект списка, это останется глобальным для длины программы.
- Malloc размер продукта _ data _ t.
- для первого элемента (head равно NULL) укажите head на адрес malloced.
- чтобы добавить следующий элемент, перейдите в конец списка, а затем добавьте указатель адреса malloced к следующему из последнего элемента. (Следующий из последнего элемента всегда будет NULL, так вот как вы пересекаете до конца.)
- забудь хвост на некоторое время.
в памяти ваши элементы связаны указателями в структуре списка
элемент1 -> место № 2
почему бы не сделать структуру списка частью вашего элемента?
затем вы выделяете элемент продукта, и структура каталогов внутри него.
typedef struct product_data
{
int product_code;
char product_name[PRODUCT_NAME_LEN];
int product_cost;
struct list_t list; // contains the pointers to other product data in the list
}product_data_t;
Я думаю, что сначала нужно представить бэк-энд. Код не имеет значения. Перейдите сюда и визуализируйте back-end basic C-код всей вставки. 1) вставка в начале посетите и прокрутите, чтобы получить каждое выполнение инструкций на back-end И тебе нужен фронт и воображение, иди сюда. задний конец imagin
и все другие возможные вставки здесь.
и важная вещь, которую вы можете использовать таким образом.
struct Node{ int data;//data field struct Node*next;//pointer field };
struct Node * head, * tail; / / попробуйте этот путь
или короткую стрижку
struct Node{ int data;//data field struct Node*next;//pointer field }*head,*tail; //global root pointer
и
клик > > визуализировать другую проблему связанного списка.
Спасибо.
демо Список Одиночных Ссылок. Если вы предпочитаете, попробуйте проверить Круговой Связанный Список и Дважды Связанный Список.
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int val;
struct node * next;
} node_t;
// Iterating over a list
void
print_list(node_t *head)
{
node_t *current = head;
while(current != NULL)
{
printf("%d\n", current->val);
current = current->next;
}
}
// Adding an item to the end of the list
void
push_end(node_t *head, int val)
{
node_t *current = head;
while (current->next != NULL)
{
current = current->next;
}
current->next = malloc(sizeof(node_t));
current->next->val = val;
current->next->next = NULL;
}
// Adding an item to the head of the list
void
push_head(node_t **head, int val)
{
node_t *new_node = NULL;
new_node = malloc(sizeof(node_t));
new_node->val = val;
new_node->next = *head;
*head = new_node;
}
// Removing the head item of the list
int
pop_head(node_t **head)
{
int retval = -1;
node_t *next_node = NULL;
if (*head == NULL) {
return -1;
}
next_node = (*head)->next;
retval = (*head)->val;
free(*head);
*head = next_node;
return retval;
}
// Removing the last item of the list
int
pop_last(node_t *head)
{
int retval = 0;
node_t *current = NULL;
if (head->next == NULL) {
retval = head->val;
free(head);
return retval;
}
/* get to the second to last node in the list */
current = head;
while (current->next->next != NULL) {
current = current->next;
}
/* now current points to the second to last item of the list.
so let's remove current->next */
retval = current->next->val;
free(current->next);
current->next = NULL;
return retval;
}
// Removing a specific item
int
remove_by_index(node_t **head, int n)
{
int i = 0;
int retval = -1;
node_t *current = *head;
node_t *temp_node = NULL;
if (n == 0) {
return pop_head(head);
}
for (i = 0; i < n - 1; i++) {
if (current->next == NULL) {
return -1;
}
current = current->next;
}
temp_node = current->next;
retval = temp_node->val;
current->next = temp_node->next;
free(temp_node);
return retval;
}
int
main(int argc, const char *argv[])
{
int i;
node_t * testnode;
for (i = 0; i < argc; i++)
{
push_head(&testnode, atoi(argv[i]));
}
print_list(testnode);
return 0;
}
// http://www.learn-c.org/en/Linked_lists
// https://www.geeksforgeeks.org/data-structures/linked-list/
реализация связанного списка, вдохновленная реализацией, используемой в ядре Linux:
// for 'offsetof', see: https://stackoverflow.com/q/6433339/5447906.
#include <stddef.h>
// See: https://stackoverflow.com/q/10269685/5447906.
#define CONTAINER_OF(ptr, type, member) \
( (type *) ((char *)(ptr) - offsetof(type, member)) )
// The macro can't be used for list head.
#define LIST_DATA(ptr, type, member) \
CONTAINER_OF(ptr, type, member);
// The struct is used for both: list head and list nodes.
typedef struct list_node
{
struct list_node *prev, *next;
}
list_node;
// List heads must be initialized by this function.
// Using the function for list nodes is not required.
static inline void list_head_init(list_node *node)
{
node->prev = node->next = node;
}
// The helper function, mustn't be used directly.
static inline void list_add_helper(list_node *prev, list_node *next, list_node *nnew)
{
next->prev = nnew;
nnew->next = next;
nnew->prev = prev;
prev->next = nnew;
}
// 'node' must be a list head or a part of a list.
// 'nnew' must not be a list head or a part of a list. It may
// be uninitialized or contain any data (even garbage).
static inline void list_add_after(list_node *node, list_node *nnew)
{
list_add_helper(node, node->next, nnew);
}
// 'node' must be a list head or a part of a list.
// 'nnew' must not be a list head or a part of a list. It may
// be uninitialized or contain any data (even garbage).
static inline void list_add_before(list_node *node, list_node *nnew)
{
list_add_helper(node->prev, node, nnew);
}
// 'node' must be part of a list.
static inline list_node *list_del(list_node *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
return node->prev;
}
пример использования:
#include <stdio.h>
// The struct must contain 'list_node' to be able to be inserted to a list
typedef struct
{
int data;
list_node node;
}
my_struct;
// Convert 'list_node *' to 'my_struct*' that contains this 'list_node'
static inline my_struct* get_my_struct(list_node *node_ptr)
{
return LIST_DATA(node_ptr, my_struct, node);
}
void print_my_list(list_node *head)
{
printf("list: {");
for (list_node *cur = head->next; cur != head; cur = cur->next)
{
my_struct *my = get_my_struct(cur);
printf(" %d", my->data);
}
printf(" }\n");
}
// Print 'cmd' and run it. Note: newline is not printed.
#define TRACE(cmd) \
(printf("%s -> ", #cmd), (cmd))
int main()
{
// The head of the list and the list itself. It doesn't contain any data.
list_node head;
list_head_init(&head);
// The list's nodes, contain 'int' data in 'data' member of 'my_struct'
my_struct el1 = {1};
my_struct el2 = {2};
my_struct el3 = {3};
print_my_list(&head); // print initial state of the list (that is an empty list)
// Run commands and print their result.
TRACE( list_add_after (&head , &el1.node) ); print_my_list(&head);
TRACE( list_add_after (&head , &el2.node) ); print_my_list(&head);
TRACE( list_add_before(&el1.node, &el3.node) ); print_my_list(&head);
TRACE( list_del (head.prev) ); print_my_list(&head);
TRACE( list_add_before(&head , &el1.node) ); print_my_list(&head);
TRACE( list_del (&el3.node) ); print_my_list(&head);
return 0;
}
результат выполнения кода выше:
list: { }
list_add_after (&head , &el1.node) -> list: { 1 }
list_add_after (&head , &el2.node) -> list: { 2 1 }
list_add_before(&el1.node, &el3.node) -> list: { 2 3 1 }
list_del (head.prev) -> list: { 2 3 }
list_add_before(&head , &el1.node) -> list: { 2 3 1 }
list_del (&el3.node) -> list: { 2 1 }
http://coliru.stacked-crooked.com/a/6e852a996fb42dc2
конечно, в реальной жизни вы, скорее всего, использовать malloc
для элементов списка.
на языке C нам нужно определить узел для хранения целочисленных данных и указатель на следующее значение.
struct Node{
int data;
struct Node *next;
};
чтобы добавить новый узел, у нас есть функция add, которая имеет данные в качестве параметра int. Сначала создаем новый узел n. Если программа не создает n, то мы печатаем сообщение об ошибке и возвращаемся со значением -1. Если мы создадим n, мы установим данные n, чтобы иметь данные параметра, а следующий будет содержать корень, поскольку он имеет верхнюю часть стека. После этого мы установите корень для ссылки на новый узел n.
#include <stdio.h>
struct node
{
int data;
struct node* next;
};
int main()
{
//create pointer node for every new element
struct node* head = NULL;
struct node* second = NULL;
struct node* third = NULL;
//initialize every new pointer with same structure memory
head = malloc(sizeof(struct node));
second = malloc(sizeof(struct node));
third = malloc(sizeof(struct node));
head->data = 18;
head->next = second;
second->data = 20;
second->next = third;
third->data = 31;
third->next = NULL;
//print the linked list just increment by address
for (int i = 0; i < 3; ++i)
{
printf("%d\n",head->data++);
return 0;
}
}
Это простой способ понять, как работает указатель с указателем. Здесь вам нужно просто создать приращение указателя с новым узлом, чтобы мы могли сделать его автоматическим.
перейти STL маршрут. Объявление связанных списков должно быть агностическим для данных. Если вам действительно нужно написать его самостоятельно, посмотрите, как он реализован в STL или Boost.
вы даже не должны держать указатель *next со структурой данных. Это позволяет использовать структуру данных продукта в различных структурах данных-деревьях, массивах и очередях.
Надеюсь, эта информация поможет в вашем дизайнерском решении.
Edit:
с пост помеченные C, у вас есть эквивалентные реализации с использованием указателей void*, которые следуют основному принципу проектирования. Например, проверьте: