Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Програм.С++ ч.3.doc
Скачиваний:
10
Добавлен:
01.07.2025
Размер:
5.75 Mб
Скачать

Лабораторная работа 48 Двунаправленные списки

Цель работы: изучить создание и работу с двунаправленными списками, научиться решать задачи с использованием двунаправленных списков на языке C++.

Теоретические сведения

Двунаправленный список является сложной динамической структурой, состоящей из последовательности элементов, каждый из которых содержит информационную часть и два указате­ля на соседние элементы. При этом два соседних элемента должны содержать взаимные ссылки друг на друга.

Динамический список, в котором каждый элемент (кроме, возможно, первого и последнего) связан с предыдущим и следующим за ним элементами называется двунаправленным (двусвязным). Каждый элемент такого списка имеет два поля с указателями: одно поле содержит ссылку на следующий элемент, другое поле – ссылку на предыдущий элемент и третье поле – информационное. Наличие ссылок на следующее звено и на предыдущее позволяет двигаться по списку от каждого звена в любом направлении: от звена к концу списка или от звена к началу списка, поэтому такой список называют двунаправленным.

Рис. Двунаправленный список

Описание простейшего элемента такого списка выглядит следующим образом:

struct имя_типа {

информационное поле;

адресное поле 1;

адресное поле 2;

};

где информационное поле – это поле любого, ранее объявленного или стандартного, типа;

адресное поле 1 – это указатель на объект того же типа, что и определяемая структура, в него записывается адрес следующего элемента списка;

адресное поле 2 – это указатель на объект того же типа, что и определяемая структура, в него записывается адрес предыдущего элемента списка.

Например:

struct list {

type elem ;

list *next, *pred ;

}

list *headlist ;

где type – тип информационного поля элемента списка;

*next, *pred – указатели на следующий и предыдущий элементы этой структуры соответственно.

Переменная-указатель headlist задает список как единый программный объект, ее значение – указатель на первый (или заглавный) элемент списка.

Основные операции, выполняемые над двунаправленным списком, те же, что и для однонаправленного списка. Так как двунаправленный список более гибкий, чем однонаправленный, то при включении элемента в список, нужно использовать указатель как на элемент, за которым происходит включение, так и указатель на элемент, перед которым происходит включение. При исключении элемента из списка нужно использовать как указатель на сам исключаемый элемент, так и указатели на предшествующий или следующий за исключаемым элементы. Но так как элемент двусвязного списка имеет два указателя, то при выполнении операций включения/исключения элемента надо изменять больше связей, чем в односвязном списке. Поиск элемента в двусвязном списке можно вести:

а) просматривая элементы от начала к концу списка;

б) просматривая элементы от конца списка к началу;

в) просматривая список в обоих направлениях одновременно: от начала к середине списка и от конца к середине (учитывая, что элементов в списке может быть четное или нечетное количество).

Создание двунаправленного списка

//формирование двунаправленного списка

struct point {

char *key;//адресное поле – динамическая строка

point *next;//указатель на следующий элемент

point *pred;//указатель на предыдущий элемент

};

point* make_point(){

//создание одного элемента

point*p=new(point);

p->next=0;p->pred=0;//обнуляем указатели

char s[50];

cout<<"\nEnter string:";

cin>>s;

p->key=new char[strlen(s)+1];

//выделение памяти под строку

strcpy(p->key,s);

return p;

}

point*make_list(int n) {

//создание списка

point *p,*beg;

beg=make_point();//создаем первый элемент

for(int i=1;i<n;i++) {

p=make_point();//создаем один элемент

//добавление элемента в начало списка

p->next=beg;//связываем р с первым элементом

beg->pred=p;//связываем первый элемент с p

beg=p;// p становится первым элементом списка

}

return beg;

}

Фрагменты программ, выполняющих различные действия с двусвязным списком:

void exam1(list2 *p) {

/*Просмотр циклического (возможно пустого) списка без

заглавного звена*/

list2 *ph; //в направлении от начала к концу списка

if ( p = = NULL ) return;

ph = p;

do { . . .

p = p -> next; }

while ( p != ph );

//Просмотр продолжается пока текущий указатель

// p не равен указателю на начало списка - ph

}

. . .

void exam2( list2 *p) {

/*Просмотр циклического (возможно пустого) списка с за

главным звеном*/

list2 *pr; //в направлении от конца списка к началу

if ( p –> next = = p ) return;

pr = p -> pred; /*Текущий указатель pr получил значение

ссылки на последний элемент списка*/

while (pr != p)

/*Просмотр продолжается пока текущий указатель pr не

равен указателю на заглавное звено списка – p*/

{ . . . pr = pr -> pred; }

. . .

x = new list2;

/*Включение нового элемента (в список с заглавным

звеном) перед элементом, на который ссылается p*/

x -> pred = p -> pred;

x -> next = p;

p -> pred -> next = x;

p -> pred = x;

. . .

p -> pred -> next = p -> next;

/*Исключение из списка с заглавным звеном элемента, на

который ссылается указатель p*/

p -> next -> pred = p -> pred;

delete p;

}

Печать двунаправленного списка

