- начало списка - это замок->next.
- конец списка - это замок->prev.
Двусвязанный список
Пусть в нашем списке хранятся данные - целые числа.typedef int Data;
struct Node { Data data; // данные struct Node * prev; // указатель на предыдущий узел списка struct Node * next; // указатель на следующий узел списка };
Создаем заглушку и пишем функцию печати
Для понимания, как устроен список и как его перебрать с начала до конца, сделаем список из узлов "руками" и попробуем пройтись по ним.#include <stdio.h> typedef int Data; // покажем список на примере целых чисел struct Node { // один узел списка Data data; // данные struct Node * prev; // указатель на предыдущий узел struct Node * next; // указатель на следующий узел }; int main() { struct Node z, // замковый элемент, данные - мусор (не знаем какие) a, b, c, d, f; // прочие узлы // сделаем список из чисел 5, 7, -3 // замковый элемент, данные - мусор z.next = &a; z.prev = &c; a.data = 5; a.next = &b; a.prev = &z; b.data = 7; b.next = &c; b.prev = &a; c.data = -3; c.next = &z; c.prev = &b; // для печати данных из списка нужно выполнить код // данные в z не печатаем, там мусор printf("%d ", a.data); // 5 printf("%d ", b.data); // 7 printf("%d ", c.data); // -3 printf("\n"); // или реализуем функцию печати списка и вызовем ее // list_print(&z); return 0; }
void list_print(struct Node * list) { struct Node * p; for ( p = list->next; // первая бусина - после замка p != list; // печатаем только бусины, стоп если дошли до замка p = p->next ) { printf("%d ", p->data); // печать чисел } printf("\n"); }
void list_print(struct Node * list) { struct Node * p; #ifdef LIST_DBG printf("LIST:\tthis=%p prev=%p next=%p\n", list, list->prev, list->next); // замок #endif for ( p = list->next; // первая бусина - после замка p != list; // печатаем только бусины, стоп если дошли до замка p = p->next ) { #ifdef LIST_DBG printf("%d\tthis=%p prev=%p next=%p\n", p->data, p, p->prev, p->next); // печать чисел #else printf("%d ", p->data); // печать чисел #endif } printf("\n"); }
#define LIST_DBG
gcc -Wall -Wextra -DLIST_DBG dlist0.c
List API
API - application programming interface - набор функций для работы. Для работы со списком напишем функции:// делает список пригодным для работы void list_init(struct Node * list); // вставка и удаление элемента БЕЗ выделения памяти (операция с узлами) void list_insert(struct Node * list, struct Node * t); void list_insert_before(struct Node * list, struct Node * t); void list_remove(struct Node * t); // вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d); struct Node * list_push_back(struct Node * list, Data d); // удаление 1 элемента С освобождением памяти Data list_pop_front(struct Node * list); Data list_pop_back(struct Node * list); Data list_delete(struct Node * t); // удаление всех элементов, кроме "замка" void list_clear(struct Node * list); // вспомогательные функции печати и проверки на пустоту void list_print (struct Node * list); int list_is_empty(struct Node * list);
Вставка одного элемента БЕЗ выделения памяти
Попробуем реализовать функциюvoid list_insert(struct Node * list, struct Node * t);
d.data = 10; // вставим узел d после узла a // вставим узел d после узла a a.next = &d; // запишем адрес 700 в поле next узла с адресом 100 b.prev = &d; // запишем адрес 700 в поле prev узла с адресом 200 d.next = &b; // запишем адрес 200 в поле next узла с адресом 700 d.prev = &a; // запишем адрес 100 в поле prev узла с адресом 700 // list_insert(&a, &d); // убедимся, что узлы вставились верно list_print(&z); // 5 10 7 -3
list_insert
Глядя на "заглушечный" код, реализуем функцию list_insert. Меняем:- a. на p->
- b. на n->
- &d на t
// вставляем узел t в список после узла p void list_insert(struct Node * p, struct Node * t) { // объявим новую переменную n - указатель на следующий узел списка после p // (в заглушке это был узел b) struct Node * n = p->next; // вставим узел t после узла p p->next = t; // запишем адрес 700 в поле next узла с адресом 100 n->prev = t; // запишем адрес 700 в поле prev узла с адресом 200 t->next = n; // запишем адрес 200 в поле next узла с адресом 700 t->prev = p; // запишем адрес 100 в поле prev узла с адресом 700 }
list_insert(&z, &d);
// вставляем узел t в список после узла p void list_insert(struct Node * p, struct Node * t) { // вставим узел t после узла p p->next->prev = t; // запишем адрес 700 в поле prev узла с адресом 200 t->next = p->next; // запишем адрес 200 в поле next узла с адресом 700 p->next = t; // запишем адрес 700 в поле next узла с адресом 100 t->prev = p; // запишем адрес 100 в поле prev узла с адресом 700 }
list_insert_before
Теперь напишем вставку узла f ПЕРЕД узлом b. Можно аналогично писать// вставляем узел t в список ПЕРЕД узлом n void list_insert_before(struct Node * n, struct Node * t) { // объявим новую переменную p - указатель на ПРЕДЫДУЩИЙ узел списка перед n struct Node * p = n->prev; // далее код не изменился: // вставим узел t после узла p p->next = t; // запишем адрес 700 в поле next узла с адресом 100 n->prev = t; // запишем адрес 700 в поле prev узла с адресом 200 t->next = n; // запишем адрес 200 в поле next узла с адресом 700 t->prev = p; // запишем адрес 100 в поле prev узла с адресом 700 }
// вставляем узел t в список перед узлом n void list_insert_before(struct Node * n, struct Node * t) { list_insert(n->prev, t); }
list_remove
Операция удаления одного элемента из списка - это обратная операция вставке элемента в список. Напишем ее сразу в виде функции (проще ее реализовать через локальные переменные p и n - указатели на предыдущий и следующий после t узлы в списке:// удаляем узел t из списка void list_remove(struct Node * t) { struct Node * p = t->prev; struct Node * n = t->next; p->next = n; n->prev = p; }
Инициализация списка
Мы делали "заглушку" в main - создавали список из узлов a, b, c вручную. Сделаем пустой список так, чтобы в него можно было добавлять узлы с помощью list_insert и list_insert_before. Пустой список тоже должен быть циклическим и содержать барьерный элемент ("замок"). Он не должен содержать других элементов. То есть в main код должен быть таким:struct Node z; struct Node * list = &z; list_init(list);
// инициализация списка: следующий и предыдущий для "замка" - сам "замок" void list_init(struct Node * list) { list->next = list; list->prev = list; }
int list_is_empty(struct Node * list) { // функция возвращает ИСТИНУ, если список пустой (только "замок"), иначе возвращает ЛОЖЬ // тут нужно написать 1 строку кода }
int main() { struct Node z; // "замок" struct Node * list = &z; // указатель на список struct Node a = {5}, b = {7}, c = {-3}, d = {10}, f = {22}; list_init(list); list_print(list); // ничего не печатается printf("is_empty = %d\n", list_is_empty(list)); // ДА list_insert(list, &c); // очень похоже на стек? printf("is_empty = %d\n", list_is_empty(list)); // НЕТ list_insert(list, &b); list_insert(list, &a); list_print(list); // 5 7 -3 printf("is_empty = %d\n", list_is_empty(list)); // НЕТ list_insert(list->next, &d); list_insert_before(list, &f); list_print(list); // 5 10 7 -3 22 list_remove(&d); list_remove(&f); list_print(list); // 5 7 -3 return 0; }
Вставка и удаление данных + работа с памятью
Напишем следующий блок функций, который вставляет данные и удаляет данные из списка, выделяя и освобождая память динамически. Сравним прототипы функций list_insert и list_push. Видно, что в первую функцию передается готовый узел t, а во вторую - только данные data.void list_insert(struct Node * list, struct Node * t); struct Node * list_push_front(struct Node * list, Data data);
void test_memory() { /* тестируем функции работы со списком // вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d); struct Node * list_push_back(struct Node * list, Data d); // удаление 1 элемента С освобождением памяти Data list_pop_front(struct Node * list); Data list_pop_back(struct Node * list); Data list_delete(struct Node * t); // удаление всех элементов, кроме "замка" void list_clear(struct Node * list); */ printf("------------------------ Test push/pop/delete function, use valgring now!\n"); struct Node z; struct Node * list = &z; list_init(list); list_push_front(list, 5); list_push_front(list, 7); list_push_back(list, 10); list_push_back(list, -3); list_push_back(list, 22); list_print(list); // 5 7 10 -3 22 printf("popped front: %d\t", list_pop_front(list)); // 5 list_print(list); // 7 10 -3 22 printf("popped back: %d\t", list_pop_back(list)); // 22 list_print(list); // 7 10 -3 printf("delete node: %d\t", list_delete(list->next->next)); // 10 list_print(list); // 7 -3 list_clear(list); printf("after clear: list_is_empty %d\n", list_is_empty(list)); }
добавление данных в список
Так как в этих функциях в аргументах передаются данные типа Data, а не адреса узлов, сначала необходимо выделить под узлы память и записать в узлы данные.// вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d) { // выделим память для нового узла t struct Node * t = malloc(sizeof(struct Node)); t->data = d; // запишем в узел данные list_insert(list, t); // вставим узел в список (с начала) return t; // вернем указатель на новый узел }
удаление данных, очистка списка
Начнем с самой общей функции удаления 1 конкретного узла из списка и освобождения занимаемой узлом памяти. Функция list_delete должна возвращать данные, которые хранятся в этом узле.// удаление 1 элемента С освобождением памяти Data list_delete(struct Node * t) { Data d = t->data; // запомним данные, которые нужно возвращать list_remove(t); // вытащим узел из списка free (t); // освободим память (разрушим узел!) return d; // вернем данные, которые хранились в разрушенном узле }
Код в одном файле
Для удобства посылки в проверяющую систему, все, что не нужно посылать на сервер (декларация типов и функция main) заключены в команды условной компиляции#ifdef AAA ... #endif
#include <stdio.h> #include <stdlib.h> #ifdef AAA typedef int Data; // покажем список на примере целых чисел struct Node { // один узел списка Data data; // данные struct Node * prev; // указатель на предыдущий узел struct Node * next; // указатель на следующий узел }; // делает список пригодным для работы void list_init(struct Node * list); // вставка и удаление элемента БЕЗ выделения памяти (операция с узлами) void list_insert(struct Node * list, struct Node * t); void list_insert2(struct Node * list, struct Node * t); void list_insert_before(struct Node * list, struct Node * t); void list_remove(struct Node * t); // вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d); struct Node * list_push_back(struct Node * list, Data d); // удаление 1 элемента С освобождением памяти Data list_pop_front(struct Node * list); Data list_pop_back(struct Node * list); Data list_delete(struct Node * t); // удаление всех элементов, кроме "замка" void list_clear(struct Node * list); // вспомогательные функции печати и проверки на пустоту void list_print (struct Node * list); int list_is_empty(struct Node * list); int test_pointers() { struct Node z; // "замок" struct Node * list = &z; // указатель на список struct Node a = {5}, b = {7}, c = {-3}, d = {10}, f = {22}; printf("------------------------ Test init, print, insert/remove functions\n"); list_init(list); list_print(list); // ничего не печатается printf("is_empty = %d\n", list_is_empty(list)); list_insert(list, &c); // очень похоже на стек? list_insert(list, &b); list_insert(list, &a); list_print(list); // 5 7 -3 printf("is_empty = %d\n", list_is_empty(list)); list_insert(list->next, &d); list_insert_before(list, &f); list_print(list); // 5 10 7 -3 22 list_remove(&d); list_remove(&f); list_print(list); // 5 7 -3 return 0; } void test_memory() { /* тестируем функции работы со списком // вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d); struct Node * list_push_back(struct Node * list, Data d); // удаление 1 элемента С освобождением памяти Data list_pop_front(struct Node * list); Data list_pop_back(struct Node * list); Data list_delete(struct Node * t); // удаление всех элементов, кроме "замка" void list_clear(struct Node * list); */ printf("------------------------ Test push/pop/delete function, use valgring now!\n"); struct Node z; struct Node * list = &z; list_init(list); list_push_front(list, 5); list_push_front(list, 7); list_push_back(list, 10); list_push_back(list, -3); list_push_back(list, 22); list_print(list); // 5 7 10 -3 22 printf("popped front: %d\t", list_pop_front(list)); // 5 list_print(list); // 7 10 -3 22 printf("popped back: %d\t", list_pop_back(list)); // 22 list_print(list); // 7 10 -3 printf("delete node: %d\t", list_delete(list->next->next)); // 10 list_print(list); // 7 -3 list_clear(list); printf("after clear: list_is_empty %d\n", list_is_empty(list)); } int main() { test_pointers(); test_memory(); return 0; } #endif void list_print(struct Node * list) { struct Node * p; #ifdef LIST_DBG printf("LIST:\tthis=%p prev=%p next=%p\n", list, list->prev, list->next); // замок #endif for ( p = list->next; // первая бусина - после замка p != list; // печатаем только бусины, стоп если дошли до замка p = p->next ) { #ifdef LIST_DBG printf("%d\tthis=%p prev=%p next=%p\n", p->data, p, p->prev, p->next); // печать чисел #else printf("%d ", p->data); // печать чисел #endif } printf("\n"); } // вставляем узел t в список после узла p void list_insert(struct Node * p, struct Node * t) { // объявим новую переменную n - указатель на следующий узел списка после p // (в заглушке это был узел b) struct Node * n = p->next; // вставим узел t после узла p p->next = t; // запишем адрес 700 в поле next узла с адресом 100 n->prev = t; // запишем адрес 700 в поле prev узла с адресом 200 t->next = n; // запишем адрес 200 в поле next узла с адресом 700 t->prev = p; // запишем адрес 100 в поле prev узла с адресом 700 } // вставляем узел t в список перед узлом n void list_insert_before(struct Node * n, struct Node * t) { list_insert(n->prev, t); } // удаляем узел t из списка void list_remove(struct Node * t) { struct Node * p = t->prev; struct Node * n = t->next; p->next = n; n->prev = p; } // инициализация списка: следующий и предыдущий для "замка" - сам "замок" void list_init(struct Node * list) { list->next = list; list->prev = list; } // вставка элемента С выделением памяти struct Node * list_push_front(struct Node * list, Data d) { // выделим память для нового узла t struct Node * t = malloc(sizeof(struct Node)); t->data = d; // запишем в узел данные list_insert(list, t); // вставим узел в список (с начала) return t; // вернем указатель на новый узел } struct Node * list_push_back(struct Node * list, Data d) { // тут нужно написать код } // удаление 1 элемента С освобождением памяти Data list_delete(struct Node * t) { Data d = t->data; // запомним данные, которые нужно возвращать list_remove(t); // вытащим узел из списка free (t); // освободим память (разрушим узел!) return d; // вернем данные, которые хранились в разрушенном узле } Data list_pop_front(struct Node * list) { // тут нужно написать код } Data list_pop_back(struct Node * list) { // тут нужно написать код } // удаление всех элементов, кроме "замка" void list_clear(struct Node * list) { // тут нужно написать код } int list_is_empty(struct Node * list) { // тут нужно написать код }
Дополнительные задачи
Реализуйте функции Функция ищет первое вхождение данных d в список, возвращает указатель на найденный узел или NULL, если данных d нет в списке.struct Node * list_find(struct Node * list, Data d);
struct Node * list_find_back(struct Node * list, Data d);
int list_size(struct Node * list);
int list_count(struct Node * list, Data d);
void list_revers(struct Node * list);
-- TatyanaDerbysheva - 11 Feb 2019