Search code examples
javascriptalgorithmmergesort

Why is this implementation of merge sort of 1,000,000 taking so long?


I'm testing it with 1,000,000 numbers, and it's just kind of hanging. I thought it would breeze through 1,000,000 easily. Is it my implementation? I have a feeling it's because of the slice(), anyone have an idea?

Edit: Just got this message: FATAL ERROR: CALL_AND_RETRY_2 Allocation failed - process out of memory

TopDownSplitMerge(numbersArray);
function TopDownSplitMerge(arrayOfNumbers) {     
    var length = arrayOfNumbers.length
    var middleIndex = parseInt(length/2);

    if(length <= 1) {
        return arrayOfNumbers;
    }                       

    // Split left side
    var left = TopDownSplitMerge(arrayOfNumbers.slice(0, middleIndex));  

    // Split right side
    var right = TopDownSplitMerge(arrayOfNumbers.slice(middleIndex, length));   

    // Merge every back together
    return TopDownMerge(left, right);
}

function TopDownMerge(left, right) {
    var results = []

    while(left.length || right.length) {
        console.log("looping...");

        // Check if both sides are NOT empty, if so, then just finish shifting the non-empty side
        if(left.length && right.length) { 
            if(left[0] <= right[0]) {
               results.push(left.shift()) 
            } else {
               results.push(right.shift()) 
            }
        } else if(left.length) {
           results.push(left.shift()) 
        } else {
           results.push(right.shift()) 
        }

    }

    console.log("Merging....", results.length);
    return results;
}

Solution

  • There are two things I had to change

    var right = TopDownSplitMerge(arrayOfNumbers.slice(middleIndex, length));
    
    ....
    ....
    ....
    
    function TopDownMerge(left, right) {
        var results = [], leftLen = left.length, rightLen = right.length;
    
        for (var i = 0, j = 0; i < leftLen || j < rightLen;) {
            if (i < leftLen && j < rightLen) {
                if(left[i] <= right[j]) {
                    results.push(left[i]);
                    i += 1;
                } else {
                    results.push(right[j]);
                    j += 1;
                }
            } else if (i < leftLen) {
                results.push(left[i]);
                i += 1;
            } else {
                results.push(right[j]);
                j += 1;
            }
        }
        return results;
    }
    

    Edit: Now I changed it to accept indices instead of sliced arrays and it boosts the performance more.

    function TopDownSplitMerge(arrayOfNumbers, start, end) {
        var length = end - start;
        var middleIndex = start + parseInt(length / 2);
    
        if (length <= 1) {
            return [arrayOfNumbers[start]];
        }
    
        // Split left side
        var left = TopDownSplitMerge(arrayOfNumbers, start, middleIndex);
    
        // Split right side
        var right = TopDownSplitMerge(arrayOfNumbers, middleIndex, length);
    
        // Merge every back together
        return TopDownMerge(left, right);
    }
    TopDownSplitMerge(numbersArray, 0, numbersArray.length);
    

    Jsperf: http://jsperf.com/so-q-19341534

    jsperf for my solution with 10,000,000 numbers: http://jsperf.com/solution-to-so-q-19341534