Counting basic operations in Quicksort algorithm - c++

I am trying to count the amount of basic operations done by Hoare's quicksort algorithm. I am wondering if I placed the counter in the correct position. My assignment is to count the number of basic operations on 100 randomly generated arrays of sizes 10,100,1k, and 10k.
Here is the algorithm with the counter placed (line 6):
void QuickSort(int* array, int startIndex, int endIndex, int &counter) {
int pivot = array[startIndex]; //pivot element is the leftmost element
int splitPoint;
if (endIndex > startIndex)
{
counter++; //counting
splitPoint = SplitArray(array, pivot, startIndex, endIndex);
array[splitPoint] = pivot;
QuickSort(array, startIndex, splitPoint - 1, counter); //Quick sort first half
QuickSort(array, splitPoint + 1, endIndex, counter); //Quick sort second half
}
}
void swap(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}
int SplitArray(int* array, int pivot, int startIndex, int endIndex) {
int leftBoundary = startIndex;
int rightBoundary = endIndex;
while (leftBoundary < rightBoundary)
{
while (pivot < array[rightBoundary]
&& rightBoundary > leftBoundary)
{
rightBoundary--;
}
swap(array[leftBoundary], array[rightBoundary]);
while (pivot >= array[leftBoundary]
&& leftBoundary < rightBoundary)
{
leftBoundary++;
}
swap(array[rightBoundary], array[leftBoundary]);
}
return leftBoundary;
}
Do these results make sense?
Array[Amount] Array[10] Array[100] Array[1k] Array[10k]
MAX: 8 72 682 7122
MIN: 5 63 653 7015
AVERAGE: 6.36 66.54 667.87 7059.41
Or did I put the counter in the wrong place.

The counter is in the wrong place. Your assignment is to count basic operations. What is a basic operation in sorting? Typically we count the number of compare operations to measure complexity of sort.
We know that QuickSort is O(N Log N) on average where N is the number of items being sorted, while worst case it is O(N^2).
Your numbers are smaller than N, which is not possible since each of the N items must be compared to some other elemnt at least once, the cost of sorting cannot be less than N (otherwise at least one element was not compared to anything, so you could not guaranteed that it is sorted).
In your algorithm, the compare operation occurs when you compare the elements of the array to the pivot value. So increment your counter every time you compare an array element to Pivot. Your measured numbers should be at least N, and will typically be about N*log N, and rarely be be close to N^2.
See the suggested points below in SplitArray where to increment counter:
void QuickSort(int* array, int startIndex, int endIndex, int &counter) {
int pivot = array[startIndex]; //pivot element is the leftmost element
int splitPoint;
if (endIndex > startIndex)
{
// counter++; // Don't count here
splitPoint=SplitArray(array, pivot, startIndex, endIndex, counter);
array[splitPoint] = pivot;
QuickSort(array, startIndex, splitPoint - 1, counter); //Quick sort first half
QuickSort(array, splitPoint + 1, endIndex, counter); //Quick sort second half
}
}
No change to swap:
void swap(int &a, int &b) {
int temp;
temp = a;
a = b;
b = temp;
}
SplitArray does the comparisons, so the counter should be incremented here:
int SplitArray(int* array,int pivot,int startIndex,int endIndex,int &counter) {
int leftBoundary = startIndex;
int rightBoundary = endIndex;
while ((++counter) && (leftBoundary < rightBoundary))
{
while (pivot < array[rightBoundary]
&& rightBoundary > leftBoundary)
{
rightBoundary--;
}
swap(array[leftBoundary], array[rightBoundary]);
while ((++counter) && (pivot >= array[leftBoundary])
&& leftBoundary < rightBoundary)
{
leftBoundary++;
}
swap(array[rightBoundary], array[leftBoundary]);
}
return leftBoundary;
}

Related

C++ Counting Quicksort Comparisons <Unsolved>

