Списки Лекция 10 Списки Список – структура данных, представляющая собой конечную последовательность элементов. Элемент списка: Данные Связь • Односвязные (в том числе циклические ) • Двусвязные (в том числе циклические ) • Иерархические Односвязные списки Односвязный список – это список, у элементов которого существует связь, указывающая на следующий элемент списка ( односторонняя связь). Голова Хвост Односвязный список struct Tlist { char word[256]; //область данных; можно: char* word; int count; struct Tlist *next; //ссылка на следующий узел }; struct Tlist * Head = NULL; //голова списка struct Tlist * new_el(char newWord[]) { struct Tlist * p=(struct Tlist*) malloc( sizeof(struct Tlist)); if(p){ strcpy(p->word, newWord); //записать слово p->count = 1; //счетчик слов == 1 p->next = NULL; //следующего узла нет } return p; //результат функции – адрес элемента } Создание первого элемента списка struct Tlist *head=NULL; Struct Tlist *p; p = (struct Tlist*) malloc(sizeof(struct Tlist)); p->data = 5; p->next = NULL; head = p; head p 5 NULL Списки смежностей 1 2 2 4 1 2 3 5 3 4 5 3 5 4 4 NULL 5 1 3 4 struct Ie_list{ struct Ie_list *next; struct Tlist *simpleList; }; struct Ie_list *list; list=(struct Ie_list*)malloc(sizeof(struct Ie_list)); If (list) { list->next=NULL; list->simpleList = new_el(“hello!”); } Вставка элемента в односвязный список после элемента с адресом prev typedef struct Tlist *Node; //указатель на элемент void AddAfter (Node prev, char data[]) { Node cur = new_el(data); if (cur) { cur ->next = prev->next; prev->next = cur; } } Вставка элемента в голову односвязного списка Node AddFirst (Node Head, char data[]) { Node cur = new_el(data); if(cur) cur ->next = Head; return cur; } Удаление элемента из односвязного списка Проход по списку Node p = Head; while (p) { // пока не дошли p = p->next; // переходим } до конца (p!= NULL) к следующему Поиск элемента в списке Node Find (Node Head, char s[]) { Node q = Head; //лишняя переменная, while (q && strcmp(q->word, s)) q = q->next; return q; } достаточно Head Циклические списки Циклический список – это список, в котором связь последнего элемента указывает на первый или один из других элементов этого списка. head Задача: Для заданного односвязного списка определить, является ли он циклическим. Преобразовывать список нельзя. Двусвязные списки Двусвязные списки – это списки, элементы которых имеют по две связи, указывающие на предыдущий и следующий элементы. NULL head typedef struct list { int data; struct list *prev; struct list *next; } List; NULL Двусвязный список struct Tlist2 { char word[256]; //область данных struct Tlist2 *next; //ссылка на следующий узел struct Tlist2 *prev; //ссылка на предыдущий узел }; struct Tlist2 * Head = NULL; // голова списка struct Tlist2* new_el_2(char newWord[]) { struct Tlist2 * p=(struct Tlist2 *) malloc( sizeof(struct Tlist2)); if(p){ strcpy(p->word, newWord); //записать слово p->next = NULL; //следующего узла нет p->prev = NULL; //предыдущего узла нет } return p; //результат функции – адрес элемента } Удаление элемента после элемента с адресом cur q = cur->next; cur->next = q -> next; cur->next->next->prev = cur; free(q); Вставка элемента в двусвязный список после элемента с адресом pr cur = new_el_2(data); If cur) { cur ->next = pr->next; pr->next->prev = cur; pr->next = cur; cur->prev = pr; } Иерархические списки Это списки, значениями элементов которых являются указатели на другие списки (подсписки). NULL head Линейный список - это множество, состоящее из n (n≥0) узлов (элементов) X[1], X[2], … , X[n], структурные свойства которого ограничены линейным (одномерным) относительным положением узлов (элементов), т.е. следующими условиями: • если n > 0, то X[1] – первый узел; • если 1 < k < n, то k-му узлу X[k] предшествует узел X[k-1], а за узлом X[k] следует узел X[k+1]; • X[n] – последний узел. Операции над линейными списками 1. Получить доступ к k-му элементу списка, проанализировать и/или изменить значения его полей. 2. Включить новый узел перед k- м. 3. Исключить k-й узел. 4. Объединить два или более линейных списков в один. 5. Разбить линейный список на два или более линейных списков. 6. Сделать копию линейного списка. 7. Определить количество узлов. 8. Выполнить сортировку в возрастающем порядке по некоторым значениям полей в узлах. 9. Найти в списке узел с заданным значением в некотором поле. 10. … и т.д. Не все операции нужны одновременно! => Будем различать типы линейных списков по набору главных операций, которые над ними выполняются. Стек - это линейный список, в котором все включения и исключения (и всякий доступ) делаются на одном конце списка (вершина стека) Верх Включить или исключить Второй сверху Третий сверху Четвертый сверху Низ Операции работы со стеками 1. makenull (S) – делает стек S пустым 2. create(S) – создает стек 3. top (S) – выдает значение верхнего элемента стека, не удаляя его 4. pop(S) – выдает значение верхнего элемента стека и удаляет его из стека 5. push(x, S) – помещает в стек S новый элемент со значением x 6. empty (S) - если стек пуст, то функция возвращает 1 (истина), иначе – 0 (ложь). Стеки • • • • • • push-down список реверсивная память гнездовая память магазин LIFO (last-in-first-out) список йо-йо В современных компьютерах стек используется для • размещения локальных переменных; • размещения параметров процедуры или функции; • сохранения адреса возврата (по какому адресу надо вернуться из процедуры); • временного хранения данных. На стек выделяется ограниченная область памяти. При каждом вызове процедуры в стек добавляются новые элементы. Поэтому если вложенных вызовов будет много, стек переполнится. Опасной в отношении переполнения стека является рекурсия. Реализация стека strict Tlist { int data; struct Tlist * next; } typedef struct Tlist * Stack1; // в этом случае работа такая же, как // с односвязным списком typedef struct stack {struct Tlist * top; } Stack; void makenull (Stack *S){ struct Tlist *p; while (S->top) { p = S->top; S->top = p->next; free(p); } } Stack * create () { Stack *S; S = (Stack *)malloc(sizeof(Stack)); S->top = NULL; return S; } int top (Stack *S) { if (S->top) return (S->top->data); else return 0; //здесь может быть реакция на //ошибку – обращение к пустому стеку } int pop(Stack *S){ int a; struct Tlist *p; p = S->top; a = p->data; S-> top = p->next; free(p); return a; } void push(int a, Stack *S){ // return NULL - ? struct Tlist *p; p =(struct Tlist *)malloc(sizeof(struct Tlist)); if(p) { p->data = a; p->next = S-> top; S->top = p ; } } int empty (Stack *S) { return (S->top == NULL); } Очередь - это линейный список, в котором все включения производятся на одном конце списка, все исключения – на другом его конце Исключить Начало Включить Второй Третий Четвертый Конец Очереди • FIFO (first-in-first-out) –первый вошел, первый вышел Операции работы с очередями 1. makenull (Q) – делает очередь Q пустой 2. create(Q) – создает очередь 3. first (Q) – выдает значение первого элемента очереди, не удаляя его 4. get(Q)/outqueue(Q) – выдает значение первого элемента очереди и удаляет его из очереди 5. put(x, Q)/inqueue(x, Q) – помещает в конец очереди Q новый элемент со значением x 6. empty (Q) - если очередь пуста, то функция возвращает 1 (истина), иначе – 0 (ложь). Реализация очереди struct Tlist { into data; struct Tlist * next; } typedef struct queue { struct Tlist *first; struct Tlist *end; } Queue; Дек (double-ended queue) очередь с двумя концами - это линейный список, в котором все включения и исключения производятся на обоих концах списка Включить или исключить Левый конец Второй слева Включить или исключить Третий слева или справа Второй справа Правый конец