JavaScript sorting, not just bubbles

Posted by ashly on Tue, 16 Jul 2019 19:25:07 +0200

I highly recommend you to read a book on gitBook.- Top Ten Classic Sorting Algorithms: https://sort.hust.cc/ In this paper, the motion chart and demonstration code are all in it.

Sorting is an inevitable requirement for programming. Front-end is no exception, although not many, but you will certainly encounter.

But when it comes to sorting, the easiest thing to think about is bubble sorting, selection sorting, insertion sorting.

Bubble sort

Compare the two adjacent elements in turn, if the latter is less than the former, then exchange, so that once from beginning to end, the maximum is placed at the end.

Once again from beginning to end, because each round, the last is the largest, so the need for comparison in the latter round can be less than the last one. Although you can still let him compare from beginning to end, the latter comparison is meaningless and useless. In order to be efficient, you should optimize the code.

The pictures are illustrated as follows:

Code implementation:

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // Two-to-two comparison of adjacent elements
                var temp = arr[j+1];        // Element Exchange
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

Selective Sorting

I think the choice of ranking is the simplest. When I was a freshman in VB, I just remembered this sort method. The principle is very simple: I always find a maximum or minimum ranking at the beginning.

  1. First, find the smallest (large) element in the unordered sequence and store it at the beginning of the ordered sequence.

  2. Then continue to find the smallest (large) element from the remaining unordered elements, and then put it at the end of the ordered sequence.

  3. Repeat the second step until all elements are sorted.

Motion Map Demonstration:

Code demonstration:

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     // Find the smallest number
                minIndex = j;                 // Save the smallest index
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

Insertion sort

Insertion sort is also relatively simple. Just like playing cards, you can insert the elements you get into the right place in turn.

  1. Consider the first element of the first sequence to be sorted as an ordered sequence, and the second element to the last element as an unordered sequence.

  2. Scanning the unordered sequence from beginning to end, inserting each element scanned into the appropriate position of the ordered sequence. (If the element to be inserted is equal to an element in an ordered sequence, the element to be inserted is inserted after the equivalent element.)

Motion Map Demonstration:

Code example:

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

The price of simplicity is inefficiency.

The above three are very simple sorting methods, simple at the same time, the efficiency will be relatively low, or take the comparison chart in this book to illustrate:

Time complexity is as high as O(n^2), while the time complexity of some sorting algorithms behind them is only O(n log n).

My obsessive-compulsive disorder is on the rise again. I want a more efficient method of sorting.

Merge Sort

Simply go through the content of this book, then understand the merger sort, so here we talk about the merger sort.

The basic principle is dividing and sorting.

The steps are as follows:

  1. The application space is the sum of two sorted sequences, which is used to store the merged sequences.

  2. Set two pointers, the initial position is the starting position of two sorted sequences;

  3. Compare the elements pointed by the two pointers, select the relatively small elements into the merge space, and move the pointer to the next position.

  4. Repeat step 3 until a pointer reaches the end of the sequence.

  5. Copy all the remaining elements of another sequence directly to the end of the merged sequence.

Motion Map Demonstration:

Code example:

