- Construya el gráfico de decisión, agregue el vértice de inicio a él. Cada vértice contiene "nivel de recorte", es decir, el valor al que se deben disminuir todos los valores de matriz a la izquierda del nodo actual. El "nivel de ajuste" del vértice de inicio es infinito. Cada borde del gráfico tiene un valor, que corresponde al costo de la decisión.
- Para cada elemento de la matriz, a partir de la más a la derecha, realice los pasos 3 .. 5.
- para cada vértice de la hoja, realice los pasos 4 .. 5.
- Crear hasta 2 bordes salientes, (1) con el el costo de eliminar el elemento de matriz y (2) con el costo de recortar todos los elementos a la izquierda (exactamente, el costo de disminuir el "nivel de recorte").
- Conecte estos bordes a los vértices recién creados, un vértice para cada elemento de la matriz y cada "nivel de recorte".
- Encuentra la ruta más corta desde el vértice de inicio hasta uno de los vértices, correspondiente al elemento de la matriz más a la izquierda. La longitud de esta ruta es igual al costo de la solución.
- Disminuye y elimina elementos de matriz de acuerdo con el gráfico de decisión.
Este algoritmo se puede tratar como una optimización del enfoque de fuerza bruta. Para la búsqueda de fuerza bruta, comenzando desde el elemento de la matriz más a la derecha, construye el árbol de decisión binario. Cada vértice tiene 2 bordes de salida, uno para la decisión de "eliminar", otra decisión de "recorte". El costo de la decisión está asociado con cada borde. El "nivel de recorte" está asociado a cada vértice. La solución óptima está determinada por el camino más corto en este árbol.
Elimine cada ruta, que obviamente no es óptima. Por ejemplo, si el elemento más grande es el último en la matriz, la decisión de "recortar" ha costado cero, y la decisión de "eliminar" no es óptima. Eliminar ruta, a partir de esta decisión de "eliminar". Después de esta optimización, el árbol de decisión es más disperso: algunos vértices tienen 2 bordes salientes, algunos, solo uno.
En cada nivel de profundidad, el árbol de decisión puede tener varios vértices con el mismo "nivel de recorte". Los subárboles, a partir de estos vértices, son idénticos entre sí. Esa es una buena razón para unir todos estos vértices a un vértice. Esto transforma el árbol en un gráfico que tiene como máximo n /2 vértices.
implementación más simple de este algoritmo es O (n), porque para cada uno de los O (n) vértices que calcula el recorte de coste iterativamente, en O Complejidad de tiempo (n) .
Los cálculos de costos de recorte repetidos no son necesarios si hay memoria suficiente para almacenar todos los resultados del costo de recorte parcial. Esto puede requerir O (n) o incluso O (n) espacio.
Con dicha optimización, este algoritmo es O (n). Debido a la estructura simple del gráfico, la búsqueda de ruta más corta tiene O (n) complejidad, no O (n * log (n)).
C++ 11 aplicación (tanto en el espacio y el tiempo la complejidad es O (n 2 )):
//g++ -std=c++0x
#include <iostream>
#include <vector>
#include <algorithm>
typedef unsigned val_t;
typedef unsigned long long acc_t; // to avoid overflows
typedef unsigned ind_t;
typedef std::vector<val_t> arr_t;
struct Node
{
acc_t trimCost;
acc_t cost;
ind_t link;
bool used;
Node()
: trimCost(0)
, used(false)
{}
};
class Matrix
{
std::vector<Node> m;
ind_t columns;
public:
Matrix(ind_t rows, ind_t cols)
: m(rows * cols)
, columns(cols)
{}
Node& operator() (ind_t row, ind_t column)
{
return m[columns * row + column];
}
};
void fillTrimCosts(const arr_t& array, const arr_t& levels, Matrix& matrix)
{
for (ind_t row = 0; row != array.size(); ++row)
{
for (ind_t column = 0; column != levels.size(); ++column)
{
Node& node = matrix(row + 1, column);
node.trimCost = matrix(row, column).trimCost;
if (array[row] > levels[column])
{
node.trimCost += array[row] - levels[column];
}
}
}
}
void updateNode(Node& node, acc_t cost, ind_t column)
{
if (!node.used || node.cost > cost)
{
node.cost = cost;
node.link = column;
}
}
acc_t transform(arr_t& array)
{
const ind_t size = array.size();
// Sorted array of trim levels
arr_t levels = array;
std::sort(levels.begin(), levels.end());
levels.erase(
std::unique(levels.begin(), levels.end()),
levels.end());
// Initialize matrix
Matrix matrix(size + 1, levels.size());
fillTrimCosts(array, levels, matrix);
Node& startNode = matrix(size, levels.size() - 1);
startNode.used = true;
startNode.cost = 0;
// For each array element, starting from the last one
for (ind_t row = size; row != 0; --row)
{
// Determine trim level for this array element
auto iter = std::lower_bound(levels.begin(), levels.end(), array[row - 1]);
const ind_t newLevel = iter - levels.begin();
// For each trim level
for (ind_t column = 0; column != levels.size(); ++column)
{
const Node& node = matrix(row, column);
if (!node.used)
continue;
// Determine cost of trimming to current array element's level
const acc_t oldCost = node.trimCost;
const acc_t newCost = matrix(row, newLevel).trimCost;
const acc_t trimCost = (newCost > oldCost)? newCost - oldCost: 0;
// Nodes for "trim" and "delete" decisions
Node& trimNode = matrix(row - 1, newLevel);
Node& nextNode = matrix(row - 1, column);
if (trimCost)
{
// Decision needed, update both nodes
updateNode(trimNode, trimCost + node.cost, column);
updateNode(nextNode, array[row - 1] + node.cost, column);
trimNode.used = true;
}
else
{
// No decision needed, pass current state to the next row's node
updateNode(nextNode, node.cost, column);
}
nextNode.used = true;
}
}
// Find optimal cost and starting trim level for it
acc_t bestCost = size * levels.size();
ind_t bestLevel = levels.size();
for (ind_t column = 0; column != levels.size(); ++column)
{
const Node& node = matrix(0, column);
if (node.used && node.cost < bestCost)
{
bestCost = node.cost;
bestLevel = column;
}
}
// Trace the path of minimum cost
for (ind_t row = 0; row != size; ++row)
{
const Node& node = matrix(row, bestLevel);
const ind_t next = node.link;
if (next == bestLevel && node.cost != matrix(row + 1, next).cost)
{
array[row] = 0;
}
else if (array[row] > levels[bestLevel])
{
array[row] = levels[bestLevel];
}
bestLevel = next;
}
return bestCost;
}
void printArray(const arr_t& array)
{
for (val_t val: array)
if (val)
std::cout << val << ' ';
else
std::cout << "* ";
std::cout << std::endl;
}
int main()
{
arr_t array({9,8,7,6,5,4,3,2,1});
printArray(array);
acc_t cost = transform(array);
printArray(array);
std::cout << "Cost=" << cost << std::endl;
return 0;
}
decremento como en 'a [i] -'? – soulcheck
Y para ser claros: no tiene que terminar con todos los enteros con los que comenzó? – Jere
sí, que no tienen que terminar con los números enteros que empezamos – TimeToCodeTheRoad