Revisando el problema de Joseph de la estructura de datos y el algoritmo


josé pregunta

prefacio

El problema de Joseph es un problema de informática y matemáticas. En el algoritmo de programación informática, problemas similares también se denominan anillos de Joseph , también conocido como el "problema del pañuelo arrojadizo".

Se dice que el famoso historiador judío Josefo contó la siguiente historia:

Después de que los romanos ocuparon Chotapat, 39 judíos se escondieron en una cueva con Josefo y sus amigos. Los 39 judíos decidieron que preferirían morir antes que ser atrapados por el enemigo, por lo que decidieron suicidarse. 41 personas se alinearon en el círculo, contando comienza desde la primera persona, y cada vez que la tercera persona cuenta hasta la tercera persona, la persona debe suicidarse, y luego la siguiente persona cuenta nuevamente hasta que todos se suicidan.

Sin embargo, Josefo y sus amigos no querían seguir esta regla, por lo que le pidió a su amigo que fingiera obedecer primero y se colocó a sí mismo y a su amigo en las posiciones 16 y 31, escapando así del juego de la muerte.

1. La Ley de la Violencia

Todo el proceso se puede simular utilizando el método de fuerza bruta:

  • Primero personalice una lista enlazada circular, debe apuntar el siguiente puntero del nodo final al primer nodo
  • n(= 41) número personal de 0-40, añadido a la lista enlazada
  • Eliminar un nodo requiere que se elimine un pre nodo pre y un nodo actual cur, por lo que al eliminar, solo necesita apuntar el siguiente de pre al siguiente de cur para eliminar el nodo cur
  • Definir un recuento de variables de contador Al llegar a m (= 3), elimine el nodo y restablezca el contador a 0
  • Dado que al final sobreviven 2 personas, la condición del bucle puede ser cur.next != pre. Si 1 persona sobrevive, la condición puede ser cur.next != cur.
  • Entonces, los números finales de supervivencia son 15 y 30. Dado que este número comienza desde 0, Josefo colocó a sus amigos y a él mismo en las posiciones 16 y 31, y escapó de este juego de muerte.
class Node {
    
    
    int data;
    Node next;
}

class CycleLinkedList  {
    
    
    Node first;
    Node last;

    public void add(int o) {
    
    
        Node l = last;
        Node n = new Node();
        n.data = o;
        last = n;
        if (l == null) {
    
    
            first = n;
        } else {
    
    
            l.next = n;
        }
        last.next = first;
    }
}

public void josephus() {
    
    

    int n = 41, m = 3;
    CycleLinkedList list = new CycleLinkedList();
    for (int i = 0; i < n; i++) {
    
    
        list.add(i);
    }

    int count = 0;
    Node pre = list.first;
    Node cur = pre;
    while (cur.next != pre) {
    
    
        count++;

        if (count == m) {
    
    
            pre.next = cur.next;
            System.out.println(" killer 编号:" + cur.data);
            count = 0;
        } else {
    
    
            pre = cur;
        }
        cur = cur.next;
    }
    System.out.println("最终存活2人编号:" + cur.data + "," + pre.data);
    // 最终存活2人编号:15,30
}

2. Programación dinámica

El método de fuerza bruta anterior es fácil de entender y simple de implementar. Sin embargo, el recorrido repetido reduce la eficiencia. Cada vez que se elimina un elemento, debe moverse m pasos. Para n elementos, la complejidad del tiempo es O (mn) O(mn)O ( mn )

De hecho, se puede encontrar que los problemas anteriores en realidad cumplen con la categoría de solución de programación dinámica. El número obtenido en cada paso es una subpregunta, y el número obtenido en el paso anterior es útil para el paso siguiente. Entonces las tres partes importantes de dp son las siguientes:

  • Definición de estado: dp[i] representa la solución del problema de Joseph, es decir, se eliminan elementos i cada m, y finalmente se deja el número del elemento

  • Derivación de la ecuación de transferencia:

    • 0 , 1 , ⋯ , yo − 2 ⏟ i-1 piezas \underbrace{0,1,\cdots, i-2}_{\text{i-1 piezas}}yo- 1 0 ,1 ,,i2Obtenga el número de elemento final dp [ i − 1 ] dp[i-1]d p ​​[ yo1 ]

    • Entonces 0 , 1 , ⋯ , k − 1 , k , k + 1 , ⋯ , yo − 1 ⏟ i \underbrace{0,1,\cdots,k-1,k,k+1,\cdots, i- 1 }_{\text{i piezas}}yo _ 0 ,1 ,,k1 ,k ,k+1 ,,i1Retire el primero ( m − 1 ) % i = k (m - 1)\%i = k( metro1 ) % yo=Después de k (si m es mayor que i, se debe tomar el resto), hayk + 1 , k + 2 , ⋯ , i − 2 , i − 1 , 0 , 1 , ⋯ , k − 1 ⏟ i- 1 \ underbrace{k+1,k+2,\cdots,i-2,i-1,0,1,\cdots,k-1}_{\text{i-1}}yo- 1 k+1 ,k+2 ,,i2 ,i1 ,0 ,1 ,,k1, el número de elementos también se convierte en i − 1 i-1i1 , debido a que el orden de los elementos es creciente y circular, para llegar al máximo se partirá del mínimo, que puede ser equivalente al anterior desde0 00 comienzo ai − 2 i-2iSecuencia de 2 dígitos.

    • Obtener dp [ i − 1 ] dp[i-1]d p ​​[ yo1 ] La secuencia numérica comienza desde 0, luegodp [ i ] dp[i]d p ​​[ i ]了,为dp [ i ] = ( k + 1 + dp [ i − 1 ] ) % i dp[i] = (k + 1 + dp[i - 1] ) \% idp [ yo ] _=( k+1+d p ​​[ yo1 ]) % yo ,dp [ yo ] = ( ( metro − 1 ) % yo + 1 + dp [ yo − 1 ] ) % yo dp[i] = ((m - 1)\%i + 1 + dp[ yo - 1] ) \% yodp [ yo ] _=(( metro1 ) % yo+1+d p ​​[ yo1 ]) % i

    • Así que la ecuación de derivación final es: dp [ i ] = ( dp [ i − 1 ] + m ) % i dp[i] = (dp[i - 1] + m) \% idp [ yo ] _=( dp [ yo _1 ]+m ) % i

  • Estado inicial: dp[1]=0 significa que 1 elemento eventualmente dejará el número de elemento como 0

