web-dev-qa-db-fra.com

Somme de tranche verticale minimale

Je souhaite entrer une taille de matrice N x N et couper une tranche de sorte que chaque élément se trouve directement en dessous, à gauche en dessous ou à droite en dessous de celui au-dessus. Et le coût est la somme de tous les éléments de la tranche. Comment puis-je écrire un programme pour faire cela?

Par exemple. la matrice est donnée comme liste de liste 

[[1,2,3],
 [4,5,6],
 [7,8,9]]

qui a les tranches suivantes:

(1,4,7), (1,4,8), (1,5,7), (1,5,8), (1,5,9), 
(2,4,7), (2,4,8), (2,5,7), (2,5,8), (2,5,9), (2,6,8), (2,6,9), 
(3,5,7), (3,5,8), (3,5,9), (3,6,8), (3,6,9)

Alors la tranche de poids le plus bas est (1,4,7) qui a la somme 12.

3
mathnoob

Nous pouvons traiter les éléments de la matrice comme des sommets dans un graph et considérer les connexions possibles (définies par vos "tranches") comme des arêtes. On peut alors exprimer le problème en trouvant le chemin le plus court entre l’un des sommets de la rangée supérieure et ceux de la rangée inférieure, où chaque arête a un poids égal à la valeur de l’élément connecté poids de l'élément de la première rangée en plus).

Ensuite, nous pouvons utiliser par exemple l’algorithme Bellman-Ford pour trouver le chemin le plus court dans ces conditions. Voici un exemple d'implémentation:

import numpy as np


m, n = 10, 10
M = np.arange(m*n).reshape(m, n) + 1
for i in range(1, m):
    M[i:] = np.roll(M[i:], 1 if i <= m // 2 else -1, axis=1)
print('Matrix:')
print(M, end='\n\n')


def edges():
    for i in range(m - 1):
        yield [(i, 0), (i + 1, 0)]
        yield [(i, 0), (i + 1, 1)]
        for j in range(1, n - 1):
            yield [(i, j), (i + 1, j - 1)]
            yield [(i, j), (i + 1, j)]
            yield [(i, j), (i + 1, j + 1)]
        yield [(i, n - 1), (i + 1, n - 1)]
        yield [(i, n - 1), (i + 1, n - 2)]


def compute_path(start):
    distance = {index: np.inf for index in np.ndindex(m, n)}
    predecessor = {index: None for index in np.ndindex(m, n)}

    distance[start] = M[start]
    for __ in range(M.size - 1):
        for u, v in edges():
            weight = M[v]
            if distance[u] + weight < distance[v]:
                distance[v] = distance[u] + weight
                predecessor[v] = u
    stop = min(filter(lambda x: x[0] == n - 1, distance), key=lambda y: distance[y])
    path = [stop]
    while predecessor[path[-1]] is not None:
        path.append(predecessor[path[-1]])
    return path[::-1], distance[stop]


paths = [compute_path((0, c)) for c in range(n)]
opt = min(paths, key=lambda x: x[1])
print('Optimal path: {}, with weight: {}'.format(*opt))
print('Vertices: ', M[list(Zip(*opt[0]))])

Ce qui donne comme sortie:

Matrix:
[[  1   2   3   4   5   6   7   8   9  10]
 [ 20  11  12  13  14  15  16  17  18  19]
 [ 29  30  21  22  23  24  25  26  27  28]
 [ 38  39  40  31  32  33  34  35  36  37]
 [ 47  48  49  50  41  42  43  44  45  46]
 [ 56  57  58  59  60  51  52  53  54  55]
 [ 67  68  69  70  61  62  63  64  65  66]
 [ 78  79  80  71  72  73  74  75  76  77]
 [ 89  90  81  82  83  84  85  86  87  88]
 [100  91  92  93  94  95  96  97  98  99]]

Optimal path: [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 4), (7, 3), (8, 2), (9, 1)], with weight: 460
Vertices:  [ 1 11 21 31 41 51 61 71 81 91]
2
a_guest

Comme mentionné par vivek, vous pouvez résoudre ce problème avec un programme dynamique:

Créez une table de coûts ayant la même taille que votre matrice d’entrée. Chaque élément de la matrice de coûts stocke le coût de la tranche minimale qui se termine à cet élément. Si vous stockez également l'élément de tranche précédent dans cette table de coûts, vous pouvez également extraire la tranche réelle à la fin (au lieu de simplement son coût).