I am working on a project that asks us to implement different sorts and add counter variables to measure the runtime with different array sizes.
My problem is that the output is not the same as the expected output
I already completed the insertion sort and correctly counts the number of comparisons.
I am allowed to use reference parameter.
Any feedback is appreciated.
Output:[Updated from Answer]
Quick sort 16 384 6401 74378
Expected Output:
Array Size: 10 100 1000 10000
--------------------------------------------------------------------
Quick Sort 35 630 10292 132882
Whats inside the contents of the array for array size 10:
The contents stay Constant with the used seed.
Insertion sort
[ 935, 942, 697, 299, 382, 579, 408, 181, 366, 505 ] //unsorted
[ 181, 299, 366, 382, 408, 505, 579, 697, 935, 942 ] //sorted
Program: [Updated From Answer]
#include <iostream>
#include <stdlib.h>
#include <iomanip>
#include <cmath>
/******************************/
/* Start of Quick Sort Algorithm */
/******************************/
static const int MIN_SIZE = 10; // Smallest size of an array that quicksort will sort
/**
* Sorts the items in an array into ascending order.
*/
template<class ItemType>
int insertionSort(ItemType theArray[], int first, int last) {
int count = 0;
for (int unsorted = first + 1; unsorted <= last; unsorted++) {
ItemType nextItem = theArray[unsorted];
int loc = unsorted;
while ((loc > first) && (count++,theArray[loc - 1] > nextItem)) {
theArray[loc] = theArray[loc - 1];
loc--;
}
theArray[loc] = nextItem;
}
return count;
}
/**
* Arranges two specified array entries into sorted order by
* exchanging them, if necessary.
* */
template<class ItemType>
void order(ItemType theArray[], int i, int j) {
if (theArray[i] > theArray[j]) {
std::swap(theArray[i], theArray[j]);
}
}
/**
* Arranges the first, middle, and last entry in an array in sorted order.
*/
template<class ItemType>
int sortFirstMiddleLast(ItemType theArray[], int first, int last) {
int mid = first + (last - first) / 2;
order(theArray, first, mid); // Make theArray[first] <= theArray[mid]
order(theArray, mid, last); // Make theArray[mid] <= theArray[last]
order(theArray, first, mid); // Make theArray[first] <= theArray[mid]
return mid;
}
/**
* Partitions the entries in an array about a pivot entry for quicksort.
*/
template<class ItemType>
int partition(ItemType theArray[], int first, int last,int &counter) {
// Choose pivot using median-of-three selection
int pivotIndex = sortFirstMiddleLast(theArray, first, last);
// Reposition pivot so it is last in the array
std::swap(theArray[pivotIndex], theArray[last - 1]);
pivotIndex = last - 1;
ItemType pivot = theArray[pivotIndex];
// Determine the regions S1 and S2
int indexFromLeft = first + 1;
int indexFromRight = last - 2;
bool done = false;
while (!done) {
// Locate first entry on left that is >= pivot
while (theArray[indexFromLeft] < pivot) {
counter++;//I incremented Count here
indexFromLeft = indexFromLeft + 1;
}
// Locate first entry on right that is <= pivot
while (theArray[indexFromRight] > pivot) {
counter++;
indexFromRight = indexFromRight - 1;
}
if (indexFromLeft < indexFromRight) {
std::swap(theArray[indexFromLeft], theArray[indexFromRight]);
indexFromLeft = indexFromLeft + 1;
indexFromRight = indexFromRight - 1;
}
else {
done = true;
}
}
// Place pivot in proper position between S1 and S2, and mark its new location
std::swap(theArray[pivotIndex], theArray[indexFromLeft]);
pivotIndex = indexFromLeft;
return pivotIndex;
}
/**
* Sorts an array into ascending order. Uses the quick sort with
* median-of-three pivot selection for arrays of at least MIN_SIZE
* entries, and uses the insertion sort for other arrays.
*/
template<class ItemType>
int quicksort(ItemType theArray[], int first, int last) {
int result = 0;
int counter = 0;
if (last - first + 1 < MIN_SIZE) {
result = insertionSort(theArray, first, last);
}
else {
// Create the partition: S1 | Pivot | S2
int pivotIndex = partition(theArray, first, last,counter);
// Sort subarrays S1 and S2
result += quicksort(theArray, first, pivotIndex - 1);
result += quicksort(theArray, pivotIndex + 1, last);
result += counter;
}
return result; //return final count
}
/******************************/
/* Start of Sorting Benchmark */
/******************************/
int* makeRandomArray(int n, int seed) {
srand(seed);
int * a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = rand() % 1000;
}
return a;
}
int main(){
const int seed = 9000;
int *a;
/******************************/
/* Start of Quick Sort */
/******************************/
std::cout << "Quick sort";
int n = 10;
a = makeRandomArray(10, seed);
std::cout <<std::setw(13)<< quicksort(a, 0, n-1);
delete[] a;
n = 100;
a = makeRandomArray(100, seed);
std::cout <<std::setw(13)<< quicksort(a, 0, n-1);
delete[] a;
n = 1000;
a = makeRandomArray(1000, seed);
std::cout <<std::setw(13)<< quicksort(a, 0, n-1);
delete[] a;
n = 10000;
a = makeRandomArray(10000, seed);
std::cout <<std::setw(13)<< quicksort(a, 0, n-1)<<std::endl;
delete[] a;
return 0;
}
In quick sort, the comparisons are made when the partition method/function is looking for elements that break the rule that elements smaller than the pivot must be on the left of the pivot and elements larger than it must be on the right. These two whiles do exactly this. The first one keeps going to the right while the current element is smaller than the pivot (first set of comparisons). The second while do the opposite operation, i.e., it keeps going to the left while the current element is bigger than the pivot, doing the second set of comparisons. If the left index is smaller than the right index, it means that it found an element that is bigger than the pivot on the left and an element that is smaller than the pivot on the right, so it will swap them. The process continues, until the left index pass throught the right one. When it finishes, all the left values are smaller than the pivot and all the right values are bigger than the pivot, so it swaps the pivot with the last element of the smaller side and returns the pivot position, since now, the pivot is in the correct position and the "middle" of that partition was found, allowing the sorting process to continue to the left partition and to the right partition.
while (theArray[indexFromLeft] < pivot) {
// count here
indexFromLeft = indexFromLeft + 1;
}
while (theArray[indexFromRight] > pivot) {
// and here
indexFromRight = indexFromRight - 1;
}

