Консультация № 178253
07.05.2010, 00:01
0.00 руб.
0 5 1
Здравствуйте, уважаемые эксперты. Прошу помочь с реализацией следующей программы на языке программирования С++. Тема задания - динамические структуры данных. Требуется использовать следующие типы данных: int, double,c har*, struct T { int v1,char v2};
Задача: Написать программу, реализующую двухсвязный список, который хранит элементы произвольного типа (Программа заранее не знает, данные какого типа будут хранится в элементах списка). Реализовать полный набор операций (создание списка, вставка эл-та, удаление эл-та).

Обсуждение

Неизвестный
08.05.2010, 00:02
общий
это ответ
Здравствуйте, Селеверстов Антон Юрьевич.

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

Программа протестирована в MSVC++ 6.0

Успехов!

Приложение:
/*
вопрос № 178253
Написать программу, реализующую двухсвязный список, который хранит
элементы произвольного типа (Программа заранее не знает, данные какого
типа будут хранится в элементах списка). Реализовать полный набор
операций (создание списка, вставка эл-та, удаление эл-та).
*/
#include <stdlib.h>
#include <stdio.h>

struct t_DoubleListItem
{
t_DoubleListItem* next;
t_DoubleListItem* prev;
};

class t_DoubleList
{
public:
t_DoubleList();
virtual ~t_DoubleList();

virtual void freeItem( t_DoubleListItem* pItem );

void clear();
void add( t_DoubleListItem* pItem );
bool insertAfter( const t_DoubleListItem* pAfter, t_DoubleListItem* pItem );
bool removeItem( t_DoubleListItem* pItem );
bool deleteItem( t_DoubleListItem* pItem );
void forEach( void (*func)( t_DoubleListItem* ) );

protected:
t_DoubleListItem* m_pHead; // указатель на первый добавленный элемент
t_DoubleListItem* m_pTail; // указатель на последний добавленный элемент
};


// создание пустого списка
inline t_DoubleList::t_DoubleList() : m_pHead(0), m_pTail(0)
{
}

// уничтожение списка
t_DoubleList::~t_DoubleList()
{
clear();
}

//--------------------------------------------------------------
// Функция очищает список. Все элементы также уничтожаются.
//--------------------------------------------------------------
void t_DoubleList::clear()
{
t_DoubleListItem* p = m_pHead;
while( p ) {
t_DoubleListItem* pNext = p->next;
freeItem( p ); // удаляем элемент
p = pNext; // переходим к следующему
}
m_pHead = 0; m_pTail = 0;
}

//--------------------------------------------------------------
// Виртуальная функция - удаление элемента в предположении, что
// память для него выделена посредством new. Если это не так, то
// функция должна быть перекрыта в производном классе.
//--------------------------------------------------------------
void t_DoubleList::freeItem( t_DoubleListItem* pItem )
{
delete pItem;
}