function mergeSort(arr) {  // Using a top-down recursive approach
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

Since I am a person who loves tossing, I have to see the effect of tossing.

Efficiency testing

Because I learned this to sort not simple arrays, arrays are all objects, to sort an object's attributes, but also consider ascending and descending order.

So my code is implemented as follows:

/**
 * [Merge Sort]
 * @param  {[Array]} arr   [Array to sort]
 * @param  {[String]} prop  [Sort field, which is used when an array member is an object, is sorted according to one of its attributes. Simple array sorting ignores this parameter directly]
 * @param  {[String]} order [Ordering omitted or asc ascended or descended]
 * @return {[Array]}       [Sorted arrays, new arrays, not modifications on the original array]
 */
var mergeSort = (function() {
    // merge
    var _merge = function(left, right, prop) {
        var result = [];

        // Sort an attribute of a member in an array
        if (prop) {
            while (left.length && right.length) {
                if (left[0][prop] <= right[0][prop]) {
                    result.push(left.shift());
                } else {
                    result.push(right.shift());
                }
            }
        } else {
            // Direct Sorting of Group Members
            while (left.length && right.length) {
                if (left[0] <= right[0]) {
                    result.push(left.shift());
                } else {
                    result.push(right.shift());
                }
            }
        }

        while (left.length)
            result.push(left.shift());

        while (right.length)
            result.push(right.shift());

        return result;
    };

    var _mergeSort = function(arr, prop) { // Using a top-down recursive approach
        var len = arr.length;
        if (len < 2) {
            return arr;
        }
        var middle = Math.floor(len / 2),
            left = arr.slice(0, middle),
            right = arr.slice(middle);
        return _merge(_mergeSort(left, prop), _mergeSort(right, prop), prop);
    };

    return function(arr, prop, order) {
        var result = _mergeSort(arr, prop);
        if (!order || order.toLowerCase() === 'asc') {
            // Ascending order
            return result;
        } else {
            // Descending order
            var _ = [];
            result.forEach(function(item) {
                _.unshift(item);
            });
            return _;
        }
    };
})();

Which attribute needs to be sorted is uncertain and can be specified at will, so it is written as a parameter. Because you don't want these things to be judged in every loop, the code is a bit redundant.

On the issue of descending order, the parameters are not added, but simply ascending order and then reverse order output. The reason is that I don't want to let the condition be judged in every loop recursion, so I simply deal with it.

Here's the time to witness efficiency, a data simulation:

var getData = function() {
    return Mock.mock({
        "list|1000": [{
            name: '@cname',
            age: '@integer(0,500)'
        }]
    }).list;
};

Mock is used to simulate the data above, about Mock: http://mockjs.com/

The actual test is coming:

// Efficiency testing
var arr = getData();

console.time('Merge Sort');
mergeSort(arr, 'age');
console.timeEnd('Merge Sort');

console.time('Bubble sort');
for (var i = 0, l = arr.length; i < l - 1; ++i) {
    var temp;
    for (var j = 0; j < l - i - 1; ++j) {
        if (arr[j].age > arr[j + 1].age) {
            temp = arr[j + 1];
            arr[j + 1] = arr[j];
            arr[j] = temp;
        }
    }
}
console.timeEnd('Bubble sort');

Five times, the effect is as follows:

// Merge sort: 6.592 MS
 // Bubble sort: 25.959ms

// Merge sort: 1.334 MS
 // Bubble sort: 20.078ms

// Merge Sort: 1.085 MS
 // Bubble sort: 16.420ms

// Merge Sort: 1.200 MS
 // Bubble sorting: 16.574 MS

// Merge sort: 2.593ms
 // Bubble sort: 12.653ms

The difference of efficiency between the lowest 4 times and the highest 16 times is satisfactory.

Although 1000 pieces of data are unlikely to be sorted by the front end, there are still dozens of hundreds. In addition, because of node, JavaScript can also run on the server side, this efficiency improvement is also useful.

A little doubt

Recursion is used in merge sort. In "Description of Data Structure and Algorithms JavaScript", the author gives a bottom-up iteration method. But for the recursive method, the author thinks that:

However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle.

However, this approach is not feasible in JavaScript because the recursive depth of the algorithm is too deep for it.

The author of this book on gitbook has doubts about it, and I have doubts about it.

Recursion is used in merging, but it is placed after return. Regarding the recursion after renturn is tailed recursive optimization.

Tail recursive optimization refers to: if the original outer function calls another function inside, because the outer function needs to wait for the return of the inner function before returning the result, after entering the inner function, the information of the outer function must be remembered in memory, that is, the call stack. When the inner function is placed after the return keyword, it means that the outer function is over. After entering the inner function, it is not necessary to remember all the information in the outer function.

The above is a description of my understanding. I don't know if it's accurate. You can turn on tail recursive optimization under chrome, and I don't think this recursion should affect his use in JavaScript.

Last

If you are interested, you can recommend some more efficient ways to read the book and sort it.

However, it should be noted that these efficient sorting methods generally require a relatively large amount of extra memory space, which needs to be weighed.

In addition, very small-scale data is unnecessary. First, the impact is too small, but the efficiency of our people, a minute from the beginning to write a bubble, selection, insertion of the sort method, and instead of merger sort?

The original was published on my blog JavaScript sorting, not just bubbles Welcome to visit!

Topics: Javascript Attribute less Programming