How to count the amount of comparisons in a quick sort algorithm?

I have the following quick sort algorithm:
int quicksort(int data[], size_t n, int &counter)
// Library facilities used: cstdlib
{
size_t pivot_index; // Array index for the pivot element
size_t n1; // Number of elements before the pivot element
size_t n2; // Number of elements after the pivot element
if (n > 1)
{
// Partition the array, and set the pivot index.
partition(data, n, pivot_index, counter);
// Compute the sizes of the subarrays.
n1 = pivot_index;
n2 = n - n1 - 1;
// Recursive calls will now sort the subarrays.
quicksort(data, n1, counter);
quicksort((data + pivot_index + 1), n2, counter);
}
return counter;
}
void partition(int data[], size_t n, size_t& pivot_index, int &counter){
int pivot = data[0];
size_t too_big_index = 1;
size_t too_small_index = n - 1;
while (too_big_index <= too_small_index)
{
while (++counter && (too_big_index < n) && (data[too_big_index] <= pivot)) too_big_index++;
while (++counter && data[too_small_index] > pivot ) too_small_index--;
counter++;
if (too_big_index < too_small_index) swap(data[too_big_index], data[too_small_index]);
};
pivot_index = too_small_index;
data[0] = data[pivot_index];
data[pivot_index] = pivot;
}
I have added the three counter increments in the partition function, however the the counter comes out with a value of 32019997 when using a sorted array of 8000 elements, I am using a pivot of the left-most element (I know that gives me a terrible worst-case in terms of sorted array), unless I am incorrect, shouldn't the worst case be n^2 i.e 64000000? So I assume the way I am counting comparisons is wrong, but I am not sure how.

Why does the quick sort algorithm duration increase when the array has duplicate values?