//--------------------------------------------------------------
// Функция вставляет новый элемент в конец списка
//--------------------------------------------------------------
void t_DoubleList::add( t_DoubleListItem* pItem )
{
pItem->prev = m_pTail; // предыдущий элемент - прежний конец списка
pItem->next = NULL; // следующего элемента пока нет
if( !m_pHead ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else // иначе добавляем в конец
m_pTail->next = pItem;

m_pTail = pItem; // новый конец списка
}


//--------------------------------------------------------------
// Вставляем новый элемент после элемента pInsertAfter.
// Если pInsertAfter == NULL, то вставляем в начало списка.
// Функция возвращает true, если элемент вставлен в список,
// false - если элемент pInsertAfter не найден.
//--------------------------------------------------------------
bool t_DoubleList::insertAfter( const t_DoubleListItem* pInsertAfter, t_DoubleListItem* pItem )
{
t_DoubleListItem* p = m_pHead;
if( !pInsertAfter ) { // вставляем в начало списка
pItem->next = p; // следующий элемент - прежнее начало списка
pItem->prev = NULL; // предыдущего элемента нет - это новое начало списка
if( !p ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else
p->prev = pItem;

m_pHead = pItem; // новое начало списка
return true;
}
while( p ) {
// ищем элемент, после которого нужно вставлять
if( p == pInsertAfter ) {
t_DoubleListItem* pNext = p->next;
if( pNext )
pNext->prev = pItem;
else // pInsertAfter == m_pTail, причем здесь m_pTail != 0
m_pTail = pItem; // новый конец списка

pItem->prev = p;

pItem->next = pNext;
p->next = pItem;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, сам элемент не меняется.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден.
//--------------------------------------------------------------
bool t_DoubleList::removeItem( t_DoubleListItem* pItem )
{
t_DoubleListItem* p = m_pHead;
while( p ) {
// ищем удаляемый элемент
if( p == pItem ) {
if( p->prev )
p->prev->next = p->next;
else // удаляется начало списка
m_pHead = p->next;

if( p->next )
p->next->prev = p->prev;
else // удаляется конец списка
m_pTail = p->prev;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, затем уничтожаем сам элемент.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден, в этом случае элемент
// не меняется.
//--------------------------------------------------------------
bool t_DoubleList::deleteItem( t_DoubleListItem* pItem )
{
if( removeItem(pItem) ) {
freeItem( pItem );
return true;
}
return false;
}


//--------------------------------------------------------------
// Выполняет функцию func для каждого элемента списка
//--------------------------------------------------------------
void t_DoubleList::forEach( void (*func)( t_DoubleListItem* ) )
{
if( !func ) return;

t_DoubleListItem* p = m_pHead;
while( p ) {
func( p );
p = p->next;
}
}

//==============================================================
// Проверка некоторых функций списка
//==============================================================

// объявляем производный класс,
// содержащий элементы нужного нам типа
struct t_ItemInt : public t_DoubleListItem
{
t_ItemInt( int i ) : m_i(i) {}

int m_i;
};


void printValue( t_DoubleListItem* p )
{
printf( "%4d\n", ((t_ItemInt*)p)->m_i );
}

void main()
{
t_DoubleList* pListInt = new t_DoubleList();

t_ItemInt* a[10];
for( int i = 0; i < 10; ++i ) {
t_ItemInt* p = new t_ItemInt(i);
a[i] = p;
pListInt->add( p );
}
pListInt->insertAfter( 0, new t_ItemInt(-1)); // вставка в начало
pListInt->insertAfter( a[5], new t_ItemInt(-5)); // вставка в середину
pListInt->insertAfter( a[9], new t_ItemInt(50)); // добавление в конец
pListInt->add( new t_ItemInt(100) ); // добавление в конец
pListInt->deleteItem( a[7] ); // удаление элемента
pListInt->forEach( printValue ); // распечатываем список
delete pListInt;
}
5
Неизвестный
08.05.2010, 00:13
общий
Селеверстов Антон Юрьевич:
Надеюсь, что кто-то из экспертов добавит решение с применением шаблонов. К сожалению, у меня сейчас уже нет времени делать это, разве что после праздника (11 мая).
Неизвестный
08.05.2010, 01:53
общий
Большое спасибо, пойду разбираться, уделенное внимание и компетентная помощь экспертов очень радует.
Так-же хотелось бы видеть второй вариант решения задачи (с помощью шаблонов) дабы разобраться для себя во всех нюансах обоих спопобов
Неизвестный
13.05.2010, 21:33
общий
Селеверстов Антон Юрьевич:
Извините за задержку с ответом — много работы. И спасибо Вам за оценку моей работы.

Несмотря на то, что использование шаблонов (template) всячески рекламируется, мне они не нравятся. Именно поэтому я привел Вам вариант без их использования. Ниже привожу 2 варианта: "более шаблонистый" и менее. Из них я бы предпочел "менее шаблонистый". Причина в том, что в другом варианте для каждого типа, используемого в двусвязном списке (t_DoubleListItem<int>, t_DoubleListItem<char>, t_DoubleListItem<MyStruct>) компилятор будет генерировать свой код для класса t_DoubleList (t_DoubleList<int>, t_DoubleList<char>, t_DoubleList<MyStruct>)), что приведет к совершенно необоснованному, на мой взгляд, разбуханию сгенерированного файла, если программа использует несколько типов данных. К тому же, в "более шаблонистом" варианте необходимо все методы класса (template <class T> class t_DoubleList) реализовывать во включаемом файле. Мне это не нравится. В данном случае, класс t_DoubleList не обращается к данным, хранимым в списке, поэтому объявление этого класса как
template <class T> class t_DoubleList
является, на мой взгляд, излишним. Единственное преимущество этого варианта — проверка типов данных, включаемых в список, на этапе компиляции.

Сравните, как используются списки в этих двух вариантах (после комментария "Проверка некоторых функций списка"). Разница совсем небольшая.

"более шаблонистый" вариант:
Код:
/*
вопрос № 178253
Написать программу, реализующую двухсвязный список, который хранит
элементы произвольного типа (Программа заранее не знает, данные какого
типа будут хранится в элементах списка). Реализовать полный набор
операций (создание списка, вставка эл-та, удаление эл-та).

Вариант с избыточным использованием шаблонов.
*/
#include <stdlib.h>
#include <stdio.h>

template <class T> struct t_DoubleListItem
{
t_DoubleListItem<T>( T val ) { data = val; }

t_DoubleListItem<T>* next;
t_DoubleListItem<T>* prev;
T data;
};

template <class T> class t_DoubleList
{
public:
t_DoubleList();
virtual ~t_DoubleList();

virtual void freeItem( t_DoubleListItem<T>* pItem );

void clear();
void add( t_DoubleListItem<T>* pItem );
bool insertAfter( const t_DoubleListItem<T>* pAfter, t_DoubleListItem<T>* pItem );
bool removeItem( t_DoubleListItem<T>* pItem );
bool deleteItem( t_DoubleListItem<T>* pItem );
void forEach( void (*func)( t_DoubleListItem<T>* ) );

protected:
t_DoubleListItem<T>* m_pHead; // указатель на первый добавленный элемент
t_DoubleListItem<T>* m_pTail; // указатель на последний добавленный элемент
};


// создание пустого списка
template <class T>
inline t_DoubleList<T>::t_DoubleList() : m_pHead(0), m_pTail(0)
{
}

// уничтожение списка
template <class T>
t_DoubleList<T>::~t_DoubleList()
{
clear();
}

//--------------------------------------------------------------
// Функция очищает список. Все элементы также уничтожаются.
//--------------------------------------------------------------
template <class T>
void t_DoubleList<T>::clear()
{
t_DoubleListItem<T>* p = m_pHead;
while( p ) {
t_DoubleListItem<T>* pNext = p->next;
freeItem( p ); // удаляем элемент
p = pNext; // переходим к следующему
}
m_pHead = 0; m_pTail = 0;
}

//--------------------------------------------------------------
// Виртуальная функция - удаление элемента в предположении, что
// память для него выделена посредством new. Если это не так, то
// функция должна быть перекрыта в производном классе.
//--------------------------------------------------------------
template <class T>
void t_DoubleList<T>::freeItem( t_DoubleListItem<T>* pItem )
{
delete pItem;
}

//--------------------------------------------------------------
// Функция вставляет новый элемент в конец списка
//--------------------------------------------------------------
template <class T>
void t_DoubleList<T>::add( t_DoubleListItem<T>* pItem )
{
pItem->prev = m_pTail; // предыдущий элемент - прежний конец списка
pItem->next = NULL; // следующего элемента пока нет
if( !m_pHead ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else // иначе добавляем в конец
m_pTail->next = pItem;

m_pTail = pItem; // новый конец списка
}


//--------------------------------------------------------------
// Вставляем новый элемент после элемента pInsertAfter.
// Если pInsertAfter == NULL, то вставляем в начало списка.
// Функция возвращает true, если элемент вставлен в список,
// false - если элемент pInsertAfter не найден.
//--------------------------------------------------------------
template <class T>
bool t_DoubleList<T>::insertAfter( const t_DoubleListItem<T>* pInsertAfter, t_DoubleListItem<T>* pItem )
{
t_DoubleListItem<T>* p = m_pHead;
if( !pInsertAfter ) { // вставляем в начало списка
pItem->next = p; // следующий элемент - прежнее начало списка
pItem->prev = NULL; // предыдущего элемента нет - это новое начало списка
if( !p ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else
p->prev = pItem;

m_pHead = pItem; // новое начало списка
return true;
}
while( p ) {
// ищем элемент, после которого нужно вставлять
if( p == pInsertAfter ) {
t_DoubleListItem<T>* pNext = p->next;
if( pNext )
pNext->prev = pItem;
else // pInsertAfter == m_pTail, причем здесь m_pTail != 0
m_pTail = pItem; // новый конец списка

pItem->prev = p;

pItem->next = pNext;
p->next = pItem;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, сам элемент не меняется.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден.
//--------------------------------------------------------------
template <class T>
bool t_DoubleList<T>::removeItem( t_DoubleListItem<T>* pItem )
{
t_DoubleListItem<T>* p = m_pHead;
while( p ) {
// ищем удаляемый элемент
if( p == pItem ) {
if( p->prev )
p->prev->next = p->next;
else // удаляется начало списка
m_pHead = p->next;

if( p->next )
p->next->prev = p->prev;
else // удаляется конец списка
m_pTail = p->prev;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, затем уничтожаем сам элемент.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден, в этом случае элемент
// не меняется.
//--------------------------------------------------------------
template <class T>
bool t_DoubleList<T>::deleteItem( t_DoubleListItem<T>* pItem )
{
if( removeItem(pItem) ) {
freeItem( pItem );
return true;
}
return false;
}


//--------------------------------------------------------------
// Выполняет функцию func для каждого элемента списка
//--------------------------------------------------------------
template <class T>
void t_DoubleList<T>::forEach( void (*func)( t_DoubleListItem<T>* ) )
{
if( !func ) return;

t_DoubleListItem<T>* p = m_pHead;
while( p ) {
func( p );
p = p->next;
}
}

//==============================================================
// Проверка некоторых функций списка
//==============================================================


void printValue( t_DoubleListItem<int>* p )
{
printf( "%4d\n", p->data );
}

void main()
{
// при использовании класса указываем нужный нам тип
t_DoubleList<int>* pListInt = new t_DoubleList<int>();

t_DoubleListItem<int>* a[10];
for( int i = 0; i < 10; ++i ) {
t_DoubleListItem<int>* p = new t_DoubleListItem<int>( i );
a[i] = p;
pListInt->add( p );
}
pListInt->insertAfter( 0, new t_DoubleListItem<int>(-1)); // вставка в начало
pListInt->insertAfter( a[5], new t_DoubleListItem<int>(-5)); // вставка в середину
pListInt->insertAfter( a[9], new t_DoubleListItem<int>(50)); // добавление в конец
pListInt->add( new t_DoubleListItem<int>(100) ); // добавление в конец
pListInt->deleteItem( a[7] ); // удаление элемента
pListInt->forEach( printValue ); // распечатываем список
delete pListInt;
}


"менее шаблонистый" вариант — более разумное, по-моему, использование шаблонов:
Код:
/*
вопрос № 178253
Написать программу, реализующую двухсвязный список, который хранит
элементы произвольного типа (Программа заранее не знает, данные какого
типа будут хранится в элементах списка). Реализовать полный набор
операций (создание списка, вставка эл-та, удаление эл-та).
Вариант с использованием шаблонов.
*/
#include <stdlib.h>
#include <stdio.h>

// Эта структура напрямую используется только в классе t_DoubleList
// Пользовательские типы данных следует объявлять с использованием шаблона
struct __t_DoubleListItem
{
__t_DoubleListItem* next;
__t_DoubleListItem* prev;
};

// шаблон для объявления пользовательских типов данных
template <class T> struct t_DoubleListItem : __t_DoubleListItem
{
t_DoubleListItem<T>( T val ) { data = val; }
T data;
};

class t_DoubleList
{
public:
t_DoubleList();
virtual ~t_DoubleList();

virtual void freeItem( __t_DoubleListItem* pItem );

void clear();
void add( __t_DoubleListItem* pItem );
bool insertAfter( const __t_DoubleListItem* pAfter, __t_DoubleListItem* pItem );
bool removeItem( __t_DoubleListItem* pItem );
bool deleteItem( __t_DoubleListItem* pItem );
void forEach( void (*func)( __t_DoubleListItem* ) );

protected:
__t_DoubleListItem* m_pHead; // указатель на первый добавленный элемент
__t_DoubleListItem* m_pTail; // указатель на последний добавленный элемент
};


// создание пустого списка
inline t_DoubleList::t_DoubleList() : m_pHead(0), m_pTail(0)
{
}

// уничтожение списка
t_DoubleList::~t_DoubleList()
{
clear();
}

//--------------------------------------------------------------
// Функция очищает список. Все элементы также уничтожаются.
//--------------------------------------------------------------
void t_DoubleList::clear()
{
__t_DoubleListItem* p = m_pHead;
while( p ) {
__t_DoubleListItem* pNext = p->next;
freeItem( p ); // удаляем элемент
p = pNext; // переходим к следующему
}
m_pHead = 0; m_pTail = 0;
}

//--------------------------------------------------------------
// Виртуальная функция - удаление элемента в предположении, что
// память для него выделена посредством new. Если это не так, то
// функция должна быть перекрыта в производном классе.
//--------------------------------------------------------------
void t_DoubleList::freeItem( __t_DoubleListItem* pItem )
{
delete pItem;
}

//--------------------------------------------------------------
// Функция вставляет новый элемент в конец списка
//--------------------------------------------------------------
void t_DoubleList::add( __t_DoubleListItem* pItem )
{
pItem->prev = m_pTail; // предыдущий элемент - прежний конец списка
pItem->next = NULL; // следующего элемента пока нет
if( !m_pHead ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else // иначе добавляем в конец
m_pTail->next = pItem;

m_pTail = pItem; // новый конец списка
}


//--------------------------------------------------------------
// Вставляем новый элемент после элемента pInsertAfter.
// Если pInsertAfter == NULL, то вставляем в начало списка.
// Функция возвращает true, если элемент вставлен в список,
// false - если элемент pInsertAfter не найден.
//--------------------------------------------------------------
bool t_DoubleList::insertAfter( const __t_DoubleListItem* pInsertAfter, __t_DoubleListItem* pItem )
{
__t_DoubleListItem* p = m_pHead;
if( !pInsertAfter ) { // вставляем в начало списка
pItem->next = p; // следующий элемент - прежнее начало списка
pItem->prev = NULL; // предыдущего элемента нет - это новое начало списка
if( !p ) // если первый элемент в списке,
m_pHead = pItem; // ... то инициализируем указатель на начало
else
p->prev = pItem;

m_pHead = pItem; // новое начало списка
return true;
}
while( p ) {
// ищем элемент, после которого нужно вставлять
if( p == pInsertAfter ) {
__t_DoubleListItem* pNext = p->next;
if( pNext )
pNext->prev = pItem;
else // pInsertAfter == m_pTail, причем здесь m_pTail != 0
m_pTail = pItem; // новый конец списка

pItem->prev = p;

pItem->next = pNext;
p->next = pItem;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, сам элемент не меняется.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден.
//--------------------------------------------------------------
bool t_DoubleList::removeItem( __t_DoubleListItem* pItem )
{
__t_DoubleListItem* p = m_pHead;
while( p ) {
// ищем удаляемый элемент
if( p == pItem ) {
if( p->prev )
p->prev->next = p->next;
else // удаляется начало списка
m_pHead = p->next;

if( p->next )
p->next->prev = p->prev;
else // удаляется конец списка
m_pTail = p->prev;
return true;
}
p = p->next; // переходим к следующему
}
return false;
}

//--------------------------------------------------------------
// Удаляем элемент pItem из списка, затем уничтожаем сам элемент.
// Функция возвращает true, если элемент удален из списка,
// false - если элемент pItem не найден, в этом случае элемент
// не меняется.
//--------------------------------------------------------------
bool t_DoubleList::deleteItem( __t_DoubleListItem* pItem )
{
if( removeItem(pItem) ) {
freeItem( pItem );
return true;
}
return false;
}


//--------------------------------------------------------------
// Выполняет функцию func для каждого элемента списка
//--------------------------------------------------------------
void t_DoubleList::forEach( void (*func)( __t_DoubleListItem* ) )
{
if( !func ) return;

__t_DoubleListItem* p = m_pHead;
while( p ) {
func( p );
p = p->next;
}
}

//==============================================================
// Проверка некоторых функций списка
//==============================================================

void printValue( __t_DoubleListItem* p )
{
printf( "%4d\n", ((t_DoubleListItem<int>*)p)->data );
}

void main()
{
t_DoubleList* pListInt = new t_DoubleList();

// при использовании класса указываем нужный нам тип элементов
t_DoubleListItem<int>* a[10];
for( int i = 0; i < 10; ++i ) {
t_DoubleListItem<int>* p = new t_DoubleListItem<int>( i );
a[i] = p;
pListInt->add( p );
}
pListInt->insertAfter( 0, new t_DoubleListItem<int>(-1)); // вставка в начало
pListInt->insertAfter( a[5], new t_DoubleListItem<int>(-5)); // вставка в середину
pListInt->insertAfter( a[9], new t_DoubleListItem<int>(50)); // добавление в конец
pListInt->add( new t_DoubleListItem<int>(100) ); // добавление в конец
pListInt->deleteItem( a[7] ); // удаление элемента
pListInt->forEach( printValue ); // распечатываем список
delete pListInt;
}

Неизвестный
14.05.2010, 19:43
общий
Большое спасибо, пойду разбираться
Форма ответа