Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

СТА (лекции+лабы) / СТА Лекция 2

.docx
Скачиваний:
52
Добавлен:
16.03.2016
Размер:
73.4 Кб
Скачать

void IntegerListPopBack ( IntegerList & _list )

{

// Предполагаем, что список не пустой

assert( ! IntegerListIsEmpty( _list ) );

// Обрабатываем случай удаления единственного узла

IntegerList::Node * pLast = _list.m_pLast;

if ( _list.m_pFirst == _list.m_pLast )

_list.m_pFirst = _list.m_pLast = nullptr;

else

{

// Иначе, находим предпоследний узел. Увы, только просмотрев весь список.

IntegerList::Node * pCurrent = _list.m_pFirst;

while ( pCurrent->m_pNext != _list.m_pLast )

pCurrent = pCurrent->m_pNext;

// Назначаем последним узлом предпоследний

_list.m_pLast = pCurrent;

// Разрывам связь между предпоследним и последним узлами

pCurrent->m_pNext = nullptr;

}

// Последний узел больше не нужен

delete pLast;

}

Так выглядит удаление после произвольной позиции:

void IntegerListDeleteAfter ( IntegerList & _list, IntegerList::Node * _pPrevNode )

{

// Предполагаем, что список не пустой

assert( ! IntegerListIsEmpty( _list ) );

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

assert( _list.m_pLast != _pPrevNode );

// Связываем предыдущий узел с узлом, следующим за удаляемым

IntegerList::Node * pDyingNode = _pPrevNode->m_pNext;

_pPrevNode->m_pNext = pDyingNode->m_pNext;

// Обрабатываем случай, когда удаляется последний узел списка

if ( _list.m_pLast == pDyingNode )

_list.m_pLast = _pPrevNode;

// Удалаемый узел больше не нужен

delete pDyingNode;

}

Наконец, приведем реализацию удаления до произвольной позиции :

void IntegerListDeleteBefore ( IntegerList & _list, IntegerList::Node * _pNextNode )

{

// Предполагаем, что список не пустой

assert( ! IntegerListIsEmpty( _list ) );

// Предполагаем, что указан не первый элемент

assert( _list.m_pFirst != _pNextNode );

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

IntegerList::Node * pPrevNode = _list.m_pFirst,

* pCurrentNode = _list.m_pFirst->m_pNext;

if ( pCurrentNode == _pNextNode )

{

delete _list.m_pFirst;

_list.m_pFirst = _pNextNode;

}

else

{

// Ищем узел, предшествующий удаляемому

while ( pCurrentNode->m_pNext != _pNextNode )

{

pPrevNode = pCurrentNode;

pCurrentNode = pCurrentNode->m_pNext;

}

// Связываем левого и правого соседа между собой

pPrevNode->m_pNext = _pNextNode;

// Удаляемый узел больше не нужен

delete pCurrentNode;

}

}

Функции ввода-вывода реализуются полностью аналогично векторам:

void IntegerListRead ( IntegerList & _list, std::istream & _stream )

{

while ( true )

{

int temp;

_stream >> temp;

if ( _stream )

IntegerListPushBack( _list, temp );

else

break;

}

}

void IntegerListReadTillZero ( IntegerList & _list, std::istream & _stream )

{

while ( true )

{

int temp;

_stream >> temp;

if ( _stream && temp != 0 )

IntegerListPushBack( _list, temp );

else

break;

}

}

void IntegerListPrint ( const IntegerList & _list, std::ostream & _stream, char _sep )

{

const IntegerList::Node * pCurrent = _list.m_pFirst;

while ( pCurrent )

{

_stream << pCurrent->m_value << _sep;

pCurrent = pCurrent->m_pNext;

}

}

Пример использования связных списков

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

#include "integer_list.hpp"

int main ()

