Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие часть1.doc
Скачиваний:
22
Добавлен:
01.03.2025
Размер:
6.94 Mб
Скачать

2.6.2. Реализация линейного списка при рекурсивном подходе

Приведенный выше рекурсивный подход к обработке линейных списков реализуется компактно и наглядно. Для формирования списка будем использовать динамическую память. Описание структуры линейного списка сводится к описанию точечной пары (структуры из двух полей):

struct list

{ type_of_data list_h; //голова, тип был определен в typedef

list *list_t; //указатель на хвост списка типа list

};

Базовые функции получают в качестве параметра указатель на список, что удобно для выполнения рекурсивных вызовов.

Представим реализацию базовых функций:

bool isnull(list *l) // проверка на пустоту

{ return (l==NULL);

}

type_of_data head(list *l) //получение значения головы списка

{ if (isnull(l)) { cerr<<"список пуст"; exit(1); }

return l->list_h;

}

list *tail(list *l) // получение указателя на хвост списка

{ if (isnull(l)) { cerr<<"!tail(NULL)"; exit(2); }

return l->list_t;

}

list *cons(type_of_data l_head,list *l_tail)//создание списка

{ list *temp=new list;

temp->list_h=l_head; temp->list_t=l_tail;

return temp;

}

В дополнение к основным функциям добавим еще рекурсивную функцию очистки списка, которая освобождает память, которую занимали элементы списка, и присваивает значение NULL указателю на список. Это значение является признаком пустого списка.

list* makenull(list *l) // очистка списка

{ if (isnull(tail(l))) {delete l; return NULL;}

else return makenull(tail(l));

}

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

list *concat(list *l1, list *l2) //присоединение l2 к l1

{ if (isnull(l1)) return l2;

return cons(head(l1),concat(tail(l1),l2));

}

type_of_data sum(list *l) // сумма (для числовых данных)

{ if (isnull(l)) return 0;

return head(l)+sum(tail(l));

}

list *append(type_of_data x, list *l) //добавление элемента x

{ return concat(l,cons(x,NULL));

}

list *reverse(list *l) //список в обратном порядке

{ if (isnull(l)) return NULL;

return concat(reverse(tail(l)),cons(head(l),NULL));

}

void print(list *l) // вывод элементов по порядку

{ if (isnull(l)) { cout<<endl;return;}

cout<<head(l)<<" "; print(tail(l));

}

Небольшая демонстрационная программа показывает применение приведенных функций при работе со списком, элементами которого являются целые числа (при определении списка использовался оператор typedef int type_of_data).

main()

{ list *l=cons(1,cons(2,cons(3,cons(4,NULL))));

print(l); //создали и вывели список(1 2 3 4)

l=concat(l,cons(5,cons(6,NULL))); //добавили список (5 6)

print(l);

l=append(7,l); print(l);//добавили еще элемент (7)

l=reverse(l); // изменили порядок элементов на обратный

print(l);

cout<<"Sum="<<sum(l)<<endl; // вывели сумму элементов

l=empty(l); if (isnull(l)) cout<<"Список пуст";

cin.get(); return 0;

}

В заключение сравним итерационный и рекурсивный подходы к обработке линейных списков. По наглядности и компактности кода, конечно, выиграет рекурсивный подход (хотя для разработки нужны определенные навыки применения рекурсии). Однако более эффективным как по времени исполнения, так и по расходу памяти все-таки является традиционное итерационное решение. Это связано с дополнительными затратами стековой памяти и времени для организации рекурсивных вызовов, причем, чем больше размер списка, тем больше глубина рекурсии, следовательно, выше накладные расходы, связанные с организацией рекурсии.

Будем считать приведенную здесь рекурсивную реализацию линейного списка хорошим упражнением в программировании рекурсивных функций и подготовкой к изучению иерархических структур, при реализации которых рекурсия более уместна.