Реализация динамического списка
Даны две очереди целых чисел от 0 до 1000. Из элементов первой очереди, которых нет во второй, сформировать стек. Выполнить задания, используя для представления очередей и стеков:
а) массивы
б) динамические списки
#include <iostream>
#include <time.h>
#include <utility>
#include <initializer_list>
using namespace std;
// начало функций для массива
struct Stack {
int top;
int* data;
};
void InitStack(Stack& st, int capacity) {
st.data = new int[capacity];
st.top = -1;
}
void push(Stack& st, int value) {
st.data[++st.top] = value;
}
int pop(Stack& st) {
return st.data[st.top--];
}
void nullStack(Stack& st) {
st.top = -1;
}
bool empty(Stack& st) {
return st.top == -1;
}
void print(Stack& st) {
if (empty(st)) cout << "Stack is empty" << endl;
else {
int tmp = st.top;
while (!empty(st)) cout << pop(st) << " ";
st.top = tmp;
}
cout << endl;
}
struct Queue1 {
int head, tail, size;
int* data;
};
void nullQueue(Queue1& q) {
q.head = 0;
q.tail = q.size - 1;
}
void InitQueue(Queue1& q, int capacity) {
q.size = capacity + 1;
q.data = new int[q.size];
nullQueue(q);
}
int next(Queue1& q, int n) {
return(n + 1) % q.size;
}
bool empty(Queue1& q) {
return next(q, q.tail) == q.head;
}
void add(Queue1& q, int value) {
if (next(q, next(q, q.tail)) == q.head)
cout << "Queue overflow" << endl;
else {
q.tail = next(q, q.tail);
q.data[q.tail] = value;
}
}
int del(Queue1& q) {
if (empty(q)) {
cout << "Queue is empty" << endl;
return 0;
}
else
{
int d = q.data[q.head];
q.head = next(q, q.head);
return d;
}
}
void print(Queue1& q) {
if (empty(q)) cout << "Queue is empty";
else {
int tmph = q.head;
int tmpt = q.tail;
while (!(empty(q))) cout << del(q) << " ";
q.head = tmph;
q.tail = tmpt;
}
cout << endl;
}
// конец функций для массива
// начало функций для линейного списка
struct Node {
int data;
Node* next;
};
void InitStack(Node*& top) {
top = NULL;
}
void push(Node*& top, int value) {
Node* tmp = new Node;
tmp->next = top;
top = tmp;
top->data = value;
}
int pop(Node*& top) {
Node* tmp = top;
int d = top->data;
top = top->next;
delete(tmp);
return d;
}
bool empty(Node*& top) {
return top == NULL;
}
void nullStack(Node*& top) {
Node* tmp;
while (!empty(top)) {
tmp = top;
top = top->next;
delete(tmp);
}
}
void print(Node*& top) {
Node* tmp = top;
while (!empty(tmp)) {
cout << tmp->data << " ";
tmp = tmp->next;
}
cout << endl;
}
class Queue {
public:
//int size;
Node* next;
Node* head, * tail;
int* data;
Queue() {
//this->size = size;
//this->data = new int[size];
data = NULL;
head = NULL;
tail = NULL;
}
bool empty() {
return head == NULL;
}
void add(int value) {
if (empty()) {
head = new Node;
head->data = value;
head->next = NULL;
tail = head;
}
else {
tail->next = new Node;
tail = tail->next;
tail->data = value;
tail->next = NULL;
}
}
int del() {
if (empty()) {
cout << "Queue is empty" << endl;
return 0;
}
else {
int d = head->data;
Node* tmp = head;
head = head->next;
delete(tmp);
return d;
}
}
void nullQueue() {
Node* tmp;
while (!empty()) {
tmp = head;
head = head->next;
delete(tmp);
}
}
void print() {
Node* tmp = head;
while (tmp != tail) {
cout << tmp->data << " ";
tmp = tmp->next;
}
cout << tail->data << endl;
}
Queue(Queue&& a) {
std::swap(data, a.data);
//std::swap(size, a.size);
std::swap(head, a.head);
std::swap(tail, a.tail);
}
/*int i = 0;
this->data = new int[a.size];
this->size = a.size;
while (a.head != a.tail) {
Node* tmp = a.head;
this->head = tmp;
tmp = tmp->next;
}
for (i; i < size; i++) {
this->data[i] = a.data[i];
}
this->tail = a.tail;
this->data[i] = a.data[i];
}*/
}; // конец функций для линейного списка
void main() {
srand(time_t(0));
//int n = rand() % 10 + 1, S;
int n, S = 0;
cout << "Count: ";
cin >> n;
bool b;
cout << "Press 1 for array or 2 for linear list" << endl;
cin >> S;
switch (S) {
case 1:
{
Stack s;
Queue1 q1, q2;
InitQueue(q1, n);
for (int i = 0; i < n; i++) {
add(q1, rand() % 1001);
}
cout << "Queue1: ";
print(q1);
InitQueue(q2, n);
for (int i = 0; i < n; i++) {
add(q2, rand() % 1001);
}
cout << "Queue2: ";
print(q2);
InitStack(s, n);
for (int i = 0; i <= q1.size; i++) {
b = false;
for (int j = 0; j <= q2.size; j++) {
if (q1.data[i] != q2.data[j])
b = true;
else {
b = false;
break;
}
}
if (b == true)
push(s, q1.data[i]);
}
cout << "New stack: ";
if (!empty(s))
print(s);
else
cout << "stack is empty" << endl;
break;
}
case 2:
{
Node* s;
Queue Q1, Q2;
for (int i = 0; i < n; i++) {
Q1.add(rand() % 10);
}
cout << "Queue1: ";
Q1.print();
for (int i = 0; i < n; i++) {
Q2.add(rand() % 10);
}
cout << "Queue2: ";
Q2.print();
InitStack(s);
while (!Q1.empty()) {
int d1 = Q1.del();
b = true;
Queue Q3(std::move(Q2));
while (!Q3.empty()) {
int d2 = Q3.del();
if (d1 == d2)
b = false;
}
if (b == true)
push(s, d1);
Q3.nullQueue();
}
cout << "New stack: ";
if (!empty(s))
print(s);
else
cout << "stack is empty" << endl;
break;
}
default:
cout << "This program doesn't have another function" << endl;
break;
}
std::system("pause");
}
Ответы (2 шт):
Несколько замечаний по архитектуре решения. У вас по заданию c++, а код вы пишете фактически на c, не пользуясь преимуществами c++.
Давайте по порядку. Вам нужно реализовать два контейнера "низкого" уровня - в которых физически будут храниться данные - это массив Array и динамический список List. (пользоваться std::vector и std::list видимо нельзя?) Для этих контейнеров нужно выделить набор методов, которые нужны в задаче. В частности:
- добавление элемента в контейнер -
push() - поиск элемента -
find() - сортировка контейнера
sort() - движение по контейнеру (перебор всех элементов по очереди) - итератор + перевод итератора на следующий элемент
next()+ получение значения по итераторуget()+ перевод итератора на первый элемент контейнераto_first()
Как-то так, например:
class Container // виртуальный базовый класс
{
public:
virtual void push(int a) = 0; // добавить значение в конец
virtual bool find(int a) = 0; // найти значение
virtual void sort(void) = 0; // сортировать значения в контейнере по возрастанию
virtual void to_first(void) = 0; // установить итератор на первый элемент
virtual int next(void) = 0; // перевести итератор на следующий элемент
virtual int get(void) = 0; // получить значение элемента по итератору
};
class List : public Container
{
struct ListNode // структура одного элемента списка
{
int data_;
ListNode* next_ = nullptr;
};
ListNode *head_ = nullptr,
*tail_ = nullptr; // указатели на начало и конец
ListNode *iter_ = nullptr; // итератор на текущее значение
public:
void push(int a) override {}
bool find(int a) override {}
void sort(void) override {} // для списка можно не реализовывать
// если сортировать список при создании
void to_first(void) override {}
int next(void) override {}
int get(void) override {}
List(){}
~List(){} // пройти по списку и удалить элементы
};
class Array : public Container
{
int* data_ = nullptr;
int capacity;
int iter;
public:
void push(int a) override {}
bool find(int a) override {} // можно использовать сортировку std::find
void sort(void) override {} // можно использовать сортировку std::sort
void to_first(void) override {}
int next(void) override {}
int get(void) override {}
Array(int cap) : capacity(cap) {} // выделить память
~Array() {} // удалить память
};
Виртуальный базовый класс перечисляет обязательный набор функций (интерфейс), который должен быть у любого контейнера. Кроме того, он позволит работать с классом-наследником через указатель на базовый класс.
Сортировка нужна для более быстрого поиска элемента в контейнере. Можно её не делать, тогда для поиска вхождения элемента в контейнер нужно будет пройти по всему контейнеру. С сортировкой - тратим время на сортировку, но потом поиск быстрее. Без сортировки - не тратим время на сортировку, но поиск элемента дольше. Выбирать вам.
Если с сортировкой, то у списка List можно её не делать - проще сразу вставлять элементы в порядке возрастания, а для массива можно вызвать std::sort().
Текст всех этих функций вы написали в своём коде, но он разбросан по всему листингу.
Таким образом интерфейс одинаковый, а работает каждый по-своему. Например добавление элемента:
- для списка нужно создать элемент через
new(), установить указатели и т.д. - для массива - записать значение в массив, сдвинуть итератор
Далее, вам нужно реализовать "высокоуровневые" структуры (в рамках этой задачи) - очередь Queue и стек Stack. В них должна быть ссылка (или указатель) на контейнер низкого уровня, который используется для физического хранения данных. И в принципе такой же набор функций, но которые будут вызывать функции вложенного контейнера.
class Queque
{
Container& cont;
// или так
// Container* cont;
public:
Queque(Container& a) : cont(a){}
// Queque(Container* a) : cont(a){}
void push(int a) override { cont.push(a); }
bool find(int a) override { cont.find(a); }
void sort(void) override { cont.sort(); }
void to_first(void) override {}
int next(void) override {}
int get(void) override {}
};
class Stack
{
Container& cont;
public:
Stack(Container& a) : cont(a){}
void push(int a) {};
int pop(void) {}
};
Поскольку функции будут точно такими же, как и у "низкоуровневых" контейнеров, очередь и стек тоже можно наследовать от class Container, а можно и не наследовать. Нужно будет дополнительно реализовать функции, свойственные только очереди и стеку.
Таким образом, остается только передать в очередь и стек ссылку/указатель на контейнер, в котором физически будут храниться данные. Передадите в очередь ссылку на список - получите контейнер Queue, который хранит данные в памяти в списке List. И т.д.
И тогда работа с ними станет проще - она не будет зависеть от того, как физически хранятся данные в памяти.
int main()
{
const int n = 100;
Array arr1(n);
Array arr2(n);
Array arr3(n);
Queue q1(arr1);
Queue q2(arr2);
Stack st(arr3);
// инициализация очередей
for (int i = 0; i < n; i++)
{
q1.push(rand() % 1001);
q2.push(rand() % 1001);
}
q1.to_first(); // итератор на первый элемент
for (int i = 0; i < n; i++) // цикл по всем элементам
{
if(!q2.find( q1.get() ) ) // если элемент из первой очереди не найден во второй очереди
st.push( q1.get() ); // добавить в стек
q1.next(); // перейти на следующий элемент
}
return 0;
}
Это приблизительный набросок простенького интерфейса, чтобы понять возможности, предоставляемые парадигмой ООП.
Если насчет не работала реализация динамическим списком - у вас там две ошибки.
Первая - вы не обнулили указатели на начало списка, начало очередей и т.д. А поскольку вы добавляете узлы в начало списка, то при добавлении первого элемента его указатель next равен чему-то, но не нулю. И в дальнейшем, при проходе по списку вы не останавливаетесь, а уходите за границу выделенной памяти, что является неопределенным поведением -> крах программы. И проверка на пустой список/очередь у вас через сравнение bool empty() { return head == NULL; }, а у только что созданной очереди указатель head != NULL.
case 2:
{
// Node* s; // нужно обнулить
Node* s = nullptr;
class Queue {
public:
Node* next = nullptr; // этот указатель здесь вообще не нужен
Node* head = nullptr, * tail = nullptr; // начало и конец надо обнулить, хотя tail не используется
int* data = nullptr;
}
Ещё одна огромная ошибка Queue Q3(std::move(Q2)); Вы создаете Q3 через move от Q2, в результате все данные попадают в Q3, Q2 становится пустым. Далее вы удаляете все элементы из Q3. На следующей итерации Q3 создается из пустого Q2. Это продолжает работать, но фактически только первый элемент из Q1 ищется в Q2, все остальные попадаются прямиком в стек.
while (!Q1.empty()) {
int d1 = Q1.del();
b = true;
Queue Q3(std::move(Q2)); // почему move()? надо-то сделать копию!
// на следующей итерации Q2 - пуст
while (!Q3.empty()) {
int d2 = Q3.del(); // удаляется
if (d1 == d2)
b = false;
}
if (b == true)
push(s, d1);
Q3.nullQueue();
}
Чтобы не копировать/удалять постоянно элементы сделайте в классе Queue функцию bool find(int a) (подсказка - такая же как print() ), которая проходит по списку и сравнивает значение с искомым. И тогда код упроститься:
while (!Q1.empty()) {
int d1 = Q1.del();
b = Q2.find(d1);
if (b == false)
push(s, d1);
}
А для упрощения понимания кода сделайте 4 класса, в которых будет всего 4 функции, и перенесите код в них. Т.к. и у очереди и у стека для работы всего 2 функции push() для добавления элемента и pop() для удаления элемента. Ну а print() и find() - вспомогательные. Причем find() нужен только для очереди.
class QueueOnList
{
public:
void push(int a);
int pop(void);
void print(void);
bool find(int a);
}
class QueueOnArray{}
class StackOnList{}
class StackOnArray{}
Будет проще отлаживать, тестировать и т.д.