{

// Создаем и иницлиазируем связный список

IntegerList l;

IntegerListInit( l );

// Вводим числа в список

std::cout << "Input integers, stop with Ctrl+Z:";

IntegerListRead( l, std::cin );

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

IntegerListPopFront( l );

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

std::cout << "Result: ";

IntegerListPrint( l, std::cout );

std::cout << std::endl;

// Уничтожаем список

IntegerListDestroy( l );

}

Решение этой задачи на векторах не будет эффективно в общем случае из-за сдвига данных.

Выбор между векторами и связными списками

Имея в распоряжении все средства для работы со связным списком, можем переделать рассмотренную в начале лекции программу об удалении элемента в заданной позиции, заменив вектора списками:

#include "integer_list.hpp"

// Функция сообщения о некорректной позиции

void reportPositionError ()

{

std::cout << "Error: invalid position specified." << std::endl;

}

// Функция распечатки результата программы

void printResult ( const IntegerList & _l )

{

std::cout << "Result: ";

IntegerListPrint( _l, std::cout );

std::cout << std::endl;

}

int main ()

{

// Создаем и инициализируем список

IntegerList l;

IntegerListInit( l );

// Вводим числа до ввода 0

std::cout << "Input integers, stop with zero: ";

IntegerListReadTillZero( l, std::cin );

// Вводим позицию, которую требуется удалить

std::cout << "Input position to delete: ";

int position2Delete;

std::cin >> position2Delete;

// Проверяем позицию на некорректность (нижняя граница)

if ( position2Delete < 0 || IntegerListIsEmpty( l ) )

reportPositionError();

// Специальный случай - удаление первого элемента

else if ( position2Delete == 0 )

{

IntegerListPopFront( l );

printResult( l );

}

else

{

// Общий случай. Находим узел-предшественник интересующей позиции

int currentIndex = 0;

IntegerList::Node * pCurrentNode = l.m_pFirst;

while ( pCurrentNode && ( currentIndex + 1 ) < position2Delete )

{

pCurrentNode = pCurrentNode->m_pNext;

++currentIndex;

}

// Проверяем корректность введенной позиции (верхняя граница)

if ( ! pCurrentNode || l.m_pLast == pCurrentNode )

reportPositionError();

else

{

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

IntegerListDeleteAfter( l, pCurrentNode );

// Печатаем результирующую последовательность

printResult( l );

}

}

// Освобождаем память списка

IntegerListDestroy( l );

}

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

Еще один существенный минус связных список по сравнению с векторами состоит в нерациональном использовании памяти. Каждый узел выделяется в куче отдельной аллокацией. Узлы являются маленькими структурами, и при большом количестве узлов только лишь за счет “невидимой” служебной доли каждой динамической аллокации будет наблюдаться существенный расход памяти. Кроме того, поскольку время выделения памяти в куче мало зависит от выделяемого объема в байтах, N небольших выделений динамической памяти однозначно работает существенно медленней одного большого выделения памяти.

Утверждение, что вставка/удаление элементов в произвольной позиции в векторах работает медленнее, чем в списках, также может быть опровергнуто при определенных обстоятельствах. Строго говоря, это утверждение может не выполняться для небольших последовательностей (5-7 элементов) на большинстве современных компьютеров. Такая аномалия может быть связана с нюансами работы кэш-памяти. Как известно, элементы векторов соседствуют в физической памяти и с высокой долей вероятности будут считываться в кэш-память при обращении одновременно. Элементы же списков, напротив, скорее всего попадут в совершенно разные области физической памяти и чаще всего не будут находиться в кэш-памяти одновременно при последовательном проходе. В зависимости от свойств аппаратного обеспечения конкретного компьютера, время выполнения сдвига нескольких ячеек, находящихся рядом в более быстрой кэш-памяти может быть меньшим, чем время нескольких обращений к более медленным чипам оперативной памяти.

Учитывая такие неоднозначности, окончательный выбор между векторами и связными списками должен подтверждаться экспериментально. Если выбор не очевиден или не является критичным для задачи, рекомендуется использовать векторы, как более простую структуру.

Выводы

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

Соседние файлы в папке СТА (лекции+лабы)