public void josephus1() {
    
    

    int n = 41, m = 3;
    int [] dp = new int[n + 1];
    dp[1] = 0;
    for (int i = 2; i < n + 1; i++) {
    
    
        dp[i] = (dp[i - 1] + m) % i;
    }
    System.out.println(dp[n]);
}

// 上述dp数组可用变量替代
public void josephus2() {
    
    

    int n = 41, m = 3;
    int start = 0;
    for (int i = 2; i < n + 1; i++) {
    
    
        start = (start + m) % i;
    }
    System.out.println(start);
}

La programación dinámica anterior se puede usar para encontrar el número del último superviviente, y la complejidad del tiempo es O ( n ) O(n)O ( n ) , el problema con el uso del código anterior es que no puede obtener el número de la penúltima persona, y dp[n-1] es el número de la última persona sobreviviente de 40 personas. De hecho, también puedes usar la idea de dp para encontrar el número de la penúltima persona.

  • Definición de estado: dp[i] significa que los elementos i se eliminan cada m, dejando el número del penúltimo elemento
  • Ecuación de transición de estado: igual que arriba, dp [ i ] = ( dp [ i − 1 ] + m ) % i dp[i] = (dp[i - 1] + m) \% idp [ yo ] _=( dp [ yo _1 ]+m ) % i
  • Estado inicial: dp[2] = (m + 1) % 2
int n = 41, m = 3;
int start = (m + 1) % 2;
for (int i = 3; i < n + 1; i++) {
    
    
    start = (start + m) % i;
}
System.out.println(start);

Tres, combate real

3.1 Leek1823 Encuentra el ganador del juego

https://leetcode.cn/problems/find-the-winner-of-the-circular-game/

Un total de n amigos juegan juntos. Los amigos forman un círculo y están numerados en el sentido de las agujas del reloj del 1 al n. Para ser precisos, moverse un bit en el sentido de las agujas del reloj desde el i-ésimo socio alcanzará la posición del (i+1)-ésimo socio, donde 1 <= i < n, y moverse un bit en el sentido de las agujas del reloj desde el n-ésimo socio devolverá Ir a la posición del primer pequeño compañero.

El juego sigue las siguientes reglas:

从第 1 名小伙伴所在位置 开始 。
沿着顺时针方向数 k 名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。
你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。
如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。
否则,圈子中最后一名小伙伴赢得游戏。

Darle el número total n de amigos que participan en el juego, y un número entero k, y devolver el ganador del juego.

El clásico problema del anillo de Joseph, pero reformulado

public int findTheWinner(int n, int k) {
    
    
	int start = 0;
    for (int i = 2; i < n + 1; i++) {
    
    
        start = (start + k) % i;
    }
    return start + 1;
}

3.2 Luogu P1996 Problema de José

https://www.luogu.com.cn/problema/P1996

N personas forman un círculo, comienzan a contar desde la primera persona, la persona que cuenta hasta m sale, y luego la siguiente persona comienza a contar desde 1 nuevamente, y la persona que cuenta hasta m sale del círculo nuevamente, y así sucesivamente , hasta que todas las personas salgan del círculo, envíen los números de las personas fuera del círculo a su vez.

Aquí necesitamos averiguar el número de personas en el círculo una por una, y se puede usar el método de la violencia.

import java.io.*;
import java.util.*;
public class Main {
    
    
    static class Node {
    
    
        int data;
        Node next;
    }

    static class CycleLinkedList  {
    
    
        Node first;
        Node last;

        public void add(int o) {
    
    
            Node l = last;
            Node n = new Node();
            n.data = o;
            last = n;
            if (l == null) {
    
    
                first = n;
            } else {
    
    
                l.next = n;
            }
            last.next = first;
        }
    }
    public static void main(String args[]) throws Exception {
    
    
        Scanner cin=new Scanner(System.in);
        int n = cin.nextInt(), m = cin.nextInt();
        int [] ans = new int[n];
        int c = 0;

        CycleLinkedList list = new CycleLinkedList();
        for (int i = 0; i < n; i++) {
    
    
            list.add(i + 1);
        }

        int count = 0;
        Node pre = list.first;
        Node cur = pre;
        while (cur.next != cur) {
    
    
            count++;

            if (count == m) {
    
    
                pre.next = cur.next;
                ans[c++] = cur.data;
                count = 0;
            } else {
    
    
                pre = cur;
            }
            cur = cur.next;
        }
        ans[c] = cur.data;
        for (int an : ans) {
    
    
            System.out.print(an + " ");
        }
    }
}

referencia

  1. Tres soluciones de Joseph Ring
  2. ¡Esta es probablemente la derivación matemática más detallada del anillo de Joseph que encontrarás!
  3. Joseph solucionador de problemas

Guess you like

Origin blog.csdn.net/qq_23091073/article/details/128795594