-
Book Overview & Buying
-
Table Of Contents
C++ Data Structures and Algorithm Design Principles
By :
In this activity, we will optimize our inventory for sale to maximize our profits. Follow these steps to complete the activity:
#include <iostream>
#include <vector>
using namespace std;
struct Product
{
int quantity;
int price;
int value;
Product(int q, int p, int v)
: quantity(q), price(p), value(v) {}
};
int main()
{
int N, budget, capacity;
cin >> N >> budget >> capacity;
vector<Product> products;
for(int i = 0; i < N; i++)
{
int quantity, cost, value;
cin >> quantity >> cost >> value;
products.push_back(Product(quantity, cost, value));
}
...
return 0;
}
– The sum of the cost of all the products in the subset must not exceed budget.
– The sum of the quantity of all the products in the subset must not exceed capacity.
– The sum of the value of all the products in the subset must be maximized.
Given these criteria, we can see that each state can be defined by the following parameters:
– The current item being considered
– The number of units previously purchased
– The total cost of the purchased items
– The total profit gained after selling the products at retail value
We can also conclude that a search will terminate when:
– All the items have been considered
– The total cost exceeds the budget
– The total number of units exceeds the capacity
Like the traditional 0-1 knapsack problem, we will consider each item from 0 to N-1 linearly. For each item at index i, our states can transition in one of two ways: by either including the current item or leaving it. Writing the recursive logic in pseudocode may look like this:
F(i, count, cost, total):
I –> The index of the current item
Cost –> The total money spent
count –> The number of units purchased
total –> The total profit value of the chosen items
Base cases:
if i = N: return total
if cost > budget: return 0
if count > capacity: return 0
Recurrence:
F(i, count, cost, total) = maximum of:
F(i + 1, count + quantity[i], cost + price[i],
total + value[i]) – Include the item
AND
F(i + 1, count, cost, total) – Leave as-is
As shown in the preceding code, the recurrence relation is defined according to the values of i, count, cost, and total. Converting this logic from top down to bottom up can be done like so:
Base case:
DP(0, 0, 0) = 0 [Nothing has been chosen yet]
For i = 1 to N:
Product -> quantity, price, value
For cost = 0 to budget:
For count = 0 to capacity:
If price is greater than cost OR
quantity is greater than count:
DP(i, cost, count) = DP(i-1, cost, count)
Otherwise:
DP(i, cost, count) = maximum of:
DP(i-1, cost, count)
AND
DP(i-1, cost – price, count – quantity) + value
In other words, each state is described according to the current index, total cost, and total count. For each pair of valid cost and count values, the current result for an item at index i will be equal either to the maximum subset sum that was found for the same values of cost and count at index i – 1 (that is, DP[i – 1][cost][count]) or the sum of the current item's value with the maximum sum at index i – 1 with cost and count equal to what they would have been prior to including the item (that is, DP[i - 1][cost – price][count – quantity] + value).
vector<vector<vector<int>>> DP(N + 1, vector<vector<int>>(budget + 1, vector<int>(capacity + 1, 0)));
for(int i = 1; i <= N; i++)
{
Product product = products[i-1];
for(int cost = 0; cost <= budget; cost++)
{
for(int count = 0; count <= capacity; count++)
{
if(cost < product.price || count < product.quantity)
{
DP[i][cost][count] = DP[i-1][cost][count];
}
else
{
DP[i][cost][count] = max
(
DP[i-1][cost][count],
DP[i-1][cost – product.price][count – product.quantity] + product.value
);
}
}
}
cout << DP[N][budget][capacity] << endl;
}
As you can see, the implementation is equivalent to the 0-1 knapsack solution with an additional dimension.
This activity has quite a few potential pitfalls if you do not approach it with some forethought. The most difficult aspect of it is the fact that it requires a number of distinct steps, and a careless mistake at any point can cause the entire program to fail. Therefore, it is recommended to approach the implementation step by step. The primary steps that are required are as follows:
Since this is considerably lengthier than the other activities in this chapter, let's attack each of these steps individually.
Step 0: Preliminary Setup
Before we write any code related to input, we should decide how we want to represent our data in advance. The input we will receive is as follows:
Now, let's look at the implementation:
#include <iostream>
#include <vector>
using namespace std;
const int UNKNOWN = 1e9;
const char EMPTY_SPACE = '.';
const string roads = "-|/\\";
struct Point
{
int x;
int y;
Point(){}
Point(int x, int y) : x(x), y(y) {}
};
int N;
int H, W;
vector<string> grid;
vector<vector<int>> terrain;
vector<vector<int>> cost;
vector<Point> houses;
Step 1: Handling the Input
void Input()
{
cin >> H >> W;
cin >> N;
grid.resize(H);
houses.resize(N);
terrain.resize(H, vector<int>(W, UNKNOWN)); cost.resize(H, vector<int>(W, UNKNOWN));
// Map of property
for(auto &row : grid) cin >> row;
// Terrain ruggedness
for(int I = 0; i < H; i++)
{
for(int j = 0; j < W; j++)
{
cin >> terrain[i][j];
}
}
// House coordinates
for(int i = 0; i < N; i++)
{
cin >> houses[i].x >> house[i].y;
// Set house labels in grid
grid[houses[i].y][houses[i].x] = char(i + 'A');
}
}
Step 2: Building the Graph
The problem description states the following:
To test the first condition, we simply need to compare the coordinates of two points and determine whether any of the following three conditions are true:
Now, let's get back to our code.
bool DirectLine(Point a, Point b)
{
return a.x == b.x || a.y == b.y || abs(a.x – b.x) == abs(a.y – b.y);
}
int GetCost(int start, int end)
{
Point a = houses[start];
Point b = houses[end];
// The values by which the coordinates change on each iteration
int x_dir = 0;
int y_dir = 0;
if(a.x != b.x)
{
x_dir = (a.x < b.x) ? 1 : -1;
}
if(a.y != b.y)
{
y_dir = (a.y < b.y) ? 1 : -1;
}
int cost = 0;
do
{
a.x += x_dir;
a.y += y_dir;
cost += terrain[a.y][a.x];
}
while(grid[a.y][a.x] == '.');
return (a != b) ? UNKNOWN : res;
}
struct Point
{
......
bool operator !=(const Point &other) const { return x != other.x || y != other.y; }
}
void GetAdjacencies()
{
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
if(DirectLine(houses[i], houses[j])
{
cost[i][j] = cost[j][i] = GetCost(i, j);
}
}
}
}
Step 3: Finding the Shortest Distances between Nodes
The problem states that two houses should be connected by a road that is on the path that minimizes the cost of reaching the exit point. For this implementation, we will use the Floyd-Warshall algorithm. Let's get back to our code:
void GetShortestPaths()
{
vector<vector<int>> dist(N, vector<int>(N, UNKNOWN));
vector<vector<int>> next(N, vector<int>(N, UNKNOWN));
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
dist[i][j] = cost[i][j]
if(dist[i][j] != UNKNOWN)
{
next[i][j] = j;
}
}
dist[i][j] = 0;
next[i][i] = i;
}
...
}
for(int mid = 0; mid < N; mid++)
{
for(int start = 0; start < N; start++)
{
for(int end = 0; end < N; end++)
{
if(dist[start][end] > dist[start][mid] + dist[mid][end])
{
dist[start][end] = dist[start][mid] + dist[mid][end];
next[start][end] = next[start][mid];
}
}
}
}
Step 4: Reconstructing the Path
With the data that we obtained in the next matrix, we can easily reconstruct the points on each path in a similar way to the reconstruction approaches for the LCS or 0-1 Knapsack problems. For this purpose, we will define another function, GetPath(), that has three parameters—two integers, start and end, and a reference to the next matrix — and returns an integer vector containing the node indices of the path:
vector<int> GetPath(int start, int end, vector<vector<int>> &next)
{
vector<int> path = { start };
do
{
start = next[start][end];
path.push_back(start);
}
while(next[start][end] != end);
return path;
}
for(int i = 0; i < N; i++)
{
auto path = GetPath(i, N – 1, next);
int curr = i;
for(auto neighbor : path)
{
DrawPath(curr, neighbor);
curr = neighbor;
}
}
Step 5: Redrawing the Grid
void DrawPath(int start, int end)
{
Point a = houses[start];
Point b = houses[end];
int x_dir = 0;
int y_dir = 0;
if(a.x != b.x)
{
x_dir = (a.x < b.x) 1 : -1;
}
if(a.y != b.y)
{
y_dir = (a.y < b.y) 1 : -1;
}
……
}
int GetDirection(int x_dir, int y_dir)
{
if(y_dir == 0) return 0;
if(x_dir == 0) return 1;
if(x_dir == -1)
{
return (y_dir == 1) ? 2 : 3;
}
return (y_dir == 1) ? 3 : 2;
}
void DrawPath(int start, int end)
{
……
int direction = GetDirection(x_dir, y_dir);
char mark = roads[direction];
……
}
do
{
a.x += x_dir;
a.y += y_dir;
if(grid[a.y][a.x] == EMPTY_SPACE)
{
grid[a.y][a.x] = mark;
}
else if(!isalpha(grid[a.y][a.x]))
{
// If two roads of differing orientations intersect, replace symbol with '+'
grid[a.y][a.x] = (mark != grid[a.y][a.x]) ? '+' : mark;
}
}
while(a != b);
int main()
{
Input();
BuildGraph();
GetShortestPaths();
for(auto it : grid)
{
cout << it << endl;
}
return 0;
}
Change the font size
Change margin width
Change background colour