- •СТРУКТУРЫ ДАННЫХ И АЛГОРИТМЫ
- •Глава 1 Линейные списки
- •Понятие структуры данных
- •Тип указатель
- •Линейный список
- •Линейный однонаправленный (односвязный) список
- •Линейный двунаправленный (двусвязный) список
- •Практическое задание «Линейные списки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 2 Структуры данных стек и очередь
- •Понятие структуры данных стек и очередь
- •Структура данных стек
- •Структура данных очередь
- •Пример решения задачи с использованием стека
- •Практическое задание «Стеки и очереди»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 3 Древовидные структуры данных
- •Понятие структуры данных дерево
- •Представление структуры бинарное дерево в памяти
- •Обход дерева
- •Основные операции над деревом
- •Ввод дерева
- •Практическое задание «Бинарные деревья»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 4 Структура данных таблица
- •Понятие структуры данных таблица
- •Представление таблицы в памяти
- •Организация хеш-таблиц
- •Выбор функции расстановки
- •Разрешение коллизии
- •Операции над таблицами
- •Практическое задание «Управление таблицами»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 5 Алгоритмы упорядочения данных
- •Понятие операции упорядочения
- •Алгоритмы упорядочения
- •Сортировка слиянием
- •Основные алгоритмы сортировки посредством сравнений
- •Улучшенные алгоритмы сортировки сравнением
- •Практическое задание «Алгоритмы сортировки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Библиографический список
Память, занимаемая динамическим объектом, на который указывают a и b освобождена, но указатель a сохранил значение адреса объекта. Указатель a – “висячая” ссылка.
Обобщенным обозначением динамической структуры данных является термин список.
Список – это конечное множество (возможно пустое) данных, имеющих некоторый общий смысл для решаемой задачи, размерность множества может быть заранее неизвестна. Рекурсивное определение списка:
<список> = <пустой список> │ <непустой список> <непустой список> = <элемент из множества объектов>│
<непустой список> <элемент из множества объектов>
Рекурсивное определение списка, данное выше, отражает рекурсивную природу самой динамической структуры.
Линейный список
Линейный список – это состоящее из n 0 элементов (x1, x2, … , xn) множество, структурные свойства которого ограничиваются линейным относительным положением элементов. Другими словами, если n > 0, то существует элемент, который является первым – x1, и есть последний элемент – xn, а для всех 1< k < n у xk есть следующий за ним элемент – xk + 1 и предшествующий ему элемент – xk - 1.
В статической памяти список можно представить как множество данных, которые располагаются последовательно и непрерывно друг за другом и это множество дополнено информацией, которая позволяет вносить изменения в динамическую структуру данных. Будем называть такой список (условно) – статический список. На языке C статический список можно представить неоднородной структурой:
struct list
{
int n;
15
type elem[k];
};
Здесь элемент с именем n определяет фактическое количество данных (элементов) в списке, если n равно 0, то список пуст, если n равно k, то список полон; элемент с именем elem (в данном случае массив) определяет само множество элементов списка, type – тип элемента списка, k – константа, задающая размер структуры.
Преимущества несвязного отображения:
1)возможен прямой доступ к любому элементу;
2)быстрое продвижение от элемента к элементу.
Недостатки несвязного отображения: сложно изменять список – при удалении и включении элемента в список требуется смещение элементов.
В динамической памяти список можно представить как множество данных, расположенных в памяти произвольно (в процессе их создания) и связанных между собой указателями. Будем называть такой список (условно) – динамический список.
Преимущества связного отображения:
1)легко изменить список: для включения или исключения элемента, достаточно изменить связи между элементами;
2)легко объединить или разбить список на части;
3)возможно образовать сложную и даже нелинейную структуру. Недостатки связного отображения:
1)требуется больше памяти (для хранения указателей, связываю-
щих элементы); 2) доступ к элементам только последовательный.
Линейный однонаправленный (односвязный) список
Линейный односвязный список – это динамический список, каждый элемент которого состоит из двух полей. Одно поле содержит информацию (или ссылку на нее), другое поле содержит ссылку на следующий элемент списка. Элемент списка называют «звено» списка. Таким образом, в данном случае список – это цепочка связанных между собой звеньев от первого до последнего. По однонаправленному
16
списку можно двигаться только в одном направлении – от первого (или заглавного) звена к последнему.
В качестве примера представим последовательность символов
BETA в виде списка: |
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B |
|
|
|
E |
|
|
T |
|
|
A |
NULL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 1.1. |
|
|
|||
Последнее звено (рис. 1.1.) не ссылается на следующий элемент, поэтому поле ссылки имеет значение NULL – пустой указатель. Передвигаться в этом списке можно только от первого элемента к последнему.
B 
E 
T 
A
Рис. 1.2.
Последнее звено ссылается на первый элемент списка (рис. 1.2.) – это циклический список. Здесь можно от любого элемента добраться к любому, но только в одном направлении.
Список имеет заглавное (фиктивное) звено (рис. 1.3.), которое ссылается на первый элемент списка. Его информационное поле, как правило, не используется. Заглавное звено позволяет обрабатывать первое звено списка так же, как и другие его звенья, поскольку у первого звена есть предшествующий элемент.
B |
|
|
E |
|
|
T |
|
|
A |
NULL |
|
|
|
|
|
|
|
|
|
|
|
Заглавное
звено
Рис. 1.3.
17
Для задания динамического списка надо описать его звено. Так как звено состоит из полей разных типов, то описать его можно неоднородным типом – структурой. Задание типа элемента списка:
struct list
{
list *next; type elem;
};
Здесь type – тип информационного поля элемента списка, поле next
– ссылка на аналогичную структуру типа list. Если информационное поле имеет сложную структуру, то type может быть типом указатель на объект, содержащий значение элемента списка. Для примера 1 элемент списка может быть определен:
struct list
{
list *next; char elem;
};
Для работы со списком как с единым объектом, надо ввести в употребление статическую переменную-указатель (заголовок списка), значение которой – это адрес первого (или заглавного) звена списка. Первый элемент списка называют головой списка, последний элемент хвостом списка.
Определение статической переменной-указателя:
list *l;
l |
|
|
|
|
|
B |
|
|
E |
|
|
T |
|
|
A |
NULL |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 1.4.
18
Статическая переменная-указатель l определяет список как единый объект. Используя указатель, можно обращаться к элементам списка
(см. рис. 1.4.):
//Указатель на заглавное звено списка
l
//Указатель на первое звено списка (голову списка) l->next
//Указатель на второе звено списка
l->next->next
//Указатель на третье звено списка l->next->next->next
//Обращение к информационному полю первого элемента l->next->elem
Для работы со списком необходимо ввести еще один указатель, который ссылается на текущий элемент списка.
Инициализация списка:
//Создание пустого списка без фиктивного звена l = NULL;
//Создание пустого ациклического списка
//c фиктивным звеном
l = new list; l->next = NULL;
//Создание пустого циклического списка
//c фиктивным звеном
19
l = new list; l->next = l;
Формирование ациклического списка c заглавным звеном:
struct list
{
list *next; char elem;
} *l;
void main()
{
char c = 0;
// Создание заглавного звена:
l = new list; |
// ph получил значение адреса |
|
// заглавного звена, |
l->next = NULL; |
// его полю next присвоено |
|
// значение пустой ссылки. |
|
// Таким образом, создан пустой |
|
// список с заглавным звеном. |
list *p = l; |
// Текущему указателю p присвоена |
// ссылка на заглавное звено while ((c = getchar()) != ‘\n’)
{
p->next = new list; // |
Создание очередного звена, |
// |
поле next текущего звена |
// |
получило значение адреса |
// |
вновь созданного звена. |
|
p = p->next; // |
Текущему указателю p присвоена |
|
// |
ссылка на очередное звено. |
|
p->elem = c; // |
Информационное поле elem текущего |
} |
// звена получило значение символа c. |
|
p->next = NULL; // |
Определение конца списка: |
|
|
// |
ccылочному полю последнего |
|
|
20 |
} |
// |
сформированного звена (хвост списка) |
|
// |
присвоено значение пустой ссылки. |
Основными операциями обработки односвязного списка являют-
ся:
1)поиск заданного элемента по его значению или порядковому номеру; операция заканчивается, когда элемент найден или просмотрен весь список (если элемента нет); результат операции должен определять, есть ли элемент в списке или нет и, если есть, то возможно его адрес или значение;
2)включение (вставка) в список нового элемента перед или после заданного элемента (в том числе перед первым элементом или после последнего); включению, как правило, предшествует поиск элемента после и/или перед которым происходит включение; при включении элемента перед первым в список без заглавного звена меняется заголовок списка; при включении после некоторого элемента меняется ссылочное поле у элемента, за которым происходит включение, для этого надо определять ссылку на элемент за которым происходит включение;
3)удаление (исключение) заданного элемента из списка (в том числе удаление первого элемента или последнего); исключению, как правило, предшествует поиск, исключаемого элемента; результатом поиска должна быть ссылка на элемент предшествующий исключаемому, так как при удалении элемента из списка меняется ссылочное поле у элемента, предшествующего удаляемому; при удалении первого элемента в списке без заглавного звена меняется заголовок списка;
4)определение числа звеньев списка;
5)копирование списка;
6)объединение списков;
7)упорядочение элементов списка по значению информационного поля и т. д.
Определим тип список:
struct list
{
list *next = NULL;
21
int elem = 0;
};
Примеры операции поиск элемента в линейном односвязном ациклическом списке без фиктивного звена:
– поиск по значению
list *find1(list *l, int x)
{
for ( ; l != NULL && l->elem! = x; l = l->next); return l;
}
– поиск по значению – рекурсивная реализация операции
list *find2(list *l, int x)
{
if (l == NULL) return NULL;
if (l->elem == x) return l;
return find2(l->next, x);
}
Примеры операции включение элемента в линейный односвязный ациклический список без фиктивного звена:
–- включение элемента в начало списка
void insert1(list **l, int x)
{
list *p = new list; p->elem = x; p->next= *l;
*l = p;
}
22
– включение элемента в конец списка
list *insert2(list *l, int x)
{
list *p = new list, *q = l; p->elem = x;
p->next= NULL; if (!l)
q = p; else
{
while (l->next) l = l->next;
l->next = p;
}
return q;
}
– включение элемента x после элемента y в непустой список
int insert3(list *l, int x, int y)
{
while (l != NULL && l->elem != y) l = l->next;
if (l)
{
list *p = new list; p->elem = x; p->next = l->next; l->next = p; return 1;
}
else
return 0;
}
23
Примеры операции удаление элемента из линейного односвязного ациклического списка:
– удаление по значению в списке без фиктивного звена
int del1(list **l, int x)
{
if (*l)
if ((*l)->elem == x)
{
list *p = *l; *l = p->next; delete p; return 1;
}
else
{
list *p = *l;
while (p->next != NULL && p->next->elem != x) p = p->next;
if (p->next)
{
list *q = p->next; p->next = q->next; delete q;
return 1;
}
else
return 0;
}
else
return 0;
}
– удаление по значению в списке с фиктивным звеном
24
