Цель лекции: изучить понятия, классификацию и объявление списков, особенности доступа к данным и работу с памятью при использовании однонаправленных и
Понятие списка хорошо известно из жизненных примеров: список студентов учебной группы, список призёров олимпиады, список (перечень) документов для представления в приёмную комиссию, список почтовой рассылки, список литературы для самостоятельного чтения и т.п.
Списком называется упорядоченное множество, состоящее из переменного числа элементов, к которым применимы
Длина списка равна числу элементов, содержащихся в списке, список нулевой длины называется пустым списком. Списки представляют собой способ организации структуры данных, при которой элементы некоторого типа образуют цепочку. Для связывания элементов в списке используют систему указателей. В минимальном случае, любой элемент линейного списка имеет один указатель, который указывает на следующий элемент в списке или является пустым указателем, что интерпретируется как конец списка.
Структура, элементами которой служат записи с одним и тем же форматом, связанные друг с другом с помощью указателей, хранящихся в самих элементах, называют связанным списком. В связанном списке элементы линейно упорядочены, но порядок определяется не номерами, как в массиве, а указателями, входящими в состав элементов списка. Каждый список имеет особый элемент, называемый указателем начала списка (головой списка), который обычно по содержанию отличен от остальных элементов. В поле указателя NULL, свидетельствующий о конце списка.
Линейные связные списки являются простейшими
В основном они отличаются видом взаимосвязи элементов и/или допустимыми операциями.
Наиболее простой динамической структурой является однонаправленный список, элементами которого служат объекты
). В последнем элементе указатель на следующий элемент равен NULL.
(рис 29.1) Линейный однонаправленный списокОписание простейшего элемента такого списка выглядит следующим образом:
struct имя_типа { информационное поле; адресное поле; };
где информационное поле – это поле любого, ранее объявленного или стандартного, типа;
адресное поле – это указатель на объект того же типа, что и определяемая структура, в него записывается адрес следующего элемента списка.
Например:
struct Node {
int key;//информационное поле
Node*next;//адресное поле
};
Информационных полей может быть несколько.
Например:
struct point {
char*name;//информационное поле
int age;//информационное поле
point*next;//адресное поле
};
Каждый элемент списка содержит ключ, который идентифицирует этот элемент. Ключ обычно бывает либо целым числом, либо строкой.
Основными операциями, осуществляемыми с однонаправленными списками, являются:
Особое внимание следует обратить на то, что при выполнении любых операций с линейным однонаправленным списком необходимо обеспечивать позиционирование какого-либо указателя на первый элемент. В противном случае часть или весь список будет недоступен.
Рассмотрим подробнее каждую из приведенных операций.
Для описания алгоритмов этих основных операций используется следующее объявление:
struct Single_List {//структура данных
int Data; //информационное поле
Single_List *Next; //адресное поле
};
. . . . . . . . . .
Single_List *Head; //указатель на первый элемент списка
. . . . . . . . . .
Single_List *Current;
//указатель на текущий элемент списка (при необходимости)
Для того, чтобы создать список, нужно создать сначала первый элемент списка, а затем при помощи функции добавить к нему остальные элементы. При относительно небольших размерах списка наиболее изящно и красиво использование
//создание однонаправленного списка (добавления в конец)
void Make_Single_List(int n,Single_List** Head){
if (n > 0) {
(*Head) = new Single_List();
//выделяем память под новый элемент
cout << "Введите значение ";
cin >> (*Head)->Data;
//вводим значение информационного поля
(*Head)->Next=NULL;//обнуление адресного поля
Make_Single_List(n-1,((*Head)->Next));
}
}
Операция печати списка заключается в последовательном просмотре всех элементов списка и выводе их значений на экран. Для обработки списка организуется функция, в которой нужно переставлять указатель на следующий элемент списка до тех пор, пока указатель не станет равен NULL, то есть будет достигнут конец списка. Реализуем данную функцию рекурсивно.
//печать однонаправленного списка
void Print_Single_List(Single_List* Head) {
if (Head != NULL) {
cout << Head->Data << "\t";
Print_Single_List(Head->Next);
//переход к следующему элементу
}
else cout << "\n";
}
В динамические структуры легко добавлять элементы, так как для этого достаточно изменить значения адресных полей. Вставка первого и последующих элементов списка отличаются друг от друга. Поэтому в функции, реализующей данную операцию, сначала осуществляется проверка, на какое место вставляется элемент. Далее реализуется соответствующий алгоритм добавления (рис 29.2).
(рис 29.2) Вставка элемента в однонаправленный список/*вставка элемента с заданным номером в однонаправленный список*/
Single_List* Insert_Item_Single_List(Single_List* Head,
int Number, int DataItem){
Number--;
Single_List *NewItem=new(Single_List);
NewItem->Data=DataItem;
NewItem->Next = NULL;
if (Head == NULL) {//список пуст
Head = NewItem;//создаем первый элемент списка
}
else {//список не пуст
Single_List *Current=Head;
for(int i=1; i < Number Current->Next!=NULL; i++)
Current=Current->Next;
if (Number == 0){
//вставляем новый элемент на первое место
NewItem->Next = Head;
Head = NewItem;
}
else {//вставляем новый элемент на непервое место
if (Current->Next != NULL)
NewItem->Next = Current->Next;
Current->Next = NewItem;
}
}
return Head;
}
Из динамических структур можно удалять элементы, так как для этого достаточно изменить значения адресных полей.
Алгоритмы удаления первого и последующих элементов списка отличаются друг от друга. Поэтому в функции, реализующей данную операцию, осуществляется проверка, какой элемент удаляется. Далее реализуется соответствующий алгоритм удаления (рис 29.3).
(рис 29.3) Удаление элемента из однонаправленного списка/*удаление элемента с заданным номером из однонаправленного списка*/
Single_List* Delete_Item_Single_List(Single_List* Head,
int Number){
Single_List *ptr;//вспомогательный указатель
Single_List *Current = Head;
for (int i = 1; i < Number Current != NULL; i++)
Current = Current->Next;
if (Current != NULL){//проверка на корректность
if (Current == Head){//удаляем первый элемент
Head = Head->Next;
delete(Current);
Current = Head;
}
else {//удаляем непервый элемент
ptr = Head;
while (ptr->Next != Current)
ptr = ptr->Next;
ptr->Next = Current->Next;
delete(Current);
Current=ptr;
}
}
return Head;
}
Операция поиска элемента в списке заключается в последовательном просмотре всех элементов списка до тех пор, пока текущий элемент не будет содержать заданное значение или пока не будет достигнут конец списка. В последнем случае фиксируется отсутствие искомого элемента в списке (функция принимает значение false ).
//поиск элемента в однонаправленном списке
bool Find_Item_Single_List(Single_List* Head, int DataItem){
Single_List *ptr; //вспомогательным указатель
ptr = Head;
while (ptr != NULL){//пока не конец списка
if (DataItem == ptr->Data) return true;
else ptr = ptr->Next;
}
return false;
}
Операция удаления списка заключается в освобождении динамической памяти. Для данной операции организуется функция, в которой нужно переставлять указатель на следующий элемент списка до тех пор, пока указатель не станет равен NULL, то есть не будет достигнут конец списка. Реализуем
/*освобождение памяти, выделенной под однонаправленный список*/
void Delete_Single_List(Single_List* Head){
if (Head != NULL){
Delete_Single_List(Head->Next);
delete Head;
}
}
Таким образом, однонаправленный список имеет только один указатель в каждом элементе. Это позволяет минимизировать расход памяти на организацию такого списка. Одновременно это позволяет осуществлять переходы между элементами только в одном направлении, что зачастую увеличивает время, затрачиваемое на обработку списка. Например, для перехода к предыдущему элементу необходимо осуществить просмотр списка с начала до элемента, указатель которого установлен на текущий элемент.
Для ускорения многих операций целесообразно применять переходы между элементами списка в обоих направлениях. Это реализуется с помощью
). При этом два соседних элемента должны содержать взаимные ссылки друг на друга.
В таком списке каждый элемент (кроме первого и последнего) связан с предыдущим и следующим за ним элементами. Каждый элемент
(рис 29.4) Двунаправленный списокОписание простейшего элемента такого списка выглядит следующим образом:
struct имя_типа {
информационное поле;
адресное поле 1;
адресное поле 2;
};
где информационное поле – это поле любого, ранее объявленного или стандартного, типа;
адресное поле 1 – это указатель на объект того же типа, что и определяемая структура, в него записывается адрес следующего элемента списка ;
адресное поле 2 – это указатель на объект того же типа, что и определяемая структура, в него записывается адрес предыдущего элемента списка.
Например:
struct list {
type elem ;
list *next, *pred ;
}
list *headlist ;
где type – тип информационного поля элемента списка;
*next, *pred – указатели на следующий и предыдущий элементы этой структуры соответственно.
Переменная-указатель headlist задает список как единый программный объект, ее значение – указатель на первый (или заглавный) элемент списка.
Основные операции, выполняемые над двунаправленным списком, те же, что и для однонаправленного списка. Так как
Рассмотрим основные операции, осуществляемые с двунаправленными списками, такие как:
Особое внимание следует обратить на то, что в отличие от однонаправленного списка здесь нет необходимости обеспечивать позиционирование какого-либо указателя именно на первый элемент списка, так как благодаря двум указателям в элементах можно получить доступ к любому элементу списка из любого другого элемента, осуществляя переходы в прямом или обратном направлении. Однако по правилам хорошего тона программирования указатель желательно ставить на заголовок списка.
Для описания алгоритмов этих основных операций используется следующее объявление:
struct Double_List {//структура данных
int Data; //информационное поле
Double_List *Next, //адресное поле
*Prior; //адресное поле
};
. . . . . . . . . .
Double_List *Head; //указатель на первый элемент списка
. . . . . . . . . .
Double_List *Current;
//указатель на текущий элемент списка (при необходимости)
Для того, чтобы создать список, нужно создать сначала первый элемент списка, а затем при помощи функции добавить к нему остальные элементы. Добавление может выполняться как в начало, так и в конец списка. Реализуем
//создание двунаправленного списка (добавления в конец)
void Make_Double_List(int n,Double_List** Head,
Double_List* Prior){
if (n > 0) {
(*Head) = new Double_List();
//выделяем память под новый элемент
cout << "Введите значение ";
cin >> (*Head)->Data;
//вводим значение информационного поля
(*Head)->Prior = Prior;
(*Head)->Next=NULL;//обнуление адресного поля
Make_Double_List(n-1,((*Head)->Next),(*Head));
}
else (*Head) = NULL;
}
Операция печати списка для
//печать двунаправленного списка
void Print_Double_List(Double_List* Head) {
if (Head != NULL) {
cout << Head->Data << "\t";
Print_Double_List(Head->Next);
//переход к следующему элементу
}
else cout << "\n";
}
В динамические структуры легко добавлять элементы, так как для этого достаточно изменить значения адресных полей. Операция вставки реализовывается аналогично функции вставки для однонаправленного списка, только с учетом особенностей
(рис 29.5) Добавление элемента в двунаправленный список//вставка элемента с заданным номером в двунаправленный список
Double_List* Insert_Item_Double_List(Double_List* Head,
int Number, int DataItem){
Number--;
Double_List *NewItem=new(Double_List);
NewItem->Data=DataItem;
NewItem->Prior=NULL;
NewItem->Next = NULL;
if (Head == NULL) {//список пуст
Head = NewItem;
}
else {//список не пуст
Double_List *Current=Head;
for(int i=1; i < Number Current->Next!=NULL; i++)
Current=Current->Next;
if (Number == 0){
//вставляем новый элемент на первое место
NewItem->Next = Head;
Head->Prior = NewItem;
Head = NewItem;
}
else {//вставляем новый элемент на непервое место
if (Current->Next != NULL) Current->Next->Prior = NewItem;
NewItem->Next = Current->Next;
Current->Next = NewItem;
NewItem->Prior = Current;
Current = NewItem;
}
}
return Head;
}
Из динамических структур можно удалять элементы, так как для этого достаточно изменить значения адресных полей.
(рис 29.6) Удаление элемента из двунаправленного списка/*удаление элемента с заданным номером из двунаправленного списка*/
Double_List* Delete_Item_Double_List(Double_List* Head,
int Number){
Double_List *ptr;//вспомогательный указатель
Double_List *Current = Head;
for (int i = 1; i < Number Current != NULL; i++)
Current = Current->Next;
if (Current != NULL){//проверка на корректность
if (Current->Prior == NULL){//удаляем первый элемент
Head = Head->Next;
delete(Current);
Head->Prior = NULL;
Current = Head;
}
else {//удаляем непервый элемент
if (Current->Next == NULL) {
//удаляем последний элемент
Current->Prior->Next = NULL;
delete(Current);
Current = Head;
}
else {//удаляем непервый и непоследний элемент
ptr = Current->Next;
Current->Prior->Next =Current->Next;
Current->Next->Prior =Current->Prior;
delete(Current);
Current = ptr;
}
}
}
return Head;
}
Операция поиска элемента в двунаправленном списке реализуется абсолютно аналогично соответствующей функции для однонаправленного списка. Поиск элемента в двунаправленном списке можно вести:
а) просматривая элементы от начала к концу списка;
б) просматривая элементы от конца списка к началу;
в) просматривая список в обоих направлениях одновременно: от начала к середине списка и от конца к середине (учитывая, что элементов в списке может быть четное или нечетное количество).
//поиск элемента в двунаправленном списке
bool Find_Item_Double_List(Double_List* Head,
int DataItem){
Double_List *ptr; //вспомогательный указатель
ptr = Head;
while (ptr != NULL){//пока не конец списка
if (DataItem == ptr->Data) return true;
else ptr = ptr->Next;
}
return false;
}
Операция проверки
//проверка пустоты двунаправленого списка
bool Empty_Double_List(Double_List* Head){
if (Head!=NULL) return false;
else return true;
}
Операция удаления
//освобождение памяти, выделенной под двунаправленный список
void Delete_Double_List(Double_List* Head){
if (Head != NULL){
Delete_Double_List(Head->Next);
delete Head;
}
}
Пример 1. N -натуральных чисел являются элементами L, вычислить: X1*Xn+X2*Xn-1+...+Xn*X1. Вывести на экран каждое произведение и итоговую сумму.
Алгоритм:
Создание структуры, формирование списка и вывод на печать рассмотрены ранее. Приведем функции для реализации продвижения по списку в обоих направлениях и нахождения итоговой суммы.
//поиск последнего элемента списка
Double_List* Find_End_Item_Double_List(Double_List* Head){
Double_List *ptr; //дополнительный указатель
ptr = Head;
while (ptr->Next != NULL){
ptr = ptr->Next;
}
return ptr;
}
//итоговая сумма произведений
void Total_Sum(Double_List* Head) {
Double_List* lel = Head;
Double_List* mel = Find_End_Item_Double_List(Head);
int mltp,sum=0;
while(lel != NULL) {
mltp = (lel->Data)*(mel->Data);//умножение элементов
printf("\n\n%d * %d = %d",lel->Data,mel->Data,mltp);
sum = sum + mltp;//суммирование произведений
lel = lel->Next;
//идем по списку из первого элемента в последний
mel = mel->Prior;
//идем по списку из последнего элемента в первый
}
printf("\n\n Итоговая сумма равна %d",sum);
}
Двунаправленный (двусвязный) список – это структура данных, состоящая из последовательности элементов, каждый из которых содержит информационную часть и два указателя на соседние элементы.
Длина списка – это величина, равная числу элементов в списке.
Линейный список – это список, отражающий отношения соседства между элементами.
Однонаправленный (односвязный) список – это структура данных, представляющая собой последовательность элементов, в каждом из которых хранится значение и указатель на следующий элемент списка.
Пустой список – это список нулевой длины.
Связанный список – это структура, элементами которой служат записи одного формата, связанные друг с другом с помощью указателей, хранящихся в самих элементах.
Список – это упорядоченное множество, состоящее из переменного числа элементов, к которым применимы
Указатель начала списка (голова списка) – это указатель на первый элемент списка.
Цель работы: изучить понятия, классификацию и объявления списков, особенности доступа к данным и работу с памятью при использовании однонаправленных и
При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, в которой выполнено формирование однонаправленного или
Теоретические сведения.
Ознакомьтесь с материалом лекции 29.
Задания к лабораторной работе.
Выполните приведенные ниже задания.
char*. Добавьте в этот список элементы с нечетными номерами. Решите задачу, выполняя следующие требования:Ch, появление которого означает отмену предыдущего символа. Учитывая вхождение этого символа, преобразуйте последовательность.a1, a2, ..., a2n (n>= 2, заранее неизвестно и вводится с клавиатуры). Вычислите: max(min(a1, a2n), min(a3, a2n-2), ... , min (a2n-1, a2)).Указания к выполнению работы.
Каждое задание необходимо решить в соответствии с изученными методами формирования, вывода и обработки данных однонаправленных или
Следует реализовать каждое задание в соответствии с приведенными этапами:
Требования к отчету.
Отчет по лабораторной работе должен соответствовать следующей структуре.
Контрольные вопросы
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.