Для обработки списка организуется цикл, в котором нужно переставлять указатель p с помощью оператора p=p->next на следующий элемент списка до тех пор, пока указатель p не станет равен 0, т. е. будет достигнут конец списка.

void print_list(point* beg) {

//печать списка

point* p=beg;//начало списка

while(p!=0) {

cout<<p->data<<"\t";

p=p->next;//переход к следующему элементу

}

}

Добавление элементов в двунаправленный список

В динамические структуры легко добавлять элементы, т. к. для этого достаточно изменить значения адресных полей.

Рис. Добавление элемента в список

point* add_point(point* beg, int k) {

//добавление элемента с номером k

point*p=beg;//встали на первый элемент

point*New=new(point);//создали новый элемент

cout<<"Key?";cin>>New->data;

if(k==0) {//добавление в начало, если k=0

New->next=beg;

beg=New;

return beg;

}

for(int i=0;i<k-1&&p!=0;i++)

p=p->next;

//проходим по списку до(k-1) элемента или до конца

if (p!=0) {//если k-й элемент существует

New->next=p->next;//связываем New и k-й элемент

p->next=New;//связываем (k-1)элемент и New

}

return beg;

}

Удаление элементов из двунаправленного списка

Из динамических структур можно удалять элементы, т. к. для этого достаточно изменить значения адресных полей.

Рис. Удаление элемента из списка

point* del_point(point*beg,int k) {

//удаление элемента с номером k из списка

point*p=beg;

if(k==0) {//удаление первого элемента

beg=beg->next;

delete p;

return beg;

}

//проходим по списку до элемента с номером k-1

for(int i=1;i<k&&p->next!=0;i++)

p=p->next;

/*если такого элемента в списке нет, то возвращаем

указатель на начало списка в качестве результата

функции*/

if (p->next==0) return beg;

point* r=p->next;//ставим указатель r на k-й элемент

p->next=r->next;//связываем k-1 и k+1 элемент

delete r;//удаляем k-й элемент из памяти

return beg;

}

Пример 1. N-натуральных чисел являются элементами двунаправленного списка L, вычислить:X1*Xn+X2*Xn-1+...+Xn*X1. Вывести на экран каждое произведение и итоговую сумму.

Алгоритм:

    1. Создаём структуру.

    2. Формируем список целых чисел.

    3. Продвигаемся по списку: от начала к концу в одном цикле, от конца к началу в другом цикле и перемножаем данные, содержащиеся в соответствующих элементах списка.

    4. Суммируем полученные результаты.

    5. Выводим на печать

#include <stdio.h>

#include <stdlib.h>

typedef struct list_elem {//структура - элемент списка

int date;//полe данных

struct list_elem *next,*pred;

/*указатели адресов следующего и предыдущего

элементов списка*/

} SPK;

SPK* NewElem (SPK *pnev);

//функция присоединения нового элемента списка

void PrintList();//функция вывода списка на печать

void ITOG();

//функция нахождения суммы произведений элементов

SPK *list_start,*list_end;

//адреса первого и последнего элементов списка

void main(){

int N;

printf("\nInput size of the list: ");

scanf("%d",&N);

SPK *end=NULL;//указатель адреса последнего элемента

printf("\nINPUT DATE:");

do { //цикл формирования списка

end=NewElem(end);

if (end==NULL)

break;

N--;

}

while(N > 0);

printf("\n\n\nOur List: ");

PrintList();

ITOG();

}

SPK* NewElem (SPK *lel) {

int num=1; //номер вводимого элемента

lel=(SPK*)malloc(sizeof(SPK));

//выделение памяти для элемента списка

printf("\n%d element: ",num);

printf("\tInput date: ");

scanf("%d",&lel->date); //ввод числа

if (list_start==NULL) {

//если список пуст, то поля адресов равны 0

lel->next=lel->pred=NULL;

list_start=list_end=lel;

//элемент становится единственным в списке

}

else {

list_end->next=lel;

/*указатель последнего элемента указывает на

присоединяемый элемент*/

lel->pred=list_end;

/*указатель присоединяемого элемента указывает на

последний элемент*/

lel->next=NULL;

list_end=lel;

//присоединенный элемент становится последним в списке

}

num++;

return lel;

}

void PrintList() {

SPK *lel=list_start;

while(lel!=NULL) {

//пока не найден последний элемент списка

printf("%4d",lel->date);

lel=lel->next;

//передвигаем указатель на следующий элемент

}

}

void ITOG() {

SPK *lel=list_start;

SPK *mel=list_end;

int mltp,itog=0;

while(lel!=NULL) {

mltp=(lel->date)*(mel->date);//умножение элементов

printf("\n\n%d * %d = %d",lel->date,mel->date,mltp);

itog=itog+mltp;//суммирование произведений

lel=lel->next;

//идем по списку из первого элемента в последний

mel=mel->pred;

//идем по списку из последнего элемента в первый

}

printf("\n\nITOG = %d",itog);

}

Пример 2.

Создать двунаправленный список, выполнить удаление элемента с заданным номером, добавление элемента с заданным номером, печать полученных списков. Организовать интерфейс с помощью меню выборов предполагаемых действий.

