Это странный вопрос о сложности космоса. Может кто-нибудь дать какие-то идеи? - PullRequest
0 голосов
/ 01 мая 2020

Я решал этот вопрос, когда этот подход включил -

Учитывая один связанный список и целое число x. Ваша задача - завершить функцию deleteAllOccurances (), которая удаляет все вхождения ключа x, присутствующего в связанном списке. Функция принимает два аргумента: заголовок связанного списка и целое число x. Функция должна возвращать заголовок измененного связанного списка.

Я не уверен, какова сложность пространства моего кода.

Я думаю, поскольку я использую только 1 дополнительное пространство узла и одновременно создание новых узлов и удаление старых, поэтому должно быть O (1).

Node* deleteAllOccurances(Node *head,int x)
{
   Node *new_head = new Node(-1);
   Node *tail = new_head;
   Node *temp = head;
   Node *q;

   while(temp != NULL) {
      if(temp->data != x) {
        tail->next = new Node(temp->data);
        tail = tail->next;
      }

       q = temp;
       delete q;
       temp = temp->next;
    }

   tail->next = NULL;
   return new_head->next;
}

Ответы [ 3 ]

0 голосов
/ 01 мая 2020

Ну, вроде.

Это зависит от того, рассматриваете ли вы общие ассигнования как net изменение (в этом случае вы правы).

Но , если вы думаете о Количество раз, когда вы попадаете в кучу для новых выделений, тогда он использует больше места и массу вычислений. (Данный компилятор и среда выполнения C ++ не обязаны гарантировать немедленное повторное использование свободного места в куче, просто доступно для повторного использования.)

Как программист C ++ на протяжении десятилетий то, что вы делаете, слегка ужасает, потому что вы делаете лот нового распределения. Это приводит к разрушению структур выделения кучи.

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

Подсказка - вам не нужно создавать любые новые узлы.

0 голосов
/ 02 мая 2020

Измерение сложности частично зависит от того, что вы считаете своими переменными. Что касается количества узлов в списке, ваш алгоритм занимает O(1) в использовании пространства. Однако в этом случае это может быть не самой лучшей перспективой.

Другая переменная в этой ситуации - это размер узла. Часто этот аспект игнорируется анализом сложности, но я думаю, что он имеет значение в этом случае. Хотя требования к пространству вашего алгоритма не зависят от количества узлов, они зависят от размера узла. Чем больше данных в узле, тем больше места вам нужно. Пусть s будет размером одного узла; было бы справедливо сказать, что требование к размеру вашего алгоритма составляет O(s).

Требование к размеру более распространенного алгоритма для этой задачи равно O(1), даже если учитывается как количество узлов, так и размер каждый узел. (Нет необходимости создавать какие-либо узлы, нет необходимости копировать данные.) Я бы не рекомендовал ваш алгоритм по этому.


Чтобы не было всех негативных, я бы рассматривал ваш подход как два независимых изменения к традиционному. Одним из изменений является введение фиктивного узла new_head. Это изменение полезно (и фактически используется), даже если ваша реализация утечка памяти. Это лишь незначительно менее эффективно, чем использование фиктивной головки, и это упрощает logi c для удаления узлов в начале списка. Это хорошо, если размер вашего узла не слишком велик.

Другим изменением является переключение на копирование узлов вместо их перемещения. Это очень ценное изменение, так как оно добавляет работы программисту, компилятору и выполнению. Анализ Asymptoti c (big-O) может не поднять это дополнение, но оно не принесет никаких выгод. Вы потеряли ключевое преимущество связанных списков и ничего не получили взамен.

Давайте посмотрим, как отбросить второе изменение. Вам нужно было бы добавить одну строку, в частности, инициализируя new_head->next в head, но это уравновешивается удалением необходимости устанавливать tail->next в nullptr в конце. Другим дополнением является предложение else, так что строки, выполняемые в настоящее время на каждой итерации, не обязательно выполняются на каждой итерации. Кроме того, это удаление кода и некоторые изменения имени: удалите указатель temp (используйте вместо него tail->next) и создайте новые узлы в l oop. Взятые вместе, эти изменения строго сокращают объем выполняемой работы (и потребности в памяти) по сравнению с вашим кодом.

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

Node* deleteAllOccurances(Node *head, int x)
{
    Node new_head{-1};              //<-- Avoid dynamic allocation
    new_head.next = head;           //<-- added line
    Node *tail = &new_head;

    while(tail->next != nullptr) {
        if(tail->next->data != x) {
            tail = tail->next;
        }
        else {                      //<-- make the rest of the loop conditional
            Node *q = tail->next;
            tail->next = tail->next->next;
            delete q;
        }
    }

   return new_head.next;
}

В этой версии устраняется «фактор ограничения», поскольку для одного узла есть преимущество new не используется. Эта версия достаточно чиста, чтобы подвергнуть ее анализу сложности, и никто не спросит «почему ???».

0 голосов
/ 01 мая 2020

Да, поскольку количество места, выделенного вами в любой момент времени, не зависит от аргументов (например, длины списка или количества значений x в списке), сложность пространства функции O(1)

Практическая точка сложности пространства состоит в том, чтобы увидеть, сколько памяти потребуется вашему алгоритму. Вам никогда не потребуется более 1 узла памяти (плюс локальные переменные), и O(1) отражает это.

...