Reputation: 796
I wrote two functions maxSubSum2
and maxSubSum3
, both of which try to find the maximum continuous sum of a sub-sequence in a given sequence.
maxSubSum2()
i
on each iteration.i
it sets a corresponding ending marker j
.int maxSubSum2(const std::vector<int> &v)
{
int maxSum = 0;
for(std::size_t begin = 0; begin < v.size(); ++begin)
{
int thisSum = 0;
for(std::size_t end = begin; end < v.size(); ++end)
{
thisSum += v[end];
if(thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}
maxSubSum3()
maxSumRec()
.maxSumRec
uses divide-and-conquer to calculate the maximum sub-sequence sum.center
, and the maximum sum in right part which also includes its border: center + 1
.int maxSubSum3(const std::vector<int> &v)
{
return maxSumRec(v, 0, v.size() - 1);
}
int maxSumRec(const std::vector<int> &v, std::vector<int>::size_type left, std::vector<int>::size_type right)
{
if(left == right)
if(v[left] > 0)
return v[left];
else
return 0;
std::vector<int>::size_type center = (left + right) / 2;
int maxLeftSum = maxSumRec(v, left, center);
int maxRightSum = maxSumRec(v, center + 1, right);
int maxLeftBorderSum = 0;
int leftBorderSum = 0;
for(std::vector<int>::size_type idx = center; idx < v.size(); --idx)
{
leftBorderSum += v[idx];
if(leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
int maxRightBorderSum = 0;
int rightBorderSum = 0;
for(std::vector<int>::size_type idx = center + 1; idx <= right; ++idx)
{
rightBorderSum += v[idx];
if(rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}
int max3(int n1, int n2, int n3)
{
if(n1 >= n2 && n1 >= n3) return n1;
if(n2 >= n1 && n2 >= n3) return n2;
if(n3 >= n1 && n3 >= n2) return n3;
return 0; // <--- Should never happen
}
Because maxSubSum2()
has a double nested for
loops, its time complexity needs to be O(N), and because maxSubSum3()
uses divide and conquer, its time complexity needs to be O(NlogN).
However, I created a simple running time calculation function stopwatch()
to measure the actual running time runtime
for each function. It looks like the following:
void stopwatch(int (*maxSubSumN)(const std::vector<int>&), const std::vector<int> &v)
{
std::chrono::time_point start = std::chrono::steady_clock::now();
maxSubSumN(v);
std::chrono::time_point end = std::chrono::steady_clock::now();
std::chrono::duration<double> runtime = end - start;
std::cout << std::fixed << std::setprecision(9) << std::left << std::setw(9) << runtime.count();
}
Before the program begins, I populate two vectors small
and big
with 1000 and 10000 randomly generated int
respectively; like this:
int randInt()
{
return std::rand() % 101 - 50;
}
void populate(std::vector<int> &v)
{
for(int &i : v)
i = randInt();
}
int main()
{
std::srand(std::time(NULL));
std::vector<int> small(1000);
std::vector<int> big(10000);
populate(small);
populate(big);
std::cout << "[OPTIMIZED BRUTE FORCE] \t: ";
stopwatch(maxSubSum2, small);
std::cout << std::endl;
std::cout << "[OPTIMIZED BRUTE FORCE] \t: ";
stopwatch(maxSubSum2, big);
std::cout << std::endl;
std::cout << "[DIVIDE AND CONQUER] \t\t: ";
stopwatch(maxSubSum3, small);
std::cout << std::endl;
std::cout << "[DIVIDE AND CONQUER] \t\t: ";
stopwatch(maxSubSum3, big);
std::cout << std::endl;
return 0;
}
However, after running the program several times, the execution time for both maxSubSum2
and maxSubSum3
is almost identical. Below are the results on my machine.
small.size() == 10 && big.size() == 100
[OPTIMIZED BRUTE FORCE] : 0.000001015
[OPTIMIZED BRUTE FORCE] : 0.000041509
[DIVIDE AND CONQUER] : 0.000001789
[DIVIDE AND CONQUER] : 0.000058110
small.size() == 100 && big.size() == 1000
[OPTIMIZED BRUTE FORCE] : 0.000042093
[OPTIMIZED BRUTE FORCE] : 0.003870208
[DIVIDE AND CONQUER] : 0.000053203
[DIVIDE AND CONQUER] : 0.003899243
small.size() == 1000 && big.size() == 10000
[OPTIMIZED BRUTE FORCE] : 0.002765456
[OPTIMIZED BRUTE FORCE] : 0.271172096
[DIVIDE AND CONQUER] : 0.002931273
[DIVIDE AND CONQUER] : 0.274476880
small.size() == 1000 && big.size() == 1000000
[OPTIMIZED BRUTE FORCE] : 0.002730444
[OPTIMIZED BRUTE FORCE] : 26.383375030
[DIVIDE AND CONQUER] : 0.002903615
[DIVIDE AND CONQUER] : 26.508168165
maxSumRec
it gives the right results though?Any help would be highly appreciated. For reference, I am learning from the book: Data Structures and Algorithms in C++ (4th Edition).
for(std::vector<int>::size_type idx = center; idx < v.size(); --idx)
It makes a difference because the loop is only supposed to go down to left but you made it go down to 0. This changes the recursive call's runtime to O(n) instead of O(right - left), and the total runtime to O(n^2) because there are a total of O(n) recursive calls
Credits: interjay
Upvotes: 2
Views: 179
Reputation: 110148
for(std::vector<int>::size_type idx = center; idx < v.size(); --idx)
This loop doesn't make sense. You're decreasing idx
while idx < v.size()
, which for an unsigned number is the same as decreasing it until it underflows past 0. That will both increase your asymptotic runtime, and possibly give incorrect results.
You may have intended idx >= left
, but that won't work for left == 0
due to using unsigned types, so you may want to use difference_type
instead of size_type
.
In any case, I suggest making sure that both algorithms give the same results if you haven't done so.
Upvotes: 2