#include <iostream.h>

struct point {//описание структуры

int key;//ключевое поле

point* pred,*next;//адресные поля

};

//Создание двунаправленного списка

point*make_list(){

int n;

cout<<"n-?";cin>>n;

point *p,*r,*beg;

p=new (point);//создать первый элемент

beg=p;/*запомнить адрес в переменную beg, в которой

хранится начало списка*/

cout<<"key-?";cin>>p->key;//заполнить ключевое поле

p->pred=0;p->next=0;//запомнить адресные поля

for(int i=1;i<n;i++){//добавить элементы в конец списка

r=new(point);//новый элемент

cout<<"key-?";cin>>r->key;//адресное поле

p->next=r;//связать начало списка с r

r->pred=p;//связать r с началом списка

r->next=0;//обнулить последнее адресное поле

p=r;//передвинуть p на последний элемент списка

}

return beg;//вернуть первый элемент списка

}

//Печать двунаправленного списка

void print_list(point *beg){

if (beg==0) {//если список пустой

cout<<"The list is empty\n";

return;

}

point*p=beg;

while(p) {//пока не конец списка

cout<<p->key<<"\t";

p=p->next;//перейти на следующий

}

cout<<"\n";

}

//Удаление из двунаправленного списка элемента с номером k

point* del_point(point*beg, int k){

if (beg==0) {//если список пустой

cout<<"The list is empty\n";

return beg;

}

point *p=beg;

if(k==0) {//удалить первый элемент

beg=beg->next;

//переставить начало списка на следующий элемент

if(beg==0)return 0;//если в списке только один элемент

beg->pred=0;//обнулить адрес предыдущего элемента

delete p;//удалить первый

return beg;//вернуть начало списка

}

//если удаляется элемент из середины списка

for(int i=0;i<k-1&&p!=0;i++,p=p->next);

/*пройти по списку либо до элемента с предыдущим

номером, либо до конца списка*/

if(p==0||p->next==0)return beg;

//если в списке нет элемента с номером k

point*r=p->next;//встать на удаляемый элемент

p->next=r->next;//изменить ссылку

delete r;//удалить r

r=p->next;//встать на следующий

if(r!=0)r->pred=p;//если r существует, то связать элементы

return beg;//вернуть начало списка

}

//Вставка в двунаправленный список элемента с номером k

point* add_point(point *beg,int k){

if (beg==0) {//если список пустой

cout<<"The list is empty\n";

return beg;

}

point *p;

p=new(point);

//создать новый элемент и заполнить ключевое поле

cout<<"key-?";cin>>p->key;

if(k==0) {//если добавляется первый элемент

p->next=beg;//добавить перед beg

p->pred=0;//обнулить адрес предыдущего

beg->pred=p;//связать список с добавленным элементом

beg=p;//запомнить первый элемент в beg

return beg;//вернуть начало списка

}

point*r=beg;//встать на начало списка

for(int i=0;i<k-1&&r->next!=0;i++,r=r->next);

/*пройти по списку либо до конца списка, либо до

элемента с номером k-1*/

p->next=r->next;//связать р с концом списка

if(r->next!=0)r->next->pred=p;

//если элемент не последний, то связать конец списка с р

p->pred=r;//связать р и r

r->next=p;

return beg;//вернуть начало списка

}

void main(){

point*beg=0;

int i,k;

do {

cout<<"1. Make list\n";

cout<<"2. Print list\n";

cout<<"3. Add point\n";

cout<<"4. Del point\n";

cout<<"5. Exit\n";

cin>>i;

switch(i) {

case 1: beg=make_list();

break;

case 2: print_list(beg);

break;

case 3: cout<<"\nk-?";

cin>>k;

beg=add_point(beg,k);

break;

case 4: cout<<"\nk-?";

cin>>k;

beg=del_point(beg,k);

break;

}

}

while(i!=5);

}

Перед компиляцией кода в среде MSVC++ необходимо в меню Options/Project отключить режим Use Microsoft Foundation Classes.

Задания

  1. Наберите код программы из Примера 2. Выполните компиляцию и запуск программы.

  2. Для решения задачи сформируйте двунаправленный список с символьным информационным полем. Дана последовательность латинских букв, оканчивающаяся точкой. Среди букв есть специальный символ Ch, появление которого означает отмену предыдущего символа. Учитывая вхождение этого символа, преобразуйте последовательность.

  3. Для решения задачи сформируйте двунаправленный список. Даны действительные числа a1 , a2 , . . . , a2n (n >= 2, заранее неизвестно и вводится с клавиатуры). Вычислите: max (min (a1, a2n ) , min (a3, a2n-2 ) , . . . , min (a2n-1, a2 ) ) .

Домашние задания

  1. Наберите код программы из Примера 1. Выполните компиляцию и запуск программы.

  2. Для решения задачи сформируйте двунаправленный список с символьным информационным полем. Удалите из последовательности символов все элементы, у которых равные соседи (первый и последний символы считать соседями).

  3. Дана последовательность, состоящая только из нулей и единиц. Удалите из нее все группы подряд идущих одинаковых символов, длина которых больше n. Первый и последний символы считать соседями.