I am trying to measure the duration for both Merge Sort and Quick Sort functions using std::chrono time calculations and using randomly generated arrays of integers within some range [A, B], the sizes of the arrays vary from 5000 to 100,000 integers.
The goal of my code is to prove that when the method of picking the (pivot) in quick sort is improved, the quick sort function ends up taking less time to process the array than merge sort, the way I pick the pivot is using the random index method to minimize the probability of having a complexity of (n^2), However in some cases which I will describe below, the quick sort ends up taking more time than merge sort and I would like to know why this occurs.
case 1:
The range of the numbers in the array is small which increases the probability of having duplicate numbers in the array.
case 2:
When I use a local IDE like clion, the quick sort function takes a lot more time than merge sort, however an online compiler like IDEONE.com gives similar results in both sorting algorithms (even when the range of the generated integers is small)
here are the results I got in the mentioned cases(the first row of numbers is merge sort results, the second row is quick sort results):
1-clion results narrow range of numbers (-100, 600)
2-clion results with a wide range of numbers (INT_MIN, INT_MAX)
3-IDEONE results with a narrow range of numbers (-100, 600)
4- IDEONE results with a wide range of numbers (INT_MIN, INT_MAX)
#include <bits/stdc++.h>
#include <chrono>
#include <random>
using namespace std;
mt19937 gen(chrono::steady_clock::now().time_since_epoch().count());
int* generateArray(int size)
{
int* arr = new int[size];
uniform_int_distribution<> distribution(INT_MIN, INT_MAX);
for (int i=0; i < size; ++i)
{
arr[i] = distribution(gen);
}
return arr;
}
void merge(int* leftArr, int nL, int* rightArr, int nR, int* mainArr)
{
int i=0, j=0, k=0;
while (i < nL && j < nR)
{
if (leftArr[i] < rightArr[j]) { mainArr[k++] = leftArr[i++]; }
else { mainArr[k++] = rightArr[j++]; }
}
while (i < nL){ mainArr[k++] = leftArr[i++]; }
while (j < nR){ mainArr[k++] = rightArr[j++]; }
}
void mergeSort (int* mainArray, int arrayLength)
{
if (arrayLength < 2) { return; }
int mid = arrayLength/2;
int* leftArray = new int[mid];
int* rightArray = new int[arrayLength - mid];
for (int i=0; i<mid; ++i) {leftArray[i] = mainArray[i];}
for (int i = mid; i<arrayLength; ++i) {rightArray[i - mid] = mainArray[i];}
mergeSort(leftArray, mid);
mergeSort(rightArray, arrayLength-mid);
merge(leftArray, mid, rightArray, arrayLength-mid, mainArray);
delete[] leftArray;
delete[] rightArray;
}
int partition (int* arr, int left, int right)
{
uniform_int_distribution<> distribution(left, right);
int idx = distribution(gen);
swap(arr[right], arr[idx]);
int pivot = arr[right];
int partitionIndex = left;
for (int i = left; i < right; ++i)
{
if (arr[i] <= pivot)
{
swap(arr[i], arr[partitionIndex]);
partitionIndex++;
}
}
swap(arr[right], arr[partitionIndex]);
return partitionIndex;
}
void quickSort (int* arr, int left, int right)
{
if(left < right)
{
int partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex-1);
quickSort(arr, partitionIndex+1, right);
}
}
int main()
{
vector <long long> mergeDuration;
vector <long long> quickDuration;
for (int i = 5000; i<= 100000; i += 5000)
{
int* arr = generateArray(i);
auto startTime = chrono::high_resolution_clock::now();
quickSort(arr, 0, i - 1);
auto endTime = chrono::high_resolution_clock::now();
long long duration = chrono::duration_cast<chrono::milliseconds>(endTime - startTime).count();
quickDuration.push_back(duration);
delete[] arr;
}
for (int i = 5000; i <= 100000; i += 5000 )
{
int* arr = generateArray(i);
auto startTime = chrono::high_resolution_clock::now();
mergeSort(arr, i);
auto endTime = chrono::high_resolution_clock::now();
long long duration = chrono::duration_cast<chrono::milliseconds>(endTime - startTime).count();
mergeDuration.push_back(duration);
delete[] arr;
}
for (int i = 0; i<mergeDuration.size(); ++i)
{
cout << mergeDuration[i] << " ";
}
cout << endl;
for (int i = 0; i<quickDuration.size(); ++i)
{
cout << quickDuration[i] << " ";
}
}
Quicksort is known to exhibit poor performance when the input set contains lots of duplicates. The solution is to use three-way partitioning as described on Wikipedia:
Repeated elements
With a partitioning algorithm such as the ones described above (even
with one that chooses good pivot values), quicksort exhibits poor
performance for inputs that contain many repeated elements. The
problem is clearly apparent when all the input elements are equal: at
each recursion, the left partition is empty (no input values are less
than the pivot), and the right partition has only decreased by one
element (the pivot is removed). Consequently, the algorithm takes
quadratic time to sort an array of equal values.
To solve this problem (sometimes called the Dutch national flag
problem), an alternative linear-time partition routine can be used
that separates the values into three groups: values less than the
pivot, values equal to the pivot, and values greater than the pivot.
... The values
equal to the pivot are already sorted, so only the less-than and
greater-than partitions need to be recursively sorted. In pseudocode,
the quicksort algorithm becomes
algorithm quicksort(A, lo, hi) is
if lo < hi then
p := pivot(A, lo, hi)
left, right := partition(A, p, lo, hi) // note: multiple return values
quicksort(A, lo, left - 1)
quicksort(A, right + 1, hi)
The partition algorithm returns indices to the first ('leftmost') and
to the last ('rightmost') item of the middle partition. Every item of
the partition is equal to p and is therefore sorted. Consequently, the
items of the partition need not be included in the recursive calls to
quicksort.
The following modified quickSort gives much better results:
pair<int,int> partition(int* arr, int left, int right)
{
int idx = left + (right - left) / 2;
int pivot = arr[idx]; // to be improved to median-of-three
int i = left, j = left, b = right - 1;
while (j <= b) {
auto x = arr[j];
if (x < pivot) {
swap(arr[i], arr[j]);
i++;
j++;
} else if (x > pivot) {
swap(arr[j], arr[b]);
b--;
} else {
j++;
}
}
return { i, j };
}
void quickSort(int* arr, int left, int right)
{
if (left < right)
{
pair<int, int> part = partition(arr, left, right);
quickSort(arr, left, part.first);
quickSort(arr, part.second, right);
}
}
Output:
0 1 2 3 4 5 6 7 8 9 11 11 12 13 14 15 16 19 18 19
0 0 0 1 0 1 1 1 1 1 2 3 2 2 2 2 3 3 3 3
0 1 2 3 4 5 6 6 8 8 9 12 11 12 13 14 16 17 18 19
0 0 1 1 1 2 3 3 3 4 4 4 5 6 5 6 7 7 8 8
So, the run with lots of duplicates is now much faster.
Why does the quick sort algorithm duration increase when the array has duplicate values?
This is only true if using Lomuto type partition scheme, where duplicate values cause the splitting to get worse.
If using Hoare partition scheme, the algorithm duration generally decreases when the array has duplicate values, because the splitting gets closer to the ideal case of splitting exactly in half and the improved splitting compensates for the extra swaps on a typical system with memory cache.

