Эта статья призвана резюмировать приобретенные знание полученные в процессе обучения программированию. Так вышло, что я попал на обучение на программиста, скажем так, «по-взрослому». Поэтому первым языком стал Си. Основы языка дались легко благодаря опыту работы с языками программирования PHP, JavaScript, C# и Python.
Но позднее процесс обучения вышел на новый уровень, связанный со структурами данных. И начались мои страдания.
Оказалось, что моего опыта «кодинга» всякой мелочи недостаточно, опытные пользователи бы сказали, что в интернете существует множество статей по реализации тех же списков, но они мне не подходили. В конечном итоге до всего пришлось доходить самостоятельно, лишь используя имеющиеся статьи. Теперь в этой серии статей я попытаюсь зафиксировать свой опыт. Я всего лишь еще учусь, поэтому в решениях возможны некорректность в реализации, поэтому советам по улучшениям буду рад. А быть может мои статьи помогут еще кому-нибудь.
В чем особенность этих статей? Как говорят еще в научном сообществе — актуальность, новизна. Каждое решение будет ориентированно и оптимизировано на работу с большим объемом данных. И по крайней мере я попытаюсь так сделать. Все задачи будут решены с использованием языка Си.
09 2 Списки: пример реализации (Голубев В.И., 2019)
Статей будет несколько, каждая будет посвящена одному конкретному решению и его реализации. В каждой статье оставлю ссылки на другие, по другим решениям.
Условия реализации следующие. Все программы будут реализовываться для работы в ОС Ubuntu. Возможен их запуск в Unix-подобных системах. Другие специфические особенности будут мной указаны непосредственно в самой статье по конкретному решению.
Ну а теперь к делу. Реализация простого варианта списка:
Как мы знаем, всё везде у нас состоит из всяких списков. Куда не плюнь, всюду эти списки. Даже хлеб не купишь без списка, а купив, тоже получишь список. Так как же нам реализовать список. Давайте размышлять.
В самом простом, элементарном варианте, наш список состоит из двух вещей: Первое — общая структура списка, и второе — структура элемента списка. Попробуем реализовать эту радость.
Создадим заголовочный файл: database.h:
#include // стандартная библиотека Си #include // для работы со строками #include // для работы с памятью // структура элемента списка typedef struct list_item < void *data; // по этому указателю мы храним какие-то данные struct list_item *next; // это у нас ссылка на следующий указатель struct list_item *prev; // это у нас ссылка на предыдущий указатель >list_item; // Общая структура списка typedef struct list < int count; // информация о размере списка list_item *head; // это ссылка на головной элемент list_item *tail; // это у нас ссылка на последний элемент (хвост списка) >list;
Как можно заметить, мы пытаемся реализовать двусвязный список, кроме того у нас присутствует дополнительная структура, которая хранит информацию о списке, его размер, где начало, а где его конец. Такая структура в дальнейшем облегчит нам жизнь в работе с этим списком.
Реализация односвязного списка c++ Часть 1 | Урок #133
Теперь с этим списком нам нужно произвести кое-какие операции. Для начала его нужно создать, как сущность, сам список, а затем добавить в него некоторые элементы, какие-то в начало, а какие-то в конец. С этими элементами мы затем будем работать, попытаемся найти по ключу, по значению, удалим какой-нибудь на выбор. Естественно мы захотим вывести весь список на экран и посмотреть, а что там вообще есть.
Попробуем реализовать все эти функции. В том же самом заголовочном файле «database.h» ниже под структурами обозначим прототипы функций.
list * db_create(); // создает список. возвращает список.
Перейдем теперь в файл «main.c» — это наш главный файл программы, который мы будем компилировать. Оформляем заготовок программы.
#include «database.h» // не забудем подключить наш заголовочный файл int main(int argc, const char** argv) < // code >
Поскольку прототип функции у нас уже есть в нашем заголовочном файле, ее реализацию мы можем написать ниже под функцией main.
list * db_create() < // Создадим указатель на переменную структуры списка и выделим немного памяти для нее list *lst = (list*)malloc(sizeof(list)); // задаем первоначальные значения lst->count = 0; // наш список пуст lst->head = NULL; // первого элемента у нас нет lst->tail = NULL; // и последнего тоже return lst; >
Возвращаемся в функцию main, вызываем наш список.
int main(int argc, const char** argv) < list *database = create(); // список мы создали >
Теперь мы немного с этим списком поработаем. А чтобы было с чем работать мы туда, для начала, попробуем добавить первые данные. Добавлять мы будем следующим образом, мы укажем некий индекс, который будет сигнализировать куда добавлять элемент в начало или конец, ну и сами данные в виде строки. Ну и также в нашу функцию мы будем передавать указатель на список, чтобы она понимала, куда ей это счастье наше добавлять.
В нашем заголовочном файле «database.h» ниже пишем новый прототип:
void db_insert(list *lst, int index, char *data);
В файле «main.c»:
void db_insert(list *lst, int index, char *data) < // создадим указатель переменной элемента списка, // и присвоим ему значение указателя на первый элемент списка list_item *base = lst->head; // создадим указатель переменной на новый элемент и выделим под него память list_item *new_item = (list_item*)malloc(sizeof(list_item)); // выделим память внутри самого элемента структуры куда принимаем данные, // и получим указатель на него, // strlen() нужен, чтобы выделенная память была равна длинне полученной строки. new_item->data = malloc(sizeof(char) * strlen(data)); strcpy(new_item->data, data); // копируем туда данные // Пришла пора решить куда мы определим элемент, // т.к. у нас еще нет элементов, lst->head вернет нам NULL. // Следовательно нужно условие, при создании первого элемента списка. if (base == NULL) < // Этот элемент единственный, а значит его указатели будут NULL. new_item->next = NULL; new_item->previous = NULL; // При этом, он сам будет первым и последним в списке. lst->first = new_item; lst->last = new_item; lst->count++; // Увеличем кол-во на единицу return; > // Если индекс, который пришел будет меньше нуля, то будем вставлять в конец if (index < 0) < // голова теперь будет ссылаться на новый элм. впереди себя base->prev = new_item; new_item->previous = NULL; new_item->next = base; // а ссылка на след. элм. у нового будет на голову lst->head = new_item; // назначаем новый элемент головой > else < // тут все в обратном порядке base = lst->tail; // перейдем в хвост списка // пусть он теперь ссылаеться на новый элемент base->next = new_item; new_item->next = NULL; // Новый не будет иметь ссылки на следующий new_prev->prev = base; // А предыдущий у него будет хвост списка lst->tail = new_item; // Назначаем новый элемент хвостом списка > lst->count++; // увеличим размер на единицу >
На этом функция вставки готова, теперь вставим что-нибудь в наш список. В функции main пишем.
int main(int argc, const char** argv) < list *database = create(); // список мы создали insert(database, 0, «One»); insert(database, 1, «Two»); insert(database, -1, «Three»); >
Теперь мы хотим получить какой-нибудь элемент, допустим мы знаем его индекс в списке. Возвращать мы будем его значение.
char * db_read(list *lst, int index) < list_item *base; int middle = lst->count / 2; // Вычисляем середину списка if (index > middle) < // если индекс больше середины base = lst->tail; for (int i = lst->count; i > index; i—) base = base->prev; > else < // если индекс меньше середины base = lst->head; for (int i = 0; i < index; i++) base = base->next; > // Если элемента нет if (base == NULL) < printf(«