Gracias a todos por sus respuestas! Me doy cuenta de que esta pregunta se ha formulado hace casi dos años y que se ha aceptado una respuesta hace tiempo, pero las respuestas me han confundido un poco. Por lo tanto, a pesar de que el encuestador probablemente no se preocupe por las nuevas respuestas, me gustaría agregar mi versión, en caso de que otros lectores también estén confundidos y documenten mi propio enfoque. Parte de esto puede haber sido más apropiado como comentarios, pero todavía no tengo la reputación para comentar.
En primer lugar, no importa la frecuencia con la que lo miro, en la pizarra o en el depurador, no puedo evitar terminar con un ciclo en el primer nodo, es decirseñalándose a sí mismo, si no uso un condicional para distinguir entre casos de nodos adyacentes y no, incluso con los pasos abstractos o el código concreto de la respuesta actualmente aceptada por Smashery. He buscado un poco en Internet para encontrar el código en el estilo de la respuesta actualmente aceptada para evitar dicho condicional, pero para mí es sorprendentemente difícil de evitar y mis búsquedas no han encontrado una solución así (a menos que esté equivocado sobre el propuesto posiblemente sea incorrecto). Si hubiera alguna expresión inteligente que arrojara la dirección del primer nodo cuando son adyacentes y la dirección del sucesor del primer nodo cuando no lo están, entonces ese condicional no sería necesario, porque descubrir el nuevo sucesor del segundo nodo es lo que (aparentemente) necesita. ese condicional.
En segundo lugar, tengo la misma pregunta acerca de las asignaciones consecutivas a las mismas variables en la respuesta aceptada que otros comentaristas. Espero no estar siendo extremadamente denso aquí, pero asignar valores diferentes a la misma variable en secuencia, a menos que tal vez en el caso de los efectos secundarios, para mí nunca parezca dejar la variable con otro valor que la última asignación, sin importar qué configuración de nodos considero, y por lo tanto hace que las asignaciones anteriores aparentemente sean redundantes. Si me equivoco al respecto y ese enfoque de hecho resolvería este problema, habría podido eliminar el último condicional en el siguiente código, que estaba tratando de deshacer cuando miré por primera vez en Internet una solución para intercambiando nodos sin una envoltura especial de nodos adyacentes. No estoy muy seguro, pero sonaba como si Smashery dejara intencionalmente esas tareas repetidas fuera del rigor lógico y para ilustrar mejor el procedimiento; sin embargo, puedo haber malentendido.
En tercer lugar, en este y en otros sitios a menudo he visto la afirmación de otras respuestas repetidas de que es mejor intercambiar el contenido de los nodos, en lugar de los punteros. Por supuesto, en el caso de los enteros simples, como en los ejemplos hasta ahora, aparentemente produce un código más corto y más simple. Sin embargo, cuando discutimos listas enlazadas con nodos que contienen números enteros, generalmente es un sustituto de la estructura de datos de respaldo de un contenedor más complejo y genérico. Como tal, no creo que el intercambio de los contenidos de los nodos sea tan fácil, al menos si la implementación de la estructura de datos no puede hacer suposiciones sobre la semántica de copia de los artículos del contenedor. Además, el hecho de poder intercambiar los contenidos de nodos así me implica que la lista vinculada posee los contenidos de esos nodos, ya que de lo contrario el código fuera del método de la lista enlazada podría contener referencias a los objetos en esos nodos, cuyos valores de repente cambia debajo de ellos.
Admito, sin embargo, que esto podría depender de la semántica del contenedor. Para una matriz, se puede esperar que un método de intercambio cambie el valor debajo de las referencias a un cierto índice de esa matriz. Eso significaría que las referencias no están destinadas a referirse a un objeto específico, sino a una posición en un contenedor que se puede indexar. Si consideramos una lista vinculada como un medio para ordenar solo un conjunto de objetos, que tienen su uso fuera de la lista vinculada, un usuario probablemente esperaría que una operación de intercambio intercambiara solamente la posición, no los contenidos.
Imagine, por ejemplo, que la lista vinculada representa objetos de tipo "automóvil". Cada automóvil tiene un propietario y ese propietario hace referencia a su automóvil mediante un puntero. Ahora suponga que la lista vinculada representa el orden en que se programará el servicio de un conjunto de automóviles para su inspección en un concesionario de automóviles. Si intercambiamos el contenido de dos nodos, para intercambiar los espacios de programación para dos automóviles, y lo hicimos intercambiando sus contenidos, entonces el servicio de mantenimiento ocurriría en el nuevo orden correcto, pero la gente también terminaría siendo propietaria de repente. ¡diferentes autos! (No me importaría intercambiar con un Tesla, porque solo conduzco un Corolla.)
Si la lista vinculada era, como en el ejemplo de la matriz, basada en la semántica de indexación, entonces la posición en el nodo podría simplemente representar el orden en que se cargan los automóviles en un barco para su transporte. En este punto, los autos no tienen ningún dueño y realmente solo nos importa en qué ranura están. Entonces, supongo que realmente no hace daño intercambiar autos, es decir, el contenido de los objetos a los que hacen referencia los nodos.
Finalmente al código. Como dije antes, no he podido evitar la envoltura especial para los nodos adyacentes.
En primer lugar, la definición de un método auxiliar:
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;
}
Este método encuentra un nodo por su valor. El número entero devuelto es la posición basada en cero (llámalo su índice si quieres) del nodo en la lista vinculada. Encontré la detección de adyacencia a través de la posición, en lugar de las comparaciones con el puntero, más legible. El comienzo de la lista es root. El método establece n para apuntar al nodo que contiene el valor pasado. En pn, el método almacena el predecesor de n.
El siguiente es el intercambio real:
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;
}
}
Lo siento que he cambiado la firma del método de la OP un poco. Me pareció más conveniente pasar los valores de los nodos para intercambiar, a diferencia de los punteros de nodo. Si pasa solo punteros de nodo a los nodos respectivos, tendría que hacer otro recorrido para encontrar los predecesores con esta solución, lo que me resultó un poco incómodo. Si no podemos distinguir nodos por estos valores, p. los valores no son únicos, sin embargo, necesitaremos punteros a los nodos.
Al igual que con la explicación de find_node anterior, primero encontramos las posiciones, los nodos y los predecesores para los valores de nodo pasados a swap_nodes a través de v1 y v2. Los valores para el primer y segundo nodo se intercambian todos si el segundo nodo a intercambiar aparece antes que el primero. No es mucho código para hacerlo, reduce la carcasa especial y hace que sea un poco más fácil de visualizar.
Ahora, nos quedan solo dos condicionales más, ninguno de los cuales parecía trivial para evitar. Si el primer nodo está a la cabeza de la lista enlazada, es decir, en la posición cero, la raíz necesita apuntar al segundo nodo. De lo contrario, el predecesor del primer nodo apuntará al segundo nodo.
Se debe recordar el valor anterior del sucesor del primer nodo, en caso de que los nodos no sean adyacentes. Entonces, el sucesor del primer nodo se establece en el sucesor actual del segundo nodo. Este es el único cambio que se aplica a todos los casos: el nuevo sucesor del primer nodo siendo el antiguo sucesor del segundo nodo es la única certeza y útil para comenzar con el fin de recordar las operaciones del puntero y su secuencia al implementar este intercambio. .
Por último, si las posiciones de los nodos difieren en más de uno, no son adyacentes. Luego, el nuevo sucesor del segundo nodo se convierte en el antiguo sucesor del primer nodo, que se guardará arriba, y el predecesor del segundo nodo ahora apunta al primer nodo. Si son adyacentes, no hay nodos entre los nodos para intercambiar que necesiten actualización, por lo que simplemente vincular el segundo nodo con el primero es todo lo que queda por hacer.
Hola, Reti, he respondido a edit1 en mi respuesta. Si obtiene una segfault, verifique que esté asignando y eliminando correctamente la variable tmp. – Smashery