Vous pouvez facilement initialiser le tableau des coûts. Copiez simplement la première ligne de votre matrice d’entrée dans la table. Ensuite, nous allons remplir le reste du tableau, ligne par ligne. Soit C la matrice de coûts et M la matrice de saisie. Ensuite:

//Initialize cost table
for col = 0 to N - 1
    C(0, col) = M(0, col)
//Run dynamic program
for row = 1 to N - 1
    for col = 0 to N - 1
        //take the minimum of the three possible predecessors:
        //make sure that the entries exist (i.e., take care of the edges, not shown here)
        C(row, col) = M(row, col) 
                       + min(C(row - 1, col - 1)), C(row - 1, col), C(row - 1, col + 1))

Après cela, il vous suffit de trouver le minimum dans la dernière ligne de C, ce qui vous donnera le coût de la tranche minimale. Pour obtenir la tranche réelle, parcourez les pointeurs prédécesseurs que vous avez configurés pendant la boucle (non représentés dans le pseudo-code).

2
Nico Schertler

C'est une question de récursivité + programmation dynamique (DP). Vous pouvez ou non utiliser DP en fonction de la taille des cas de test. De telles questions sont généralement posées dans les concours de programmation compétitifs et si vous trouvez que vos cas de test arrivent à expiration, je vous recommande d’augmenter mon code avec DP. Je parlerai de la façon de le faire une fois que j'aurai expliqué l'algorithme et que je vous ai donné le code.

De chaque élément de la rangée supérieure de votre matrice, vous devrez vous déplacer vers le bas. En descendant, vous aurez trois options:

  1. Descendez jusqu'à l'élément directement sous l'élément auquel vous vous trouvez.
  2. Descendez jusqu’à l’élément situé à gauche de l’élément situé directement sous l’élément sur lequel vous vous trouvez.
  3. Descendez vers l'élément situé à droite de l'élément qui se trouve juste en dessous de l'élément où vous vous trouvez.

Continuez à ajouter les éléments tout en continuant à utiliser la récursivité. Ainsi, pour chaque élément de la matrice, trois types de sommation sont possibles. Je les appelle la somme gauche, la somme moyenne et la bonne somme. Les noms eux-mêmes sont intuitifs, mais n'hésitez pas à demander des commentaires s'ils ne le sont pas.

Je maintiens une liste globale pour garder la somme de chaque tranche. Enfin, je renvoie l'élément de taille minimale de cette liste globale. Sans compter que le plus petit élément de cette liste sera la tranche verticale minimale de votre matrice.

Veuillez trouver le code ci-dessous (en Python 2.7):

#!/bin/python

# Global list L to store sum of all the vertical slices.

L = []
def fun(M, i, j):
    """
    M: The matrix
    i: Row number
    j: Column number
    Return: Add M[i][j] to the left, middle and right sum and return the three values as a list
    """
    # Reutrn the element if you are at the last row
    if i==len(M)-1:
        return [M[i][j]]
    # Calculate the left sum only if you are not in the first column 
    if j>0:
        l_sum = [M[i][j] + Elm for Elm in fun(M, i+1, j-1)]
    m_sum = [M[i][j] + Elm for Elm in fun(M, i+1, j)]
    # Calculate the right sum only if you are not in the last column
    if j<len(M[0])-1:
        r_sum = [M[i][j] + Elm for Elm in fun(M, i+1, j+1)]
    # Return the sum of columns as a list
    if j>0 and j<len(M[0])-1:
        return l_sum+m_sum+r_sum
    if j==0:
        return m_sum+r_sum
    if j==len(M[0])-1:
        return l_sum+m_sum

def MinSliceWeight(Matrix):
    """
    Matrix: The matrix whose vertical slice sum is to be calculated
    Return: The minimum sum of the slices
    """
    global L
    # Iterate over all elements in the topmost row and find the sum of all slices for an element
    for k in range(len(Matrix[0])):
        slices = fun(Matrix, 0, k)
        for Elm in slices:
            L.append(Elm)
    return min(L)

Matrix_rows = int(raw_input().strip())
Matrix_columns = int(raw_input().strip())

Matrix = []

for _ in xrange(Matrix_rows):
    Matrix.append(map(int, raw_input().rstrip().split()))

res = MinSliceWeight(Matrix)
print res

