Реализация динамического списка

Даны две очереди целых чисел от 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 шт):

Автор решения: DmitryK

Несколько замечаний по архитектуре решения. У вас по заданию 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;
}

Это приблизительный набросок простенького интерфейса, чтобы понять возможности, предоставляемые парадигмой ООП.

→ Ссылка
Автор решения: DmitryK

Если насчет не работала реализация динамическим списком - у вас там две ошибки. Первая - вы не обнулили указатели на начало списка, начало очередей и т.д. А поскольку вы добавляете узлы в начало списка, то при добавлении первого элемента его указатель 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{}

Будет проще отлаживать, тестировать и т.д.

→ Ссылка