- Главная
- Информатика
- Тема 7.1. Линейный список
Содержание
- 2. Тема 7.1. Линейный список Реальное многообразие структур данных базируется всего на двух основных способах получения адреса
- 3. Тема 7.1. Линейный список • элемент списка доступен в программе через указатель. «Смысл» этого указателя отражает
- 4. Преимущества списков проявляются в таких структурах данных, где операции изменения порядка превалируют над операциями доступа и
- 5. Списковые структуры данных обычно являются динамическими по двум причинам: • сами переменные таких структур создаются как
- 6. Работа со списками осуществляется исключительно через указатели. Каждый из них может перемещаться по списку (переустанавливаться с
- 7. БАЗОВЫЕ ДЕЙСТВИЯ СО СПИСКОМ Тема 7.1. Линейный список
- 8. Основная операция при работе с элементами массива – «стрелочка» выполняется в контексте: указатель на элемент->поле элемента
- 9. Список может содержать ограниченное количество элементов, взятых из массива. Связи устанавливаются динамически, то есть программой. Такой
- 10. В динамическом списке элементы являются динамическими переменными, связи между ними устанавливаются программно. list *ph=NULL; // Список
- 11. • путем возврата измененного значения заголовка в виде результата функции; // Вариант 1. Измененный указатель возвращается
- 12. • передачей ссылки на заголовок. Cсылка - неявный указатель, использующий при работе синтаксис объекта, который «отображается»
- 13. Изменение порядка следования элементов осуществляется путем переустановки указателей в элементах списка и производится операциями присваивания указателей.
- 14. 2. Адресная интерпретация. Содержимым указателя является адрес указуемой переменной. Тогда элементам списка можно присвоить условные адреса
- 15. Простейший случай: элемент списка содержит единственный указатель на следующий (next), что создает «улицу с односторонним движением»
- 16. // Включение в конец списка - помещение в очередь void In(list *&ph,int v){ list *q=new list;
- 17. // Включение в начало списка - помещение в стек void PUSH(list *&ph,int v){ list *q=new list;
- 18. // Указатели на первый и последний элементы; // list *PH[2]; - заголовок очереди, [0]-первый, [1]-последний void
- 19. В операциях, связанных с элементами в середине списка возникает проблема – недоступность предыдущего элемента. Если необходимо
- 20. // pr - указатель на предыдущий элемент списка void InsSort(list *&ph, int v) { list *q
- 21. При сортировке массивов происходит физическое перемещение упорядочиваемых элементов (например, обмен значений). В списках принята другая технология,
- 22. Двусвязный список позволяет двигаться по цепочке элементов в обоих направлениях, поскольку доступны следующий и предыдущий элементы.
- 23. Чтобы учесть эти варианты при уже написанном программном коде, нужно рассмотреть «историческое» поведение программы на каждом
- 24. Витиеватое выражение q->prev->next=q->next производит «выкусывание» текущего элемента из списка путем перекидывания указателей с предыдущего на следующий,
- 25. void InsSort(list *&ph, int v) // ссылка на заголовок { list *q , *p=new list; //
- 26. Циклический список связывает крайние элементы списка между собой, образуя кольцевую структуру. На практике «замкнутость» списка является
- 27. Цикл просмотра списка завершается, когда указатель текущего элемента возвращается на начало списка. Это удобно сделать в
- 28. void In(list *&ph, int v){ list *q = new list; // Новый элемент как единственный q->val
- 29. Все перечисленные особенности можно увидеть в примере включения нового элемента с сохранением упорядоченности. Поиск места включения
- 30. Еще одна крайняя ситуация – вставка перед первым, почти «вписывается» общую схему. Цикл при этом не
- 31. Определите вид списка, «смысл» каждого указателя, выполняемое действие над списком, напишите вызов функций для статического списка.
- 32. //------------------------------------------------------- 4 list *F4(list *ph, int v) { list *p,*q = new list; q->val = v;
- 33. //------------------------------------------------------- 6 int F6(list *p) { int n; list *q; if (p==NULL) return 0; for (q
- 34. //------------------------------------------------------- 8 list *F8(list *ph) { list *q, *out, *p , *pr; out = NULL; while
- 35. //------------------------------------------------------- 9 list *F9(list *pp, int n) { list *q; for (q = pp; n!=0; q
- 36. //------------------------------------------------------ 11 list *F11(list *ph, int v) { list *q = new list; q->val = v;
- 38. Скачать презентацию
Слайд 2Тема 7.1. Линейный список
Реальное многообразие структур данных базируется всего на двух основных
Тема 7.1. Линейный список
Реальное многообразие структур данных базируется всего на двух основных
При построении структуры данных из указателей получается цепочка (последовательность) элементов, содержащих указатели друг на друга. В простейшем случае она может быть линейной (список), в более сложных случаях – ветвящейся (деревья, графы).
Список – линейная последовательность элементов, каждый из которых содержит указатели (ссылается) на своих соседей.
СПИСКОВАЯ СТРУКТУРА
Физическое размещение в памяти элементов списка не имеет никакого значения, все определяется наличием ссылок на него в других элементах и извне. У массива всегда есть «начало». У списка по определению отсутствует фиксированная привязка к памяти.
Слайд 3Тема 7.1. Линейный список
• элемент списка доступен в программе через указатель. «Смысл»
Тема 7.1. Линейный список
• элемент списка доступен в программе через указатель. «Смысл»
• в программе список задается посредством заголовка – указателя на первый элемент списка;
• порядок следования элементов определяется последовательностью связей между элементами. Изменение порядка следования элементов (вставка, удаление) осуществляются изменением переустановкой указателей на соседние элементы.
• логический (порядковый) номер элемента списка также задается его естественной нумерацией в цепочке элементов;
• список является структурой данных с последовательным доступом. Для получения n-го по счету элемента необходимо последовательно пройти по цепочке от элемента, на который имеется указатель (например, от заголовка);
• список удобен для использования именно как динамическая структура данных: элементы списка обычно создаются как динамические переменные, а связи между ними устанавливаются программно (динамически);
• список обладает свойством локальности изменений: при вставке/удалении элемента изменения касаются только текущего и его соседей. Вспомним массив: при вставке/удалении его элементов происходит физическое перемещение (сдвиг) всех элементов от текущего до конца.
ОСНОВНЫЕ РЕЛЯТИВИСТСКИЕ СВОЙСТВА СПИСКА
Слайд 4Преимущества списков проявляются в таких структурах данных, где операции изменения порядка превалируют
Преимущества списков проявляются в таких структурах данных, где операции изменения порядка превалируют
Элемент списка является составной (структурированной) переменной, содержащей собственно хранимые данные и указатели на соседей:
struct elem // определение структурированного типа
{ int value; // значение элемента (хранимые данные)
elem *next; // единственный указатель или
elem *next,*prev; // два указателя или
elem *links[10]; // ограниченное количество указателей (не больше 10) или
elem **plinks; // произвольное количество указателей (внешний МУ)
};
ОПРЕДЕЛЕНИЕ СПИСКА И РАБОТА С НИМ
Это еще не список, а описание его составляющих (элементов) как типа данных. Из него следует только, что каждый из них ссылается на аналогичные элементы. Никак нельзя определить ни количества таких переменных в структуре данных, ни характера связей между ними (последовательный, циклический, произвольный). Следовательно, конкретный тип структуры данных (линейный список, дерево, граф) зависит от функций, которые с ней работают. Значит, структура данных «зашита» не столько в этом определении, сколько в алгоритмах, работающих с этим типом.
Тема 7.1. Линейный список
Слайд 5Списковые структуры данных обычно являются динамическими по двум причинам:
• сами переменные таких
Списковые структуры данных обычно являются динамическими по двум причинам:
• сами переменные таких
• количество связей между переменными и их характер также определяются динамически в процессе работы программы.
В зависимости от связей списки бывают следующих видов:
• односвязные - каждый элемент списка имеет указатель на следующий;
• двусвязные - каждый элемент списка имеет указатель на следующий и на предыдущий элементы;
• циклические - первый и последний элементы списка ссылаются друг на друга и цепочка представляет собой кольцо.
«Обычные» (разомкнутые) списки имеют в качестве ограничителя последовательности NULL-указатель. Соответственно, возможет случай пустого списка, в котором заголовок - указатель на первый элемент также содержит значение NULL.
ОПРЕДЕЛЕНИЕ СПИСКА И РАБОТА С НИМ
Тема 7.1. Линейный список
Слайд 6Работа со списками осуществляется исключительно через указатели. Каждый из них может перемещаться
Работа со списками осуществляется исключительно через указатели. Каждый из них может перемещаться
БАЗОВЫЕ ДЕЙСТВИЯ СО СПИСКОМ
Тема 7.1. Линейный список
Слайд 7БАЗОВЫЕ ДЕЙСТВИЯ СО СПИСКОМ
Тема 7.1. Линейный список
БАЗОВЫЕ ДЕЙСТВИЯ СО СПИСКОМ
Тема 7.1. Линейный список
Слайд 8Основная операция при работе с элементами массива – «стрелочка» выполняется в контексте:
Основная операция при работе с элементами массива – «стрелочка» выполняется в контексте:
Собственно сам список представляет собой множество связанных указателями переменных. В простейшем случае, например, для создания тестовых данных, можно использовать статический список: его элементы представляет собой обычные структурированные переменные, связи между ними инициализируются транслятором, и вся структура данных «зашивается» в программный код.
struct list { int val; list *next; } a={0,NULL}, b={1,&a}, c={2,&b}, *ph = &c;
По условиям определения переменных, список создается «хвостом вперед».
В двусвязном списке проблема «ссылок вперед» на еще неопределенные элементы решается так: сначала переменные объявляются, как внешние, а потом определяются и инициализируются.
struct list2 { int val; list *next,*prev;};
extern list2 a,b,c; // предварительное объявление элементов списка
list2 a={0,&b,NULL}, b={1,&c,&a}, c={2,NULL&b}, *ph = &c; // выделены «ссылки вперед»
СТАТИЧЕСКИЙ СПИСОК
Тема 7.1. Линейный список
Слайд 9Список может содержать ограниченное количество элементов, взятых из массива. Связи устанавливаются динамически,
Список может содержать ограниченное количество элементов, взятых из массива. Связи устанавливаются динамически,
list A[100],*ph; // Создать список элементов,
for (i=0; i<99; i++) { // размещенных в статическом массиве
A[i].next = A+i+1; // Адрес следующего вычисляется
A[i].val = i;
}
A[99].next = NULL;
ph = &A[0];
СОЗДАНИЕ ДИНАМИЧЕСКОГО СПИСОКА ИЗ СТАТИЧЕСКОГО
Тема 7.1. Линейный список
Слайд 10В динамическом списке элементы являются динамическими переменными, связи между ними устанавливаются программно.
list
В динамическом списке элементы являются динамическими переменными, связи между ними устанавливаются программно.
list
for (int i=0; i
q->val=i; // списка:
q->next=ph; // следующий за новым – бывший первый
ph=q; } // новый становится первым
При модульном проектировании программы функции, работающие со списком, получают его через формальный параметр – заголовок списка.
//------ формальный параметр - заголовок списка
void F1(list *p) {
for (; p!=NULL; p=p->next) puts(p->val);
}
ДИНАМИЧЕСКИЙ СПИСОК
Тема 7.1. Линейный список
«Подводный камень». Этот вариант полезен только в том случае, когда первый (по счету) элемент списка остается таковым в процессе работы со списком. Поскольку заголовок передается по значению (как копия), то его изменение никак не сказывается на оригинале – истинном заголовке списка в main.
Слайд 11• путем возврата измененного значения заголовка в виде результата функции;
// Вариант 1.
• путем возврата измененного значения заголовка в виде результата функции;
// Вариант 1.
list *Ins1(list *ph, int v)
{ list *q=new list;
q->val=v; q->next=ph; ph=q;
return ph; }
• передачей указателя на заголовок списка (указателя на указатель);
// Вариант 2. Используется указатель на заголовок
void Ins2(list **pp, int v)
{ list *q=new list;
q->val=v; q->next=*pp; *pp=q; }
СПОСОБЫ ВКЛЮЧЕНИЯ В НАЧАЛО СПИСКА С ИЗМЕНЕНИЕМ ЗАГОЛОВКА
Тема 7.1. Линейный список
Слайд 12• передачей ссылки на заголовок.
Cсылка - неявный указатель, использующий при работе
• передачей ссылки на заголовок.
Cсылка - неявный указатель, использующий при работе
// Вариант 3. Используется ссылка на указатель
void Ins3(list *&pp, int v)
{ list *q=new list;
q->val=v; q->next=pp; pp=q; }
//----------- Пример вызова-----------------------------------------
void main(){
list *ph=NULL; // пустой список
ph=Ins1(ph,5); // сохранить новый заголовок
Ins2(&ph,66); // передается адрес заголовка
Ins3(ph,7); } // передается ссылка на заголовок
СПОСОБЫ ВКЛЮЧЕНИЯ В НАЧАЛО СПИСКА С ИЗМЕНЕНИЕМ ЗАГОЛОВКА
Тема 7.1. Линейный список
Слайд 13Изменение порядка следования элементов осуществляется путем переустановки указателей в элементах списка и
Изменение порядка следования элементов осуществляется путем переустановки указателей в элементах списка и
1. Графическая интерпретация: изменение порядка следования состоит в «соединении стрелкой» связываемых элементов:
• в левой части операции присваивания должно находиться обозначение ячейки, в которую заносится новое значение указателя – ссылка на нее (2). При этом она должна быть достижима через имеющиеся рабочие указатели. На рисунке этому соответствует цепочка операций q->prev->next;
• в правой части операции присваивания должно находиться обозначение ячейки, из которой берется значение указателя элемента, на который ссылаются (1).
ИЗМЕНЕНИЕ ПОРЯДКА СЛЕДОВАНИЯ
Тема 7.1. Линейный список
Слайд 142. Адресная интерпретация. Содержимым указателя является адрес указуемой переменной. Тогда элементам списка
2. Адресная интерпретация. Содержимым указателя является адрес указуемой переменной. Тогда элементам списка
3. Смысловая интерпретация. При работе со списками каждый указатель имеет определенный смысл – ссылка на первый, текущий, следующий, предыдущий и т.п. элементы списка. Поля prev, next также интерпретируются как указатели на
следующий и предыдущий в элементе списка, доступном через указатель. Присваивание указателей однозначно можно однозначно выразить в словесной формулировке. Например, последовательность действий по включению нового элемента (указатель q) в двусвязный список перед текущим (указатель p) словесно формулируется так:
q->next=p; // следующий для нового = текущий
q->prev=p->prev; // предыдущий для нового = предыдущий
if (p->prev == NULL) ph = q; // для текущего включение в начало списка
else p->prev->next = q; // включение в середину следующий для предыдущего = новый
p->prev=q; // предыдущий для текущего = новый
ИЗМЕНЕНИЕ ПОРЯДКА СЛЕДОВАНИЯ
Тема 7.1. Линейный список
Слайд 15Простейший случай: элемент списка содержит единственный указатель на следующий (next), что создает
Простейший случай: элемент списка содержит единственный указатель на следующий (next), что создает
Операций включения и исключения элемента в начало списка вообще реализуются парой присваиваний, для включения в конец списка требуется дополнительный цикл просмотра списка до последнего элемента. Поэтому с помощью односвязного списка удобно представлять такие структуры данных, как стек и очередь: операции извлечения из очереди и стека реализуются через исключение из списка первого элемента, операция включения в стек соответствует включению элемента в начло списка, а включение в очередь – добавлению в конец.
ОДНОСВЯЗНЫЙ СПИСОК
Тема 7.1. Линейный список
Слайд 16// Включение в конец списка - помещение в очередь
void In(list *&ph,int v){
// Включение в конец списка - помещение в очередь
void In(list *&ph,int v){
q->next=NULL;
q->val=v;
if (ph==NULL) ph=q; // список пуст - единственный
else { for (list *p=ph; p->next!=NULL; p=p->next); // цикл поиска конца списка
p->next=q; }} // следующий за последним - новый
// Включение в конец списка - помещение в очередь
void In(list *&ph,int v){
list *q=new list;
q->next=NULL;
q->val=v;
if (ph==NULL) ph=q; // список пуст - единственный
else { for (list *p=ph; p->next!=NULL; p=p->next); // цикл поиска конца списка
p->next=q; }} // следующий за последним - новый
ПРЕДСТАВЛЕНИЕ ОЧЕРЕДИ ОДНОСВЯЗНЫМ СПИСКОМ
Тема 7.1. Линейный список
Слайд 17// Включение в начало списка - помещение в стек
void PUSH(list *&ph,int v){
// Включение в начало списка - помещение в стек
void PUSH(list *&ph,int v){
q->next=NULL;
q->val=v;
q->next=ph; // следующий за новым - бывший первый
ph=q; } // новый теперь первый
// Исключение из очереди и стека – удалени первого элемента списка
int Out(list *&ph){
if (ph==NULL) return -1;
list *q=ph; // запомнить текущий
ph=ph->next; // сдвинуться к следующему
int v=q->val;
delete q; // удалить текущий
return v;}
ПРЕДСТАВЛЕНИЕ СТЕКА ОДНОСВЯЗНЫМ СПИСКОМ
Тема 7.1. Линейный список
Слайд 18// Указатели на первый и последний элементы;
// list *PH[2]; - заголовок
// Указатели на первый и последний элементы;
// list *PH[2]; - заголовок
void In(list *ph[], int v){
list *p= new list; // создать элемент списка;
p->val=v; // и заполнить его
p->next=NULL; // новый элемент - последний
if (ph[0]==NULL) ph[0]=ph[1]=p; // включение в пустую очередь
else { // включение за последним элементом
ph[1]->next = p; // следующий за последним = новый
ph[1] = p; }} // последний = новый
int Out(list *ph[]){ // извлечение из очереди
if (ph[0]==NULL) return -1; // очередь пуста
list *q=ph[0]; // исключение первого элемента
ph[0]=q->next;
if (ph[0]==NULL) ph[1]=NULL; // элемент единственный
int v = q->val;
delete q; return v; }
ПРЕДСТАВЛЕНИЕ ОЧЕРЕДИ ОДНОСВЯЗНЫМ СПИСКОМ
Тема 7.1. Линейный список
Чтобы не «мотаться все время» к концу односвязного списка, можно добавить еще один указатель на последний элемент, и тогда операция помещения в очередь аналогично реализуется парой присваиваний.
Слайд 19В операциях, связанных с элементами в середине списка возникает проблема – недоступность
В операциях, связанных с элементами в середине списка возникает проблема – недоступность
Если необходимо изменить его содержимое, то потребуются некоторые ухищрения, например:
• дополнительный цикл, пробегающий от начала списка до предыдущего элемента для найденного, например for(q=ph; q->next!=p; q=q->next);
• явное использование цикла с указателем на элемент, предыдущий по отношению к проверяемому, например for(p=ph->next; p->next!=NULL && p->next->val!=x; p=p->next). Обратите внимание, что такой цикл начинается со второго элемента, а значит, требуется отдельная проверка для первого элемента списка;
• использование дополнительного указателя на предыдущий элемент, запоминающего свое значение при переходе к следующему.
Последний вариант используется при вставке с сохранением порядка. Вставка происходит перед очередным, большим заданного, что требует коррекции предыдущего элемента списка.
ВКЛЮЧЕНИЕ В ОДНОСВЯЗНЫЙ С СОХРАНЕНИЕМ ПОРЯДКА
Тема 7.1. Линейный список
Слайд 20// pr - указатель на предыдущий элемент списка
void InsSort(list *&ph, int
// pr - указатель на предыдущий элемент списка
void InsSort(list *&ph, int
{ list *q ,*pr,*p; // перед переходом к следующему указатель на текущий
q=new list; q->val=v; // запоминается как указатель на предыдущий
for ( p=ph,pr=NULL; p!=NULL && v>p->val; pr=p,p=p->next);
if (pr==NULL) // включение перед первым
{ q->next=ph; ph=q; }
else // иначе после предыдущего
{ q->next=p; // следующий для нового = текущий
pr->next=q; }} // следующий для предыдущего = новый
Дополнительная проверка «крайних ситуаций» показывает, что фрагмент, производящий поиск места включения, корректно работает и в случае пустого списка (производит включение перед первым).
ВКЛЮЧЕНИЕ В ОДНОСВЯЗНЫЙ С СОХРАНЕНИЕМ ПОРЯДКА
Тема 7.1. Линейный список
Слайд 21При сортировке массивов происходит физическое перемещение упорядочиваемых элементов (например, обмен значений). В
При сортировке массивов происходит физическое перемещение упорядочиваемых элементов (например, обмен значений). В
list *sort(list *ph) // функция возвращает заголовок нового списка
{ list *q, *out, *p , *pr;
out = NULL; // выходной список пуст
while (ph !=NULL) // пока не пуст входной список
{ q = ph; ph = ph->next; // исключить очередной
for ( p=out,pr=NULL; p!=NULL && q->val>p->val; pr=p,p=p->next); // поиск места включения
if (pr==NULL) { q->next=out; out=q; } // включение перед первым
else { q->next=p; pr->next=q; } // иначе после предыдущего
} return out; }
СОРТИРОВКА ОДНОСВЯЗНОГО СПИСКА ВСТАВКАМИ
Тема 7.1. Линейный список
Слайд 22Двусвязный список позволяет двигаться по цепочке элементов в обоих направлениях, поскольку доступны
Двусвязный список позволяет двигаться по цепочке элементов в обоих направлениях, поскольку доступны
Здесь уместно напомнить о такой проблеме проектирования как крайние ситуации. Необходимо рассматривать все возможные качественные комбинации входных данных и все варианты структур данных, с которыми может столкнуться программа. Применительно к двусвязному списку при удалении выбранного элемента возможны следующие варианты:
• пустой список;
• единственный элемент;
• удаление первого элемента;
• удаление последнего элемента;
• удаление из середины.
«Пропуск» одного из вариантов при проектировании приводит к появлению «мерцающих» ошибок, проявляющихся не постоянно, а периодически в зависимости от входных данных и порядка выполнения операций.
ДВУСВЯЗНЫЙ СПИСОК
Тема 7.1. Линейный список
Слайд 23Чтобы учесть эти варианты при уже написанном программном коде, нужно рассмотреть «историческое»
Чтобы учесть эти варианты при уже написанном программном коде, нужно рассмотреть «историческое»
• написанный код может корректно отрабатывать крайнюю ситуацию, она «вписывается» в существующий программный код;
• крайняя ситуация не отрабатывается программным кодом, в него вносится дополнительное проверочное условие с «заплаткой», обеспечивающей правильное поведение программы.
//-------- Удаление элемента списка по заданному логическому номеру
list *Remove(list *&pp, int n)
{ list *q; // Указатель на текущий элемент
for (q = pp; q!=NULL && n!=0; q = q->next, n--); // Отсчитать n -ый
if (q==NULL) return NULL; // нет элемента с таким номером
if (q->prev==NULL) // удаление первого -
pp=q->next; // коррекция заголовка
else q->prev->next = q->next; // следующий для предыдущего =
// следующий за текущим
if (q->next!=NULL) // удаление не последнего -
q->next->prev = q->prev; // предыдущий для следующего =
return q; } // предыдущий текущего
ДВУСВЯЗНЫЙ СПИСОК
Тема 7.1. Линейный список
Слайд 24Витиеватое выражение q->prev->next=q->next производит «выкусывание» текущего элемента из списка путем перекидывания указателей
Витиеватое выражение q->prev->next=q->next производит «выкусывание» текущего элемента из списка путем перекидывания указателей
В разомкнутом списке проверяется наличие следующего и предыдущего элементов для удаляемого. При отсутствии предыдущего корректируется заголовок (удаление первого). Все крайние ситуации корректно «вписываются» в программный код.
Сравнение операций включения с сохранением порядка в односвязный и двусвязный список дает ожидаемые выводы: сохранять указатель на предыдущий элемент нет необходимости, но программный код разрастается за счет увеличения количества переназначаемых указателей и крайних ситуаций.
ДВУСВЯЗНЫЙ СПИСОК
Тема 7.1. Линейный список
Слайд 25void InsSort(list *&ph, int v) // ссылка на заголовок
{ list *q
void InsSort(list *&ph, int v) // ссылка на заголовок
{ list *q
p->val = v;
p->prev = p->next = NULL;
if (ph == NULL) { // включение в пустой список
ph=p; return ;
} // поиск места включения - q
for (q=ph; q !=NULL && v > q->val; q=q->next);
if (q == NULL){ // включение в конец списка
for (q=ph; q->next!=NULL; q=q->next);
p->prev = q; // восстановить указатель на последний
q->next = p; // включить перед текущим
return; }
p->next=q; // следующий за новым = текущий
p->prev=q->prev; // предыдущий нового = предыдущий текущего
if (q->prev == NULL) // включение в начало списка
ph = p;
else // включение в середину
q->prev->next = p; // следующий за предыдущим = новый
q->prev=p; // предыдущий текущего = новый
}
ВКЛЮЧЕНИЕ В ДВУСВЯЗНЫЙ СПИСОК С СОХРАНЕНИЕМ ПОРЯДКА
Тема 7.1. Линейный список
Слайд 26Циклический список связывает крайние элементы списка между собой, образуя кольцевую структуру. На
Циклический список связывает крайние элементы списка между собой, образуя кольцевую структуру. На
• поле next последнего элемента ссылается на первый, а поле prev первого – на последний элемент списка;
• единственный элемент списка ссылается сам на себя (q->next=q и q->prev =q);
• операции включение элемента в начало и конец списка идентичны (перед первым и после последнего) за исключением того, что при включении в начало меняется заголовок.
ДВУСВЯЗНЫЙ ЦИКЛИЧЕСКИЙ СПИСОК
Тема 7.1. Линейный список
Слайд 27Цикл просмотра списка завершается, когда указатель текущего элемента возвращается на начало списка.
Цикл просмотра списка завершается, когда указатель текущего элемента возвращается на начало списка.
list *p=ph;
do {
// тело цикла для текущего элемента – p
p=p->next;
} while (p!=ph);
Доступность первого и последнего элемента циклического списка через заголовок позволяет реализовать на нем стек и очередь, не используя циклов движения по списку.
ДВУСВЯЗНЫЙ ЦИКЛИЧЕСКИЙ СПИСОК
Тема 7.1. Линейный список
Слайд 28void In(list *&ph, int v){
list *q = new list; // Новый
void In(list *&ph, int v){
list *q = new list; // Новый
q->val = v; q->next = q->prev = q;
if (ph == NULL) { ph=q; return; } // Список пуст - единственный
q->next = ph; // следующий за новым = первый
q->prev = ph->prev; // предыдущий для нового = последний
ph->prev->next = q; // следующий для последнего = новый
ph->prev = q; } // предыдущий для первого = новый
int Out(list *&ph){
if (ph==NULL) return -1;
int v=ph->val;
list *q=ph; // Запомнить текущий
ph=ph->next; // Перейти на следующий
if (ph->next==ph) ph=NULL; // Единственный стал пустой
q->next->prev=q->prev; // Элемент сам себя "выкусывает"
q->prev->next=q->next;
delete q;
return v;
}
ОЧЕРЕДЬ НА ЦИКЛИЧЕСКОМ СПИСКЕ
Тема 7.1. Линейный список
Слайд 29Все перечисленные особенности можно увидеть в примере включения нового элемента с сохранением
Все перечисленные особенности можно увидеть в примере включения нового элемента с сохранением
list *InsSort(list *ph, int v) // Функция возвращает новый заголовок
{ list *q = new list; // Новый элемент как единственный
q->val = v; q->next = q->prev = q;
if (ph == NULL) return q; // Список пуст вернуть новый
list *p = ph;
do { if (v < p->val) break; // Место вставки перед первым,
p=p->next; // большим заданного, иначе -
} while (p!=ph); // перед первым в списке (после последнего)
q->next = p; // следующий за новым = текущий
q->prev = p->prev; // предыдущий для нового = предыдущий текущего
p->prev->next = q; // следующий для предыдущего = новый
p->prev = q; // предыдущий для текущего = новый
if ( ph->val > v) ph=q; // включение перед первым -
return ph; } // коррекция заголовка
ВКЛЮЧЕНИЕ В ЦИКЛИЧЕСКИЙ СПИСОК С СОХРАНЕНИЕМ ПОРЯДКА
Тема 7.1. Линейный список
Слайд 30Еще одна крайняя ситуация – вставка перед первым, почти «вписывается» общую схему.
Еще одна крайняя ситуация – вставка перед первым, почти «вписывается» общую схему.
Ошибки при работе со списками
При отладке программ, работающих со списками, могут возникать специфические ошибки, связанные некорректной переустановкой связей между элементами:
• потеря связности – в результате нарушения порядка присваиваний (переустановки связей) некоторые элементы списка могут оказаться недоступными, или «потерянными»;
• топологические ошибки - нарушается линейность списка, «список перестает быть списком», а становится топологически более сложной структурой, на которой поведение программы становится непредсказуемым.
Аналогичный эффект можно получить, если забыть скорректировать заголовок списка, если операция касается первого элемента. Например, если в сортировке односвязного списка вставками (63-04.cpp) забыть присваивание указателя при вставке перед первым (out=q), то в такой ситуации очередной элемент входного списка просто будет потерян. Этот эффект будет «мерцающим»: теряться будут некоторые элементы со значением меньше всех предыдущих во входном списке (например, 6,7,3,8,4,2,6,5). Но программа все-таки будет работать. В случаях более серьезного нарушения топологии она может зациклиться или вообще «упасть».
ВКЛЮЧЕНИЕ В ЦИКЛИЧЕСКИЙ СПИСОК С СОХРАНЕНИЕМ ПОРЯДКА
Тема 7.1. Линейный список
Слайд 31Определите вид списка, «смысл» каждого указателя, выполняемое действие над списком, напишите вызов
Определите вид списка, «смысл» каждого указателя, выполняемое действие над списком, напишите вызов
struct list { int val; list *next,*prev; };
//------------------------------------------------------- 1
int F1(list *p)
{ int n;
for (n=0; p!=NULL; p=p->next, n++);
return n; }
//------------------------------------------------------- 2
list *F2(list *ph, int v)
{ list *q = new list;
q->val = v; q->next = ph; ph = q;
return ph; }
//------------------------------------------------------- 3
list *F3(list *p, int n)
{ for (; n!=0 && p!=NULL; n--, p=p->next);
return p; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список
Слайд 32//------------------------------------------------------- 4
list *F4(list *ph, int v)
{ list *p,*q = new
//------------------------------------------------------- 4
list *F4(list *ph, int v)
{ list *p,*q = new
q->val = v; q->next = NULL;
if (ph == NULL) return q;
for ( p=ph ; p ->next !=NULL; p = p->next);
p ->next = q; return ph; }
//------------------------------------------------------- 5
list *F5(list *ph, int n)
{ list *q ,*pr,*p;
for ( p=ph,pr=NULL; n!=0 && p!=NULL; n--, pr=p, p =p->next);
if (p==NULL) return ph;
if (pr==NULL) { q=ph; ph=ph->next; }
else { q=p; pr->next=p->next; }
delete q;
return ph; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список
Слайд 33//------------------------------------------------------- 6
int F6(list *p)
{ int n; list *q;
if (p==NULL)
//------------------------------------------------------- 6
int F6(list *p)
{ int n; list *q;
if (p==NULL)
for (q = p, p = p->next, n=1; p !=q; p=p->next, n++);
return n; }
//------------------------------------------------------- 7
list *F7(list *p, int v)
{ list *q;
q = new list;
q->val = v; q->next = q->prev = q;
if (p == NULL) p = q;
else
{ q->next = p; q->prev = p->prev;
p->prev->next = q; p->prev = q; p=q;
}
return p; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список
Слайд 34//------------------------------------------------------- 8
list *F8(list *ph)
{ list *q, *out, *p , *pr;
//------------------------------------------------------- 8
list *F8(list *ph)
{ list *q, *out, *p , *pr;
while (ph !=NULL)
{ q = ph; ph = ph->next;
for ( p=out,pr=NULL; p!=NULL && q->val>p->val; pr=p,p=p->next);
if (pr==NULL)
{ q->next=out; out=q; }
else
{ q->next=p; pr->next=q; }
} return out; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список
Слайд 35//------------------------------------------------------- 9
list *F9(list *pp, int n)
{ list *q;
for (q
//------------------------------------------------------- 9
list *F9(list *pp, int n)
{ list *q;
for (q
if (q->next == q) { delete q; return NULL; }
if (q == pp) pp = q->next;
q->prev->next = q->next;
q->next->prev = q->prev;
delete q; return pp; }
//------------------------------------------------------ 10
list *F10(list *ph, int v)
{ list *q ,*pr,*p;
q=new list; q->val=v; q->next=NULL;
if (ph==NULL) return q;
for ( p=ph,pr=NULL; p!=NULL && v>p->val; pr=p,p=p->next);
if (pr==NULL) { q->next=ph; ph=q; }
else { q->next=p; pr->next=q; }
return ph; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список
Слайд 36//------------------------------------------------------ 11
list *F11(list *ph, int v)
{ list *q = new
//------------------------------------------------------ 11
list *F11(list *ph, int v)
{ list *q = new
q->val = v; q->next = q->prev = q;
if (ph == NULL) return q;
list *p = ph;
do {
if (v < p->val) break;
p=p->next;
} while (p!=ph);
q->next = p; q->prev = p->prev;
p->prev->next = q; p->prev = q;
if ( ph->val > v) ph=q;
return ph; }
ДОМАШНЕЕ ЗАДАНИЕ
Тема 7.1. Линейный список