最小垂直切片和

时间:2018-08-27 19:03:02

标签: python algorithm

我想输入一个矩阵大小N x N并切成一片,以使每个元素都直接位于其上方,下方或下方。成本是切片中所有元素的总和。我该如何编写程序来做到这一点?

例如。矩阵作为列表列表

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

具有以下切片:

(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)

然后权重最低的切片是(1,4,7),总和为12。

5 个答案:

答案 0 :(得分:2)

正如vivek所说,您可以使用动态程序解决此问题:

创建一个成本表,其大小与输入矩阵相同。成本矩阵的每个元素都存储以该元素结尾的最小切片的成本。如果您还将前一个slice元素存储在此费用表中,则还可以最终提取实际切片(而不只是其费用)。

您可以很容易地初始化成本表。只需将输入矩阵的第一行复制到表中即可。然后,我们将逐行填充表格的其余部分。假设C是成本矩阵,M是输入矩阵。然后:

//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))

此后,您只需要在C的最后一行中找到最小值,这将为您提供最小切片的成本。要获取实际的切片,请按照您在循环期间设置的先前指针(未在伪代码段中显示)进行操作。

答案 1 :(得分:2)

我们可以将矩阵元素视为graph中的顶点,并将可能的连接(定义为“切片”)视为边。那么问题就可以表示为找到从任何一个顶行顶点到任何一个底行顶点的最短路径,其中每个边的权重等于所连接元素的值(连接第一行的边除外)加上第一行元素的权重。

然后,我们可以使用Bellman-Ford algorithm例如在这些条件下查找最短路径。以下是示例实现:

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]))])

哪个作为输出:

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 :(得分:0)

可以使用graph theory表示此问题,然后使用linear programming技术解决该问题。

通过将矩阵的所有元素都视为顶点,目标是找到一组边,这些边在某些约束下(例如,如何构建切片)可以最小化通过矩阵的加权路径。

我们可以使用scipy.optimize.linprog(使用Simplex algorithm)来解决线性规划问题c.T @ x。解的每个元素表示N个节点中任意一个节点之间的可能连接(即解向量的大小为N ** 2)。系数c确定连接的权重:即连接到的节点的权重(矩阵元素的值),除了第一层外,我们还需要添加起始权重。

我们还需要应用几个约束条件以获得有效的解决方案:

  1. 结果必须具有准确的N - 1边。
  2. 每个节点不得与其自身连接。
  3. 除最后一行外,每行都必须连接到一个节点。
  4. 每个节点最多可以连接到另一个节点(最后一层的连接必须为零)。
  5. 每个节点都必须尊重其可能的后继节点(由切片的“形状”给出)。
  6. 对于第1层到第(N-1)层中的每一层,任何连接到的节点都必须连接到下一层。

这些很多东西我们必须放在一起,所以生成的代码有点冗长,一开始可能看起来很笨拙。但是,当仔细观察时,应该可以识别出单件及其结构(我尝试尽可能多地评论+添加一些印刷品)。因此,示例代码如下:

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.

最后一个输出是结果,其格式为:

[[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.]]

这告诉我们,为了获得最小路径,我们应该将节点0(行索引)连接到节点3(列索引),以及将节点3(行索引)连接到节点6(列索引)。这表示路径(或“切片”)(1, 4, 7)。我们可以从第一行开始,然后沿图表作为边缘点来推导路径:

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())

最后的笔记

上面的示例代码为性能改进留出了很大的空间。例如,它将节点之间的所有可能连接视为解决方案,其缩放比例为M.size**2。尽管我们限制了可能的连接,但与通过从一开始就将其包含在问题体系结构中来对其进行约束相比,计算量仍然要大得多。这意味着我们可以只使用M.size**2 coefficients而不是2*(M.shape[0] - 1) + 3*(M.shape[1] - 2)*(M.shape[0] - 1),而M.size只能缩放为git rev-parse --show-superproject-working-tree。另外,因为我们已经在问题的体系结构中建立了这些约束,所以我们可以使用更小的约束矩阵。以上面的代码示例为基础,应该可以对其进行相应的调整。因此,在这一点上,我将总结一下,并将可能的性能改进的实现留给感兴趣的读者。

(以上实现也仅适用于平方矩阵,但是对非平方矩阵的泛化应该很简单。)

答案 3 :(得分:0)

这是递归+动态编程(DP)的问题。您可能会或可能不会使用DP,具体取决于测试用例的大小。通常在竞争性编程竞赛中会问这类问题,如果您发现测试用例超时,我建议您使用DP扩充我的代码。在解释了算法并给您代码后,我将讨论如何做。

您必须从矩阵最上一行的每个元素开始向下遍历。向下移动时,您将有三个选择:

  1. 下移到您所在元素正下方的元素
  2. 向下移动到位于您所在元素正下方的元素左侧的元素
  3. 向下移动到位于您所在元素正下方的元素右侧的元素

继续使用递归遍历时,请继续添加元素。因此,对于矩阵中的每个元素,三种求和是可能的。我称它们为左和,中和和右。名称本身很直观,但是请不要在注释中提出疑问。

我维护一个全局列表以保留每个切片的总和。最后,我从该全局列表中返回最小尺寸的元素。更不用说此列表中最小的元素将是矩阵的最小垂直切片。

请找到以下代码(在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

将DP添加到代码中: 您可能已经注意到,此代码跟踪每个元素的左,中和右和。您可以通过在较小的矩阵(最好是2x3)上干运行该代码来轻松找到该代码,然后再次计算元素的总和。为避免这种情况,您可以制作一个与原始矩阵大小相同的矩阵,并将每个元素的三个和存储为元组。如果元组存在于特定元素,请从矩阵中获取元组。这样可以防止额外的函数调用并节省内存。

答案 4 :(得分:0)

def findMinPath(mat): 
  
    # To find max val in first row 
    res = min([mat[0][i] for i in range(M)])
   
    for i in range(1, N):     
        for j in range(M):
            if j == 0: #if the left most, no col(j-1) in the row above
                mat[i][j] += min(mat[i - 1][j], mat[i - 1][j + 1]) 
            elif j == M-1: #if the right most, no col(j+1) in the row above
                mat[i][j] += min(mat[i - 1][j], mat[i - 1][j - 1]) 
            else:
                mat[i][j] += min(mat[i - 1][j], mat[i - 1][j - 1],mat[i - 1][j + 1]) 
   
    # To find max val in first row      
    res = min([mat[N-1][i] for i in range(M)])
    
    return res