Как решить эти минимальные шаги, необходимые для достижения конца матричной проблемы?

Учитывая матрицу, содержащую начальное состояние каждого элемента, найдите минимальное количество шагов, чтобы добраться от верхнего левого угла до нижнего правого?

Условия:

Начальное состояние любого элемента будет случайно выбрано из севера, востока, юга или запада. На каждом шаге мы можем либо никуда не двигаться, либо двигаться в направлении текущего состояния этого элемента (конечно, мы никогда не выходим за пределы матрицы). Любой шаг будет одновременно изменять состояние всех элементов матрицы. Состояния меняются циклически по часовой стрелке, т. е. от N -> E -> S -> W. Даже если мы не движемся на шаг, состояния меняются.


person Shisui    schedule 20.07.2019    source источник
comment
Итак, в чем проблема, с которой вы столкнулись?   -  person trincot    schedule 20.07.2019


Ответы (1)


Одно важное наблюдение состоит в том, что матрица имеет 4 разных «версии»: каждые кратные 4 шагам содержимое матрицы будет точно таким же.

Вы можете представить эти 4 матрицы как третье измерение (направление Z), где Z может быть равно 0, 1, 2 или 3. Представьте это как трехмерный массив с 4 x двумерными массивами, расположенными слоями друг за другом.

С этой трехмерной матрицей магия «поворотных» значений исчезает: каждая из этих четырех матриц теперь статична.

Теперь шаг:

  • движение в направлении, указанном содержимым в текущей ячейке, так что X или Y меняются, или
  • ход, при котором X и Y остаются неизменными

...но в обоих случаях Z становится (Z+1)%4

Целевая ячейка теперь представляет собой набор из 4 ячеек, так как не имеет значения, какой Z в тот момент, когда вы попадаете в нижний правый угол.

Если вы построите этот (невзвешенный, ориентированный) граф, вы сможете реализовать простой поиск BFS. Задача решена.

Реализация

Я подумал, что сделаю небольшую анимацию для следующего образца входной матрицы:

[
    [2, 0, 0, 2, 1, 0, 3],
    [0, 0, 0, 2, 0, 0, 1],
    [3, 2, 0, 3, 3, 3, 0],
]

Цифры обозначают текущее возможное направление: 0 = север, 1 = восток, 2 = юг, 3 = запад.

Алгоритм состоит из двух функций. Одним из них является метод класса Node, shortestPathTo, который реализует общий поиск BFS от узла к набору целевых узлов. Вторая функция, createGraph, преобразует входную матрицу в график, как описано выше. После создания этого графа можно вызвать метод shortestPathTo для верхнего левого узла. Он возвращает path, массив узлов для посещения.

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

class Node { // Generic Node class; not dependent on specific problem
    constructor(value, label) {
        this.value = value;
        this.label = label;
        this.neighbors = [];
    }
    addEdgeTo(neighbor) {
        this.neighbors.push(neighbor);
    }
    shortestPathTo(targets) {
        targets = new Set(targets); // convert Array to Set
        // Standard BFS
        let queue = [this]; // Start at current node
        let comingFrom = new Map;
        comingFrom.set(this, null);
        while (queue.length) {
            let node = queue.shift();
            if (targets.has(node)) { // Found!
                let path = []; // Build path from back-linked list
                while (node) {
                    path.push(node);
                    node = comingFrom.get(node);
                }
                return path.reverse();
            }
            for (let nextNode of node.neighbors) {
                if (!comingFrom.has(nextNode)) {
                    comingFrom.set(nextNode, node);
                    queue.push(nextNode);
                }
            }
        }
        return []; // Could not reach target node
    }
}

function createGraph(matrix) {
    // Convert the matrix and its move-rules into a directed graph
    const numCols = matrix[0].length;
    const numRows = matrix.length;
    const numNodes = numRows * numCols * 4; // |Y| * |X| * |Z|
    // Create the nodes
    const nodes = [];
    for (let y = 0; y < numRows; y++) 
        for (let x = 0; x < numCols; x++) 
            for (let z = 0; z < 4; z++) {
                let dir = (matrix[y][x] + z) % 4;
                nodes.push(new Node({x, y, z, dir}, "<" + x + "," + y + ":" + "NESW"[dir] + ">"));
            }
    // Create the edges
    for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i];
        let {x, y, z, dir} = node.value;
        // The "stand-still" neighbor:
        let j = i-z + (z+1)%4;
        node.addEdgeTo(nodes[j]);
        // The neighbor as determined by the node's "direction"
        let dj = 0;
        if      (dir == 0 && y   > 0      ) dj = -numCols*4;
        else if (dir == 1 && x+1 < numCols) dj = 4;
        else if (dir == 2 && y+1 < numRows) dj = numCols*4;
        else if (dir == 3 && x   > 0      ) dj = -4;
        if (dj) node.addEdgeTo(nodes[j+dj]);
    }
    // return the nodes of the graph
    return nodes;
}

// Sample matrix
let matrix = [
    [2, 0, 0, 2, 1, 0, 3],
    [0, 0, 0, 2, 0, 0, 1],
    [3, 2, 0, 3, 3, 3, 0],
];

// Calculate solution:
const nodes = createGraph(matrix);
const path = nodes[0].shortestPathTo(nodes.slice(-4));
// path now has the sequence of nodes to visit.

// I/O handling for this snippet
const size = 26;
const paint = () => new Promise(resolve => requestAnimationFrame(resolve));

function drawSquare(ctx, x, y, angle) {
    ctx.rect(x*size+0.5, y*size+0.5, size, size);
    ctx.stroke();
    ctx.beginPath();
    angle = (270 + angle) * Math.PI / 180;
    x = (x+0.5)*size;
    y = (y+0.5)*size;
    ctx.moveTo(x + 0.5, y + 0.5);
    ctx.lineTo(x + Math.cos(angle) * size * 0.4 + 0.5, y + Math.sin(angle) * size * 0.4 + 0.5);
    ctx.stroke();
}

function drawBall(ctx, x, y) {
    x = (x+0.5)*size;
    y = (y+0.5)*size;
    ctx.beginPath();
    ctx.arc(x, y, size * 0.2, 0, 2 * Math.PI);
    ctx.fillStyle = "red";
    ctx.fill();
}

async function draw(ctx, matrix, time=0, angle=0, curX=0, curY=0) {
    await paint();
    time = time % 4;
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    for (let y = 0; y < matrix.length; y++) {
        for (let x = 0; x < matrix[0].length; x++) {
            drawSquare(ctx, x, y, (matrix[y][x] + time) * 360 / 4 - angle);
        }
    }
    drawBall(ctx, curX, curY);
}

async function step(ctx, matrix, time, curX, curY, toX, toY) {
    for (let move = 100; move >= 0; move-=5) {
        await draw(ctx, matrix, time, 90, toX + (curX-toX)*move/100, toY + (curY-toY)*move/100);
    }
    for (let angle = 90; angle >= 0; angle-=5) {
        await draw(ctx, matrix, time, angle, toX, toY);
    }
}

async function animatePath(ctx, path) {
    for (let time = 1; time < path.length; time++) {    
        await step(ctx, matrix, time, path[time-1].value.x, path[time-1].value.y, path[time].value.x, path[time].value.y);
    }
}

const ctx = document.querySelector("canvas").getContext("2d");
draw(ctx, matrix);
document.querySelector("button").onclick = () => animatePath(ctx, path);
<button>Start</button><br>
<canvas width="400" height="160"></canvas>

Вы можете изменить определение matrix в коде, чтобы попробовать другие входные данные.

person trincot    schedule 20.07.2019