QUICK SORT stack overflow c++ big numbers

good day
I am trying to use quick sort with 10000 numbers but it is giving me stack overflow error. it works with random numbers but it does not with descending and ascending numbers.
'
thank you
void quickSort(long* array, long start, long last)
{
if (start < last)
{
int p = partition(array, start, last);
quickSort(array, start, p-1);
quickSort(array, p + 1, last);
}
}
int partition(long* array, long start, long last)//first partition
{
int j = start + 1;
for (long i = start + 1;i <= last;i++)
{
if (array[i] < array[start])
{
swap(array[i], array[j]);
j++;
}
}
swap(array[start], array[j - 1]);
return j - 1;
}
'
For sorted elements, you can avoid this problem by choosing the median of the three elements array[start], array[last] and array[(start + last + 1)/2] as your pivot value.
int median_of_3(long* array, long start, long last)
{
long a = (start + last + 1)/2, b = start, c = last;
if (array[c] < array[a]) swap(array[c], array[a]);
if (array[b] < array[a]) swap(array[b], array[a]);
if (array[c] < array[b]) swap(array[c], array[b]);
return partition(array, start, last);
}
An additional strategy to avoid a large stack depth is to calculate which partition is smaller, and recursively call the smaller one. The other partition can then be optimized into a loop (tail recursion optimization).
void quickSort(long* array, long start, long last)
{
if (start >= last) return;
int p = median_of_3(array, start, last);
int next_start[2] = { start, p + 1 };
int next_last[2] = { p - 1, last };
bool i = p > (start + last)/2;
quickSort(array, next_start[i], next_last[i]);
/*
* If the compiler does not optimize the tail call below into
* a loop, it is easy to do the optimization manually.
*/
quickSort(array, next_start[!i], next_last[!i]);
}
Introspection can also be used to avoid a large stack depth. You track your recursive call depth, and if it is "too deep", you fail safe into a different sorting strategy, like merge sort or heap sort. This is the behavior currently used by std::sort.
void introSortImpl(long* array, long start, long last, int depth)
{
if (--depth == 0) {
heapSort(array, start, last);
return;
}
if (start >= last) return;
int p = median_of_3(array, start, last);
int next_start[2] = { start, p + 1 };
int next_last[2] = { p - 1, last };
bool i = p > (start + last)/2;
introSortImpl(array, next_start[i], next_last[i], depth);
introSortImpl(array, next_start[!i], next_last[!i], depth);
}
void introspectionSort(long* array, long start, long last)
{
introSortImpl(array, start, last, log2(start - last) * 3);
}
the code is okay but your compiler uses stack very ineffectively. you just need to raise reserved stack amount. it happens much more often in debug profiles rather than release ones just because compiler preserves large stack chunks to check if stack was broken during execution of your procedure.
Example of Lomuto partition scheme like quicksort that uses recursion on the smaller partition, updates l or r, then loops back to split the larger partition into two partitions, repeating the process. Worst case stack space is O(log2(n)) which should avoid stack overflow. Worst case time complexity is still O(n^2) (depending on how partition is implemented).
Some call this example a half recursion. It's not an example of tail recursion, since tail recursion means that the recursive function just returns after calling itself. The second call in the original question example is a tail call.
void quicksort(int * tab, int l, int r)
{
int q;
while(l < r)
{
q = partition(tab, l, r);
if(q - l < r - q) { // recurse into the smaller partition
quicksort(tab, l, q - 1);
l = q + 1;
} else {
quicksort(tab, q + 1, r);
r = q - 1;
}
} // loop on the larger partition
}

