why knapsack implementation need n as argument - java

When I read solution to knapsack problem (http://en.wikipedia.org/wiki/Knapsack_problem), I couldn't understand why there is iteration number n in the argument. It seems we can come to leaf use case by checking the passed limit. Ex. the 15KG backpack problem, solution seems like:
Value(n, W){ // W = limit, n = # items still to choose from
if (n == 0) return 0;
if (arr[n][W] != unknown) return arr[n][W]; // <- add memoize
if (s[n] > W) result = Value(n-1,W);
else result = max{v[n] + Value(n-1, W-w[n]), Value(n-1, W)};
arr[n][W] = result; // <- add memoize
return result;
}
My non-memoize method looks like the below, which is easier to understand, at least for me, and also could be improved with memoization.
static int n =5;
static int [] w = new int[]{12,2,1,4,1}; //weight
static int [] v = new int[]{4,2,1,10,2}; //value
public static int knapSack(int wt){
int maxValue = 0,vtemp = 0, wtemp =0;
if (wt ==0) return 0;
for (int i=0; i<n; i++){
if (w[i] > wt) continue;
int tmp = v[i] + knapSack(wt - w[i]);
if (tmp > maxValue){
maxValue = tmp;
vtemp = v[i];
wtemp = w[i];
}
}
System.out.println("wt="+wt + ",vtemp="+vtemp+",wtemp="+wtemp+",ret max="+maxValue);
return maxValue;
}
So my question is:
why do we need n for argument?
statement if (s[n] > W) result = Value(n-1,W); make me even harder to understand why
I see the same big O for memoized version of my approach. Any other difference?
Thanks.

You're actually solving a different problem. The first piece of code (with n) solves the 0-1 knapsack problem, where you can choose to take at most one of any particular item (i.e. there is no "copying" of items). In that case, you need n to keep track of which items you've already used up.
In the second piece of code, you're solving the unbounded knapsack problem, in which you can take every item an unlimited number of times.
They're both forms of the NP-complete knapsack problem, but they have different solutions.

Related

Good fix for this algorithmic puzzle code (USACO)?

I'm a first-year computer science student and I am currently dabbling in some algorithmic competitions. The code below that I made has a flaw that I'm not sure how to fix
Here is the problem statement:
http://www.usaco.org/index.php?page=viewproblem2&cpid=811
In the statement, I missed where it said that Farmer John could only switch boots on tiles that both boots can stand on. I tried adding constraints in different places but none seemed to address the problem fully. I don't really see a way to do it without butchering the code
Basically, the problem is that John keeps switching boots on tiles where the new boots can't stand on, and I can't seem to fix it
Here is my code (sorry for the one letter variables):
import java.io.*;
import java.util.*;
public class snowboots {
static int n,k;
static int[] field,a,b; //a,b --> strength, distance
static int pos;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("snowboots.in"));
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("snowboots.out")));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
k = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
field = new int[n];
a = new int[k];
b = new int[k];
for (int i = 0; i < n; i++)
field[i] = Integer.parseInt(st.nextToken());
for (int i = 0; i < k; i++) {
st = new StringTokenizer(br.readLine());
a[i] = Integer.parseInt(st.nextToken());
b[i] = Integer.parseInt(st.nextToken());
}
pw.println(solve());
pw.close();
}
static int solve() {
pos = 0;
int i = 0; //which boot are we on?
while(pos < n-1) {
while(move(i)); //move with boot i as far as possible
i++; //use the next boot
}
i--;
return i;
}
static boolean move(int c) {
for (int i = pos+b[c]; i > pos; i--) {
if (i < n && field[i] <= a[c]) { //snow has to be less than boot strength
pos = i;
return true;
}
}
return false;
}
}
I tried adding a constraint in the "move" method, and one when updating I, but they both are too strict and activate at unwanted times
Is it salvageable?
Yes, it's possible to salvage your solution, by adding an extra for-loop.
What you need to do is, if you find that your previous pair of boots can get you all the way to a tile that's too deep in snow for your next pair, then you need to try "backtracking" to the latest tile that's not too deep. This ends up giving a solution in worst-case O(N·B) time and O(1) extra space.
It may not be obvious why it's OK to backtrack to that tile — after all, just because you can reach a given tile, that doesn't necessarily mean that you were able to reach all the tiles before it — so let me explain a bit why it is OK.
Let maxReachableTileNum be the number (between 1 and N) of the last tile that you were able to reach with your previous boots, and let lastTileNumThatsNotTooDeep be the number (between 1 and N) of the last tile on or before maxReachableTileNum that's not too deeply snow-covered for your next pair. (We know that there is such a tile, because tile #1 has no snow at all, so if nothing else we know that we can backtrack to the very beginning.) Now, since we were able to get to maxReachableTileNum, then some previous boot must have either stepped on lastTileNumThatsNotTooDeep (in which case, no problem, it's reachable) or skipped over it to some later tile (on or before maxReachableTileNum). But that later tile must be deeper than lastTileNumThatsNotTooDeep (because that later tile's depth is greater than scurrentBootNum, which is at least at great as the depth of lastTileNumThatsNotTooDeep), which means that the boot that skipped over lastTileNumThatsNotTooDeep certainly could have stepped on lastTileNumThatsNotTooDeep instead: it would have meant taking a shorter step (OK) onto a less-deeply-covered tile (OK) than what it actually did. So, either way, we know that lastTileNumThatsNotTooDeep was reachable. So it's safe for us to try backtracking to lastTileNumThatsNotTooDeep. (Note: the below code uses the name reachableTileNum instead of lastTileNumThatsNotTooDeep, because it continues to use the reachableTileNum variable for searching forward to find reachable tiles.)
However, we still have to hold onto the previous maxReachableTileNum: backtracking might turn out not to be helpful (because it may not let us make any further forward progress than we already have), in which case we'll just discard these boots, and move on to the next pair, with maxReachableTileNum at its previous value.
So, overall, we have this:
public static int solve(
final int[] tileSnowDepths, // tileSnowDepths[0] is f_1
final int[] bootAllowedDepths, // bootAllowedDepths[0] is s_1
final int[] bootAllowedTilesPerStep // bootAllowedTilesPerStep[0] is d_1
) {
final int numTiles = tileSnowDepths.length;
final int numBoots = bootAllowedDepths.length;
assert numBoots == bootAllowedTilesPerStep.length;
int maxReachableTileNum = 1; // can reach tile #1 even without boots
for (int bootNum = 1; bootNum <= numBoots; ++bootNum) {
final int allowedDepth = bootAllowedDepths[bootNum-1];
final int allowedTilesPerStep = bootAllowedTilesPerStep[bootNum-1];
// Find the starting-point for this boot -- ideally the last tile
// reachable so far, but may need to "backtrack" if that tile is too
// deep; see explanation above of why it's safe to assume that we
// can backtrack to the latest not-too-deep tile:
int reachableTileNum = maxReachableTileNum;
while (tileSnowDepths[reachableTileNum-1] > allowedDepth) {
--reachableTileNum;
}
// Now see how far we can go, updating both maxReachableTileNum and
// reachableTileNum when we successfully reach new tiles:
for (int tileNumToTry = maxReachableTileNum + 1;
tileNumToTry <= numTiles
&& tileNumToTry <= reachableTileNum + allowedTilesPerStep;
++tileNumToTry
) {
if (tileSnowDepths[tileNumToTry-1] <= allowedDepth) {
maxReachableTileNum = reachableTileNum = tileNumToTry;
}
}
// If we've made it to the last tile, then yay, we're done:
if (maxReachableTileNum == numTiles) {
return bootNum - 1; // had to discard this many boots to get here
}
}
throw new IllegalArgumentException("Couldn't reach last tile with any boot");
}
(I tested this on USACO's example data, and it returned 2, as expected.)
This can potentially be optimized further, e.g. with logic to skip pairs of boots that clearly aren't helpful (because they're neither stronger nor more agile than the previous successful pair), or with an extra data structure to keep track of the positions of latest minima (to optimize the backtracking process), or with logic to avoid backtracking further than is conceivably useful; but given that N·B ≤ 2502 = 62,500, I don't think any such optimizations are warranted.
Edited to add (2019-02-23): I've thought about this further, and it occurs to me that it's actually possible to write a solution in worst-case O(N + B log N) time (which is asymptotically better than O(N·B)) and O(N) extra space. But it's much more complicated; it involves three extra data-structures (one to keep track of the positions of latest minima, to allow backtracking in O(log N) time; one to keep track of the positions of future minima, to allow checking in O(log N) time if the backtracking is actually helpful (and if so to move forward to the relevant minimum); and one to maintain the necessary forward-looking information in order to let the second one be maintained in amortized O(1) time). It's also complicated to explain why that solution is guaranteed to be within O(N + B log N) time (because it involves a lot of amortized analysis, and making a minor change that might seem like an optimization — e.g., replacing a linear search with a binary search — can break the analysis and actually increase the worst-case time complexity. Since N and B are both known to be at most 250, I don't think all the complication is worth it.
You can solve this problem by Dynamic Programming. You can see the concept in this link (Just read the Computer programming part).
It has following two steps.
First solve the problem recursively.
Memoize the states.
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mx 100005
#define mod 1000000007
int n, b;
int f[333], s[333], d[333];
int dp[251][251];
int rec(int snowPos, int bootPos)
{
if(snowPos == n-1){
return 0;
int &ret = dp[snowPos][bootPos];
if(ret != -1) return ret;
ret = 1000000007;
for(int i = bootPos+1; i<b; i++)
{
if(s[i] >= f[snowPos]){
ret = min(ret, i - bootPos + rec(snowPos, i));
}
}
for(int i = 1; i<=d[bootPos] && snowPos+i < n; i++){
if(f[snowPos + i] <= s[bootPos]){
ret = min(ret, rec(snowPos+i, bootPos));
}
}
return ret;
}
int main()
{
freopen("snowboots.in", "r", stdin);
freopen("snowboots.out", "w", stdout);
scanf("%d %d", &n, &b);
for(int i = 0; i<n; i++)
scanf("%d", &f[i]);
for(int i = 0; i<b; i++){
scanf("%d %d", &s[i], &d[i]);
}
memset(dp, -1, sizeof dp);
printf("%d\n", rec(0, 0));
return 0;
}
This is my solution to this problem (in C++).
This is just a recursion. As problem says,
you can change boot, Or
you can do a jump by current boot.
Memoization part is done by the 2-Dimensional array dp[][].
One way which to solve it using BFS. You may refer below code for details. Hope this helps.
import java.util.*;
import java.io.*;
public class SnowBoots {
public static int n;
public static int[] deep;
public static int nBoots;
public static Boot[] boots;
public static void main(String[] args) throws Exception {
// Read the grid.
Scanner stdin = new Scanner(new File("snowboots.in"));
// Read in all of the input.
n = stdin.nextInt();
nBoots = stdin.nextInt();
deep = new int[n];
for (int i = 0; i < n; ++i) {
deep[i] = stdin.nextInt();
}
boots = new Boot[nBoots];
for (int i = 0; i < nBoots; ++i) {
int d = stdin.nextInt();
int s = stdin.nextInt();
boots[i] = new boot(d, s);
}
PrintWriter out = new PrintWriter(new FileWriter("snowboots.out"));
out.println(bfs());
out.close();
stdin.close();
}
// Breadth First Search Algorithm [https://en.wikipedia.org/wiki/Breadth-first_search]
public static int bfs() {
// These are all valid states.
boolean[][] used = new boolean[n][nBoots];
Arrays.fill(used[0], true);
// Put each of these states into the queue.
LinkedList<Integer> q = new LinkedList<Integer>();
for (int i = 0; i < nBoots; ++i) {
q.offer(i);
}
// Usual bfs.
while (q.size() > 0) {
int cur = q.poll();
int step = cur / nBoots;
int bNum = cur % nBoots;
// Try stepping with this boot...
for (int i = 1; ((step + i) < n) && (i <= boots[bNum].maxStep); ++i) {
if ((deep[step+i] <= boots[bNum].depth) && !used[step+i][bNum]) {
q.offer(nBoots * (step + i) + bNum);
used[step + i][bNum] = true;
}
}
// Try switching to another boot.
for (int i = bNum + 1; i < nBoots; ++i) {
if ((boots[i].depth >= deep[step]) && !used[step][i]) {
q.offer(nBoots * step + i);
used[step][i] = true;
}
}
}
// Find the earliest boot that got us here.
for (int i = 0; i < nBoots; ++i) {
if (used[n - 1][i]) {
return i;
}
}
// Should never get here.
return -1;
}
}
class Boot {
public int depth;
public int maxStep;
public Boot(int depth, int maxStep) {
this.depth = depth;
this.maxStep = maxStep;
}
}

How to optmize the method

I have been solving a problem in hackerrank. I am sure my solution is right but as the input matrix gets large the program terminates due to time out.
I have a method where i find a series given below. This method takes array index numbers and computes a number based on the method. Based on the number, i fill up my array with something. But the program terminates every time. It only works with for maximum n=2. I think this method should be optimized because it uses huge recursion for large n. Is there any suggestion what should i do ?
static int hacko(int n)
{
if(n==1)
return 1;
else if(n==2)
return 2;
else if(n==3)
return 3;
else
return hacko(n-1)+(2*hacko(n-2))+(3*hacko(n-3));
}
You could avoid unnecessary branches, which can be costly, like this:
static int hacko(int n) {
if(n < 4)
return n;
else
return hacko(n-1)+(2*hacko(n-2))+(3*hacko(n-3));
}
I assume n > 0, otherwise use if(n > 0 && n < 4). However, you stated:
It only works with for maximum n=2.
So the method you posted is most likely not the bottleneck, since n=3 does not add any significant complexity to the code compared to n=1 or n=2. Or what do you mean by this?
As recursion is not a requirement for you, you can do the following iterative approach:
static int hacko(int n) {
// Shortcut for n=1, n=2 and n=3
if (n < 4)
return n;
// Array to store the previous results
int[] temp = new int[n];
temp[0] = 1;
temp[1] = 2;
temp[2] = 3;
// Iterative approach, more scalable, counts up
for (int i = 3; i < n; i++) {
temp[i] = 3 * temp[i - 3] + 2 * temp[i - 2] + temp[i - 1];
}
return temp[n - 1];
}
The problem here is, for large values of n, it calculates hacko(n-1)+(2*hacko(n-2))+(3*hacko(n-3)) recursively. This can be time consuming and unnecessary.
You can optimize it by saving values of hackos(i) in an array and fetching the values of hacko(n-1)+(2*hacko(n-2))+(3*hacko(n-3)) from the array and not calculating it recursively everytime. U need to start the loop from i=1 to i=N
Ex:
int savedData[] = new int[N];
static int hacko(int n)
{
if(n==1)
return 1;
else if(n==2)
return 2;
else if(n==3)
return 3;
else
return savedData[n-1]+(2*savedData[n-2])+(3*savedData[n-3]);
}
for(int i=1;i<N;i++) {
savedData[i] = hacko(i);
}
Hope it Helps.

Max Double Slice Sum codility O(1) space complexity fail performance test case

I was trying figure out why the below solution failed for a single performance test case for the 'Max Double Slice Sum' problem in the codility website: https://codility.com/demo/take-sample-test/max_double_slice_sum
There is another solution O(n) space complexity which is easier to comprehend overhere: Max double slice sum. But i am just wondering why this O(1) solution doesn't work. Below is the actual code:
import java.util.*;
class Solution {
public int solution(int[] A) {
long maxDS = 0;
long maxDSE = 0;
long maxS = A[1];
for(int i=2; i<A.length-1; ++i){
//end at i-index
maxDSE = Math.max(maxDSE+A[i], maxS);
maxDS = Math.max(maxDS, maxDSE);
maxS = Math.max(A[i], maxS + A[i]);
}
return (int)maxDS;
}
}
The idea is simple as follow:
The problem can be readdress as finding max(A[i]+A[i+1]+...+A[j]-A[m]); 1<=i<=m<=j<=n-2; while n = A.length; we call A[m] is missing element within the slice.
maxS[i] will keep max slice which end at current index i; in other words, = max(A[t] + ... + A[i]); while t < i; so when i=1; maxS = A[1]; Note that in solution, we don't keep array but rather latest maxS at current index (See above code).
maxDSE[i] is max of all double slice which end at i; in other words, = max(A[t]+A[t+1]+...+A[i]-A[m])--end at A[i]; maxDS is the final max of double slice sum which we try to find.
Now, we just use a for-loop from i=2; -> i=A.length-2; For each index i, we notice some findings:
If the missing element is A[i], then maxDSE[i] = maxS[i-1] (max sum of
all slice which end at i-1 => or A[t] + ... + A[i] - A[i]);
If missing element is not A[i] -> so it must be somewhere from A[1]->A[i-1] -> maxDSE = maxDSE[i-1] + A[i]; such as A[t] + ... + A[i] - A[m] (not that A[i] must be last element) with t
so maxDSE[i] = Math.max(maxDSE[i-1]+A[i], maxS[i-1]);
maxDS = Math.max(maxDS, maxDSE); max amount all maxDSE;
and maxS[i] = Math.max(A[i], maxS[i-1]+A[i]);
by that way, maxDS will be the final result.
But strange that, I was only able to get 92%; with one failed performance test case as shown here:
medium_range
-1000, ..., 1000
WRONG ANSWER
got 499499 expected 499500
Could anyone please enlighten me where is problem in my solution? Thanks!
Ok, I found the error with my code. Seems that I forgot one corner cases. When calculate DSE[i], in cases A[i] is missing number, maxS should contain the case when array is empty. In other word, maxS should be calculated as:
maxS[i] = Math.max(0, Math.max(A[i]+maxS[i-1], A[i])); while 0 is for case of empty subarray (end at i-th); Math.max(A[i]+maxS[i-1], A[i]) is max of all slice with at least one element (end at i-index). The complete code as follow:
import java.util.*;
class Solution {
public int solution(int[] A) {
long maxDS = 0;
long maxDSE = 0;
long maxS = A[1];
for(int i=2; i<A.length-1; ++i){
maxDSE = Math.max(maxDSE+A[i], maxS);
maxDS = Math.max(maxDS, maxDSE);
maxS = Math.max(0, Math.max(A[i], maxS + A[i]));
}
return (int)maxDS;
}
}
It seems that for the input [-11, -53, -4, 38, 76, 80], your solution doesn't work. Yes, it tricks all the codility test cases, but I managed to trick all codility test cases for other problems too.
If you don't just want to trick codility, but also you want to come with a good solution, I suggest that you create a loop and a large number of random test cases (in number of elements and element values), and create a test method of your own, that you are sure works (even if the complexity is quadratic), compare the results from both methods and then analyze the current random input that doesn't fit.
Here is clear solution. Best approach is to use algorithm of Kanade O(N) and O(1) by space
public class DuplicateDetermineAlgorithm {
public static boolean isContainsDuplicate(int[] array) {
if (array == null) {
throw new IllegalArgumentException("Input array can not be null");
}
if (array.length < 2) {
return false;
}
for (int i = 0; i < array.length; i++) {
int pointer = convertToPositive(array[i]) - 1;
if (array[pointer] > 0) {
array[pointer] = changeSign(array[pointer]);
} else {
return true;
}
}
return false;
}
private static int convertToPositive(int value) {
return value < 0 ? changeSign(value) : value;
}
private static int changeSign(int value) {
return -1 * value;
}
}
I have coded it in vb.net and got 100/100 getting idea form solution by Guillermo
Private Function solution(A As Integer()) As Integer
' write your code in VB.NET 4.0
Dim Slice1() As Integer = Ending(A)
Dim slice2() As Integer = Starting(A)
Dim maxSUM As Integer = 0
For i As Integer = 1 To A.Length - 2
maxSUM = Math.Max(maxSUM, Slice1(i - 1) + slice2(i + 1))
Next
Return maxSUM
End Function
Public Shared Function Ending(input() As Integer) As Integer()
Dim result As Integer() = New Integer(input.Length - 1) {}
result(0) = InlineAssignHelper(result(input.Length - 1), 0)
For i As Integer = 1 To input.Length - 2
result(i) = Math.Max(0, result(i - 1) + input(i))
Next
Return result
End Function
Public Shared Function Starting(input() As Integer) As Integer()
Dim result As Integer() = New Integer(input.Length - 1) {}
result(0) = InlineAssignHelper(result(input.Length - 1), 0)
For i As Integer = input.Length - 2 To 1 Step -1
result(i) = Math.Max(0, result(i + 1) + input(i))
Next
Return result
End Function
Private Shared Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
Visit Codility to see the results

Is this a better way for Fibonacci Series with Recursion?

Where ever I see Recursive Fibonacci Series everyone tell that
a[i] = fib(i - 1) + fib( i - 2)
But it can also be solved with
a[i] = fib(i - 1) + a[i-2] // If array 'a' is a global variable.
If array 'a' is a global Variable, then a[i-2] will be calculated when it is calculating for a[i-2];
It can be solved with below program in java..
public class Fibonacci {
public static int maxNumbers = 10;
public static double[] arr = new double[maxNumbers];
public static void main(String args[])
{
arr[0] = 0;
arr[1] = 1;
recur(maxNumbers - 1);
}
public static double recur(int i)
{
if( i > 1)
{
arr[i] = recur(i - 1) + arr[i - 2];
}
return arr[i];
}
}
Further more, complexity is also less when compared with original procedure. Is there any disadvantage of doing this way?
You have done the first step for Dynamic Programming calculation of Fibonacci, idea of DP is to avoid redundant calculations, and your algorithm achieve its goal.
A "classic" Bottom-Up DP Fibonacci implementation is filling the elements from lower to higher:
arr[0] = 0
arr[1] = 1
for (int i = 2; i <= n; i++)
arr[i] = arr[i-1] + arr[i-2]
(Optimization could be storing curr,last alone, and modifying them at each iteration.
Your approach is basically the same in principle.
As a side note, the DP approach to calculate Fibonacci is taking O(n) time, where there is even more efficient solution with exponential of the matrix:
1 1
1 0
The above holds because you use the fact that
1 1 F_{n+1} 1*F{n+1} + 1*F{n} F_{n+2}
* = =
1 0 F_{n} 1*F{n+1} + 0*F{n} F_{n+1}
Using exponent by squaring on the above matrix, this can be solved in O(logn).
If you just want the nth fibonacci number you could do this:
static double fib(double prev, double curr, int n) {
if(n == 0)
return curr;
return fib(curr, prev+curr, n-1);
}
Initial conditions would be prev = 0, curr = 1, n = maxNumbers. This function is tail recursive because you don't need to store the return value of the recursive call for any additional calculations. The initial stack frame gets reused (which saves memory) and once you hit your base case the value that's returned is the same value that would be returned from every other recursive call.
By using an array like you do you only recalculate one of the two branches (the longest one in each iteration) ending up with a O(n) complexity.
If you were to keep track on how large fibonacci number you have caclulated earlier you can use that and produce O(max(n-prevn, 1)). Here is an altered version of your code that fills the array from bottom to i if needed:
public class Fibonacci {
public static final int maxNumbers = 93; // fib(93) > Long.MAX_VALUE
public static long[] arr = new long[maxNumbers];
public static int calculatedN = 0;
public static long fib(int i) throws Exception
{
if( i >= maxNumbers )
throw new Exception("value out of bounds");
if( calculatedN == 0 ) {
arr[0] = 0L;
arr[1] = 1L;
calculatedN = 1;
}
if( i > calculatedN ) {
for( int x=calculatedN+1; x<=i; x++ ){
arr[x] = arr[x-2] + arr[x-1];
}
calculatedN = i;
}
return arr[i];
}
public static void main (String args[]) {
try {
System.out.println(fib(50)); // O(50-2)
System.out.println(fib(30)); // O(1)
System.out.println(fib(92)); // O(92-50)
System.out.println(fib(92)); // O(1)
} catch ( Exception e ) { e.printStackTrace(); }
}
}
I changed double to long. If you need larger fibonacci numbers than fib(92) I would change from long to Biginteger.
You can also code using two recursive function but as the same value is calculating over again and again so all You can do a dynamic programming approach where You can store the value and return it where need.Like this one in C++
#include <bits/stdc++.h>
using namespace std;
int dp[100];
int fib(int n){
if(n <= 1)
return n;
if(dp[n]!= -1)
return dp[n];
dp[n] = fib(n-1) + fib(n-2);
return dp[n];
}
int main(){
memset(dp,-1,sizeof(dp));
for(int i=1 ;i<10 ;i++)
cout<<fib(i)<<endl;
}
This is only step from non recursive version:
https://gist.github.com/vividvilla/4641152
General this partially recursive approach looks incredibly messy

Java mergesort, should the "merge" step be done with queues or arrays?

This is not homework, I don't have money for school so I am teaching myself whilst working shifts at a tollbooth on the highway (long nights with few customers)
I was trying to implement a simple "mergesort" by thinking first, stretching my brain a little if you like for some actual learning, and then looking at the solution on the manual I am using: "2008-08-21 | The Algorithm Design Manual | Springer | by Steven S. Skiena | ISBN-1848000693".
I came up with a solution which implements the "merge" step using an array as a buffer, I am pasting it below. The author uses queues so I wonder:
Should queues be used instead?
What are the advantages of one method Vs the other? (obviously his method will be better as he is a top algorist and I am a beginner, but I can't quite pinpoint the strengths of it, help me please)
What are the tradeoffs/assumptions that governed his choice?
Here is my code (I am including my implementation of the splitting function as well for the sake of completeness but I think we are only reviewing the merge step here; I do not believe this is a Code Review post by the way as my questions are specific to just one method and about its performance in comparison to another):
package exercises;
public class MergeSort {
private static void merge(int[] values, int leftStart, int midPoint,
int rightEnd) {
int intervalSize = rightEnd - leftStart;
int[] mergeSpace = new int[intervalSize];
int nowMerging = 0;
int pointLeft = leftStart;
int pointRight = midPoint;
do {
if (values[pointLeft] <= values[pointRight]) {
mergeSpace[nowMerging] = values[pointLeft];
pointLeft++;
} else {
mergeSpace[nowMerging] = values[pointRight];
pointRight++;
}
nowMerging++;
} while (pointLeft < midPoint && pointRight < rightEnd);
int fillFromPoint = pointLeft < midPoint ? pointLeft : pointRight;
System.arraycopy(values, fillFromPoint, mergeSpace, nowMerging,
intervalSize - nowMerging);
System.arraycopy(mergeSpace, 0, values, leftStart, intervalSize);
}
public static void mergeSort(int[] values) {
mergeSort(values, 0, values.length);
}
private static void mergeSort(int[] values, int start, int end) {
int intervalSize = end - start;
if (intervalSize < 2) {
return;
}
boolean isIntervalSizeEven = intervalSize % 2 == 0;
int splittingAdjustment = isIntervalSizeEven ? 0 : 1;
int halfSize = intervalSize / 2;
int leftStart = start;
int rightEnd = end;
int midPoint = start + halfSize + splittingAdjustment;
mergeSort(values, leftStart, midPoint);
mergeSort(values, midPoint, rightEnd);
merge(values, leftStart, midPoint, rightEnd);
}
}
Here is the reference solution from the textbook: (it's in C so I am adding the tag)
merge(item_type s[], int low, int middle, int high)
{
int i; /* counter */
queue buffer1, buffer2; /* buffers to hold elements for merging */
init_queue(&buffer1);
init_queue(&buffer2);
for (i=low; i<=middle; i++) enqueue(&buffer1,s[i]);
for (i=middle+1; i<=high; i++) enqueue(&buffer2,s[i]);
i = low;
while (!(empty_queue(&buffer1) || empty_queue(&buffer2))) {
if (headq(&buffer1) <= headq(&buffer2))
s[i++] = dequeue(&buffer1);
else
s[i++] = dequeue(&buffer2);
}
while (!empty_queue(&buffer1)) s[i++] = dequeue(&buffer1);
while (!empty_queue(&buffer2)) s[i++] = dequeue(&buffer2);
}
Abstractly, a queue is just some object that supports the enqueue, dequeue, peek, and is-empty operations. It can be implemented in many different ways (using a circular buffer, using linked lists, etc.)
Logically speaking, the merge algorithm is easiest to describe in terms of queues. You begin with two queues holding the values to merge together, then repeatedly apply peek, is-empty, and dequeue operations on those queues to reconstruct a single sorted sequence.
In your implementation using arrays, you are effectively doing the same thing as if you were using queues. You have just chosen to implement those queues using arrays. There isn't necessarily "better" or "worse" than using queues. Using queues makes the high-level operation of the merge algorithm clearer, but might introduce some inefficiency (though it's hard to say for certain without benchmarking). Using arrays might be slightly more efficient (again, you should test this!), but might obscure the high-level operation of the algorithm. From Skienna's point of view, using queues might be better because it makes the high-level details of the algorithm clear. From your point of view, arrays might be better because of the performance concerns.
Hope this helps!
You're worrying about minor constant factors which are largely down to the quality of your compiler. Given that you seem to be worried about that, arrays are your friend. Below is my C# implementation for integer merge-sort which, I think, is close to as tight as you can get. [EDIT: fixed a buglet.]
If you want to do better in practice, you need something like natural merge-sort, where, instead of merging up in powers of two, you simply merge adjacent non-decreasing sequences of the input. This is certainly no worse than powers-of-two, but is definitely faster when the input data contains some sorted sequences (i.e., anything other than a purely descending input sequence). That's left as an exercise for the student.
int[] MSort(int[] src) {
var n = src.Length;
var from = (int[]) src.Clone();
var to = new int[n];
for (var span = 1; span < n; span += span) {
var i = 0;
for (var j = 0; j < n; j += span + span) {
var l = j;
var lend = Math.Min(l + span, n);
var r = lend;
var rend = Math.Min(r + span, n);
while (l < lend && r < rend) to[i++] = (from[l] <= from[r] ? from[l++] : from[r++]);
while (l < lend) to[i++] = from[l++];
while (r < rend) to[i++] = from[r++];
}
var tmp = from; from = to; to = tmp;
}
return from;
}

Categories

Resources