Ajout de DP au code: .__ Comme vous l’auriez peut-être remarqué, ce code garde la trace de la somme gauche, moyenne et droite de chaque élément. Vous pouvez facilement trouver en exécutant à sec ce code sur une matrice de petite taille (de préférence 2x3) que les sommes des éléments sont calculées à nouveau. Pour éviter cela, vous pouvez créer une matrice de la même taille que la matrice d'origine et stocker les trois sommes de chaque élément qu'elle contient sous la forme d'un tuple. Si le tuple existe pour un élément particulier, extrayez-le de votre matrice. Cela empêchera des appels de fonction supplémentaires et économisera de la mémoire.

0
Aamir Khan

Ce problème peut être représenté à l’aide de théorie des graphes puis résolu en utilisant programmation linéaire techniques.

En traitant tous les éléments de la matrice comme des sommets, l'objectif est de trouver un ensemble d'arêtes qui minimise le chemin pondéré à travers la matrice sous certaines contraintes (par exemple, comment les tranches peuvent-elles être construites).

Nous pouvons utiliser scipy.optimize.linprog (qui utilise l'algorithme Simplex ) pour résoudre le problème de programmation linéaire c.T @ x. Chaque élément de la solution représente une connexion possible entre l’un quelconque des N nœuds (c’est-à-dire que le vecteur de la solution a une taille N ** 2). Les coefficients c déterminent le poids d’une connexion: c’est le poids du nœud connecté (la valeur de l’élément de la matrice), à ​​l’exception de la première couche où nous devons également ajouter les poids de départ.

Nous devons également appliquer plusieurs contraintes pour obtenir une solution valable:

  1. Le résultat doit avoir exactement N - 1 bords.
  2. Chaque nœud ne doit pas être connecté à lui-même.
  3. Chaque ligne doit se connecter à un seul noeud, à l'exception de la dernière ligne.
  4. Chaque nœud peut se connecter à au plus un autre nœud (à l'exception de ceux de la dernière couche qui doivent avoir zéro connexion).
  5. Chaque nœud doit respecter ses successeurs éventuels (qui sont donnés par la "forme" des tranches).
  6. Pour chacune des couches 1 à (N-1), tout nœud connecté à doit se connecter à la couche suivante.

Ce sont beaucoup de morceaux que nous devons assembler et le code qui en résulte est un peu long et peut sembler accablant au début. Cependant, en regardant de plus près, il devrait être possible d'identifier les pièces isolées et leur structure (j'ai essayé de commenter autant que possible + j'ai ajouté plusieurs tirages). Alors voici l'exemple de code:

import numpy as np
from scipy.optimize import linprog


M = np.arange(9).reshape(3, 3) + 1
print('Matrix:')
print(M)
print('\n\n')

N = len(M)

# Compute all possible connections between nodes (1: possible, 0: forbidden).
pc = np.zeros(shape=(N**2, N**2), dtype=int)

# Connect to nodes below (except the last layer).
i = np.arange(N**2 - N)
pc[i, i + N] = 1

# Connect to left nodes (except the last layer and leftmost column).
pc[i, i + N - 1] = 1
pc[i[::N], i[::N] + N - 1] = 0

# Connect to left nodes (except the last layer and rightmost column).
r = i + N + 1
mask = r < N**2
pc[i[mask], r[mask]] = 1
r = r[N-1::N]
mask = mask[N-1::N]
pc[i[N-1::N][mask], r[mask]] = 0

print('Possible connections:')
print(pc)
print('\n\n')

# Coefficients for linear programming problem represent the weight of connections.
c = np.zeros(shape=(N**2, N**2), dtype=int)
# Add weights for connections.
c = np.tile(M.ravel(), (N**2, 1))
# Add additional weights for first layer.
c[:N] += M[0, :][:, None]
print('Coefficient matrix:')
print(c)
print('\n\n')

# === Add constraints ===
A_eq_1 = np.concatenate((
    # Exactly N-1 connections.
    np.ones(N ** 4, dtype=int)[None, :],
    # No node can connect to itself.
    np.diag([1] * N**2).flatten()[None, :]
), axis=0)
b_eq_1 = np.asarray([N - 1, 0], dtype=int)
print('Exactly N-1 connections and no self-connecting nodes:')
print(A_eq_1)
print(b_eq_1)
print('\n\n')

# Each layer connects to exactly one other node (except the last layer).
A_eq_2 = np.zeros((N, N ** 4), dtype=int)
for j in range(N):
    A_eq_2[j, j * N**3:(j + 1)*N**3] = 1
b_eq_2 = np.ones(N, dtype=int)
b_eq_2[-1] = 0
print('Each layer connects to exactly one other node (except the last layer):')
print(A_eq_2)
print(b_eq_2)
print('\n\n')

# Each node connects to at most one other node (except the ones in the last layer).
N = N ** 2
A_ub_1 = np.zeros((N, N ** 2), dtype=int)
for j in range(N):
    A_ub_1[j, j * N:j * N + N] = 1
b_ub_1 = np.ones(N, dtype=int)
b_ub_1[-1] = 0
print('Each node connects to at most one other node (except the ones in the last layer):')
print(A_ub_1)
print(b_ub_1)
print('\n\n')

# Each node respects its possible succesors (i.e. forbid all other connections).
A_eq_3 = np.zeros((N, N ** 2), dtype=int)
for j in range(N):
    A_eq_3[j, j * N:j * N + N] = 1 - pc[j, :]
b_eq_3 = np.zeros(N, dtype=int)
print('Each node respects its possible succesors (i.e. forbid all other connections):')
print(A_eq_3)
print(b_eq_3)
print('\n\n')

# For the layers 1 through (N-1) each node connected to must connect to the next layer.
A_eq_4 = np.zeros((N, N ** 2), dtype=int)
for j in range(len(M), N-len(M)):
    A_eq_4[j, j::N] = 1
    A_eq_4[j, j*N:(j+1)*N] = -1
b_eq_4 = np.zeros(N, dtype=int)
print('For the layers 1 through (N-1) each node connected to must connect to the next layer:')
print(A_eq_4)
print(b_eq_4)
print('\n\n')

# Concatenate all constraints.
A_eq = np.concatenate([A_eq_1, A_eq_2, A_eq_3, A_eq_4])
b_eq = np.concatenate([b_eq_1, b_eq_2, b_eq_3, b_eq_4])

A_ub = np.concatenate([A_ub_1])
b_ub = np.concatenate([b_ub_1])

res = linprog(c.ravel(), A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=(0, 1))
print(res.success)
print(res.x.reshape(N, N))  # Edges.

La toute dernière sortie est le résultat et il est de la forme:

[[0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0.]]

Ce qui nous indique que pour obtenir le chemin minimal, nous devons connecter le nœud 0 (index de ligne) au nœud 3 (index de colonne) ainsi que le nœud 3 (index de ligne) au nœud 6 (index de colonne). Cela représente le chemin (ou "tranche") (1, 4, 7). Nous pouvons en déduire le chemin en commençant par la première ligne, puis en parcourant le graphique en tant que point des arêtes:

edges = res.x.reshape(N, N)
for i, r in enumerate(edges):
    # Numerical instabilities can cause some elements to have very small values > 0.
    if r.sum() > 0.5:
        print('Connect {} -> {}'.format(i, r.argmax()))

path = [edges[:len(M)].ravel().argmax() // N]
while edges[path[-1]].max() > 0.5:
    path.append(edges[path[-1]].argmax())
print('Path: ', path)
print('Elements: ', M.ravel()[path])
print('Path weight: ', M.ravel()[path].sum())

Note finale

L'exemple de code ci-dessus laisse beaucoup de place à l'amélioration des performances. Par exemple, toutes les connexions possibles entre les nœuds sont considérées comme des solutions dont l'échelle correspond à M.size**2. Bien que nous contraignions les connexions possibles, le nombre de calculs est toujours beaucoup plus grand que si nous le contraignions dès le début, en l'incluant dans l'architecture du problème. Cela signifie qu'au lieu d'avoir M.size**2 coefficients, nous pourrions utiliser uniquement 2*(M.shape[0] - 1) + 3*(M.shape[1] - 2)*(M.shape[0] - 1), qui n'est redéfini que par M.size. De plus, nous pouvons utiliser une matrice de contraintes beaucoup plus petite puisque nous avons déjà construit ces contraintes dans l'architecture du problème. En prenant l'exemple de code ci-dessus comme base, il devrait être possible de l'adapter en conséquence. Je conclurai donc à ce stade et laisserai au lecteur intéressé la mise en œuvre des améliorations de performances possibles.

(L'implémentation ci-dessus ne fonctionne également que sur des matrices carrées, la généralisation à des matrices non carrées devrait toutefois être simple.)

0
a_guest