Спасибо всем за ваши ответы! Я понимаю, что этот вопрос задавался почти два года назад, и ответ давно принят, но ответы меня немного смутили. Таким образом, несмотря на то, что спрашивающий, вероятно, не заботится о новых ответах, я хотел бы добавить свою версию, на случай, если другие читатели также будут смущены, и задокументировать мой собственный подход. Кое-что из этого могло бы быть более подходящим в качестве комментариев, но у меня пока нет репутации комментировать.
Во-первых, независимо от того, как часто я смотрю на это - на доске или в отладчике - я не могу избежать окончания цикла в первом узле, то есть указывать на себя, если я не использую условное выражение для различать случаи, когда узлы являются смежными и нет, даже с помощью абстрактных шагов или конкретного кода из принятого в настоящее время ответа Smashery. Я немного осмотрелся в Интернете, чтобы найти код в стиле принятого в настоящее время ответа, чтобы избежать такого условия, но для меня это удивительно сложно избежать, и мои поиски не дали такого решения (если я не ошибаюсь о предложенном, возможно, неправильном). Если бы существовало какое-нибудь умное выражение, которое могло бы дать адрес первого узла, когда они смежные, и адрес преемника первого узла, когда их нет, то это условное условие не понадобилось бы, потому что выяснение нового преемника второго узла - это то, что (очевидно) требует это условно.
Во-вторых, у меня тот же вопрос о последовательных присвоениях тех же переменных в принятом ответе, что и у других комментаторов. Я надеюсь, что я здесь не слишком плотный, но присваиваю разные значения одной и той же переменной последовательно, если, возможно, не в случае побочных эффектов, мне просто никогда не кажется, что переменная имеет какое-либо другое значение, кроме последнего присваивания, независимо от того, какую конфигурацию узлов я рассматриваю, и, таким образом, делает предыдущие назначения явно избыточными. Если я ошибаюсь по этому поводу и этот подход фактически решит эту проблему, то я смог бы устранить последнее условие в приведенном ниже коде, от которого я пытался избавиться, когда впервые искал в Интернете решение для замена узлов без специального корпуса соседних узлов. Я не совсем уверен, но это звучало так, как будто Smashery преднамеренно оставил эти повторяющиеся задания из логической строгости и чтобы лучше проиллюстрировать процедуру - хотя, возможно, я неправильно понял.
В-третьих, на этом и других сайтах я часто видел повторение заявления из других ответов о том, что лучше менять содержимое узлов, чем указателей. Конечно, в случае простых целых чисел, как в приведенных выше примерах, это, по-видимому, приводит к более короткому и простому коду. Тем не менее, когда мы обсуждаем связанные списки с узлами, содержащими целые числа, обычно это заменяет вспомогательную структуру данных более сложного и универсального контейнера. Таким образом, я не думаю, что обмен содержимого узлов действительно так прост, по крайней мере, если реализация структуры данных не может делать предположения о семантике копирования элементов контейнера. Кроме того, возможность обмениваться содержимым узлов, подобным этому, подразумевает, что связанный список владеет содержимым этих узлов, поскольку в противном случае код вне метода связанного списка может содержать ссылки на объекты в тех узлах, значения которых внезапно измениться под ними.
Однако я признаю, что это может зависеть от семантики контейнера. Для массива можно ожидать, что метод подкачки изменит значение под ссылками на определенный индекс этого массива. Это будет означать, что ссылки предназначены не для ссылки на конкретный объект, а на позицию в контейнере, которую можно проиндексировать. Если мы рассмотрим связанный список как средство для упорядочивания только набора объектов, которые используются вне связанного списка, пользователь, вероятно, ожидает, что операция подкачки будет обмениваться только позицией, а не содержимым.
Представьте, например, что связанный список представляет объекты типа "автомобиль". У каждого автомобиля есть владелец, и этот владелец ссылается на свой автомобиль через указатель на него. Теперь предположим, что связанный список представляет собой порядок обслуживания автомобилей, которые планируется обслужить для проверки в автосалоне. Если бы мы поменялись местами содержимого двух узлов, чтобы обменять слоты расписания на две машины, и сделали это путем обмена их содержимым, то обслуживание фактически произошло бы в новом, правильном порядке - но люди также в конечном итоге внезапно приобрели бы разные машины! (Я бы не против поменяться с Tesla, потому что я только за рулем Corolla.)
Если связанный список был, как в примере с массивом, основан на семантике индексации, то позиция в узле могла бы просто представлять порядок, в котором автомобили загружаются на судно для транспортировки. На данный момент, у автомобилей нет владельцев, и нам действительно важно только, в каком слоте они находятся. Тогда, я полагаю, действительно не помешает поменяться автомобилями, то есть содержимым объектов, на которые ссылаются узлы.
Наконец, к коду. Как я уже говорил выше, мне не удалось избежать специального кожуха для соседних узлов.
Во-первых, определение вспомогательного метода:
int find_node(int v, node* root, node** n, node** pn) {
int i = 0;
for (*n = root; *n != NULL && (*n)->v != v; ++i) {
*pn = *n;
*n = (*n)->next;
}
return i;
}
Этот метод находит узел по его значению. Возвращаемое целое число - это позиция, начинающаяся с нуля (если хотите, назовите ее индексом) узла в связанном списке. Я обнаружил, что обнаружение смежности через положение, а не сравнение указателей, более читабельно Начало списка - root. Метод устанавливает n, чтобы указать на узел, содержащий переданное значение. В pn метод сохраняет предшественника n.
Ниже приведен фактический своп:
void swap_nodes(node **root, int v1, int v2) {
if (v1 == v2) return;
node *n1, *n2, *pn1, *pn2;
int p1 = find_node(v1, *root, &n1, &pn1);
int p2 = find_node(v2, *root, &n2, &pn2);
if (p1 > p2) {
std::swap(n1, n2);
std::swap(pn1, pn2);
std::swap(p1, p2);
}
if (p1 == 0) *root = n2;
else pn1->next = n2;
node* nn1 = n1->next;
n1->next = n2->next;
if (p2 - p1 > 1) {
n2->next = nn1;
pn2->next = n1;
} else {
n2->next = n1;
}
}
Извините, что немного изменил сигнатуру метода ОП. Я обнаружил, что удобнее передавать значения узлов для обмена, а не указатели узлов. Если вы передадите указатели на узлы только на соответствующие узлы, вам придется выполнить другой обход, чтобы найти предшественников с этим решением, что показалось мне немного неловким. Если мы не можем различить узлы по этим значениям, например, значения не уникальны, однако нам понадобятся указатели на узлы.
Как и в объяснении для find_node выше, мы сначала находим позиции, узлы и предшественники для значений узлов, передаваемых в swap_nodes через v1 и v2. Все значения для первого и второго узлов меняются местами, если второй узел, который нужно поменять, появляется перед первым. Для этого не так много кода, он сокращает специальный регистр и упрощает визуализацию.
Теперь у нас осталось только еще два условия, ни одно из которых не казалось тривиальным, чтобы их избежать. Если первый узел находится во главе связанного списка, то есть в нулевой позиции, корень должен указывать на второй узел. В противном случае предшественник первого узла будет указывать на второй узел.
Предыдущее значение преемника первого узла необходимо запомнить, если узлы не являются соседними. Затем преемник первого узла устанавливается текущим преемником второго узла. Это единственное изменение, которое применяется ко всем случаям: новый преемник первого узла, являющийся старым преемником второго узла, является единственной уверенностью и полезен для начала, чтобы запомнить операции с указателями и их последовательность при реализации этого обмена .
Наконец, если позиции узлов различаются более чем на один, они не являются смежными. Затем новый преемник второго узла становится старым преемником первого узла - сохраненным выше - и предшественник второго узла теперь указывает на первый узел. Если они являются смежными, между узлами, подлежащими замене, нет узлов, которые необходимо обновить, поэтому достаточно просто связать второй узел с первым.