Meximum Subarray Sum using Divide-And-Conquer

Working on an implementation of finding the Greatest Contiguous Sum of a sequence using the Divide and Conquer method as seen here.
My return value is often incorrect.
For example:
{5, 3} returns 5 instead of 8.
{-5, 3} returns 0 instead of 3.
{ 6, -5, 7 } returns 7 instead of 8.
Other notes:
decrementing or incrementing AT the first or last iterators throws an exception, saying that I either can't increment, decrement, or dereference at that point. There's a bug somewhere in GCSMid, I think, but I haven't been able to solve it.
this implementation uses random-access iterators, signified as RAIter
//function max- finds greatest number given 3 size_ts
size_t max(size_t a, size_t b, size_t c)
{
if (a >= b && a >= c)
{
return a;
}
else if (b >= a && b >= c)
{
return b;
}
else
{
return c;
}
}
//function gcsMid
//main algorithm to find subsequence if it spans across the center line
template<typename RAIter>
size_t gcsMid(RAIter first, RAIter center, RAIter last)
{
size_t sum = 0;
size_t leftSum = 0;
size_t rightSum = 0;
//to the left of center
for (RAIter i = center; i > first; i--)
{
sum += *i;
if(sum > leftSum)
{
leftSum = sum;
}
}
//to right of center
sum = 0;
for (RAIter j = (center + 1); j < last; j++)
{
sum += *j;
if (sum > rightSum)
{
rightSum = sum;
}
}
//return the sums from mid
return leftSum + rightSum;
}
//main function to call
template<typename RAIter>
int gcs(RAIter first, RAIter last)
{
size_t size = distance(first, last);
//base case is when the subarray only has 1 element. when first == last
if (first == last || size == 1)
{
if (size < 1)
{
return 0;
}
if (*first < 0)
{
return 0;
}
return *first;
}
//middle point
RAIter center = first + (size/2);
//return max of leftsum, rightsum, and midsum
return max(gcs(first, center),
gcs(center + 1, last),
gcsMid(first, center, last));
}
You have two problems with your code:
A. This loop:
for (RAIter i = center; i > first; i--)
does not include first in the loop. The reference algorithm does. You can't just use >= as the reference algorithm does as it doesn't work for iterators. Either add an extra bit of code to check first at the end, or change your loop so it somehow includes first (maybe a do while loop would suit better).
B. These definitions:
size_t sum = 0;
size_t leftSum = 0;
size_t rightSum = 0;
should not be size_t as size_t is unsigned. This means that when the sum goes negative, checks like if(sum > leftSum) no longer work, as the negative value (which underflows) is bigger than the positive value.
Change them to int.
The best way to find these kinds of errors is to run the code through a debugger. You can then step through each line of your code and see what the variable values are. This makes it easy to spot things like negative numbers becoming large positive numbers as above.