简体   繁体   中英

Big O - Bubble sort

I wrote two different versions of the Bubble Sort algorithm- bubbleSort , the traditional version of the algorithm you'd see in textbooks, and sortIntArray , which is very similar to bubbleSort but is recursive.

Perhaps this is my misunderstanding, but the latter algorithm calling itself makes me think that the efficiency of the algorithm is different than bubble sort. Can someone explain to me the difference between these two, if any?

private static int[] sortIntArray(int[] arr) {

    for(int i=0; i<arr.length-1; i++) {
        if(arr[i+1]<arr[i]) { // [i-2], [i-1], [i], [i+1], [i+2]
            printArr(arr);
            int temp = arr[i];
            arr[i] = arr[i+1];
            arr[i+1] = temp;
            sortIntArray(arr);
        }
    }
    return arr;
}

private static int[] bubbleSort(int[] arr) {

    boolean swap = false;
    while(!swap) {
        swap = true;
        for(int i = 0; i<arr.length-1; i++) {
            if(arr[i+1]< arr[i]) {
                printArr(arr);
                swap = false;
                int temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
            }
        }
    }
    return arr;
}

/**
 * Returns an unordered integer array.
 * @return
 */
private static int[] getIntArray() {
    return new int[]{54, 899, 213, 2, 43, 8, 12, 11, 111, 43, 6, 44, 83, 3458};
}

Actually, the new implementation seems less efficient. The traditional bubble sort compares each pair of adjacent element in each pass, and essentially filters out the largest unsorted element to the end of the unsorted part in array in each pass. The passes comprise the outer loop.

Your functions emulates the following iterative version(which is effectively an insertion sort):

for (int i = 1; i < a.size(); ++i) {
    for (int j = i; j >= 1; --j) {
        if (a[j] < a[j - 1]) {
            int tmp = a[j];
            a[j] = a[j - 1];
            a[j - 1] = tmp;
        }
        else {
            break;
        }
    }
}

Since you're calling the recursive function at each swap, with the same array and the recursive call starts processing at the same index, you're essentially simulating an additional loop, (that can charged to sorting the encountered inversion from the beginning to that point) using the recursive calls.

Think of it like this: For each inversion you encounter, you have a recursive call. If during any point in the loop, you have an initial sorted part of the array, for example, let this function call be f_1
2 3 7. 1 4 5 6

Where the . indicates that the array is sorted upto that point. Now note that when the loop reached 7 and 1, it swaps and recursively calls the function, and this repeats, so that 1 eventually ends up at the front(let's say the call in which this happens is f_2 ), and the array becomes:

1 2 3 7. 4 5 6

Note that f_2 is at a more depth than f_1 . At this point, f_2 does not return, but the loop proceed forward from 1, which effectively emulates the f_1 , but which a larger part sorted, which mean that when the call to f_2 returns, the whole array will be sorted. And hence, the additional recursive calls before f_2 do nothing more but take up stack space.

The space complexity of your algorithm is worst case O(n^2) , and the time complexity is O(n^3), which is less efficient than the original (For the time complexity, consider a reverse sorted array of size n which has n^2 inversions, thus n^2 recursive calls, each of which traverses the full array, thus O(n^3) ).

I added a static counter variable which I incremented with each pass of the innermost loop in each of your methods. So for sortIntArray, an excerpt with the counter would be:

for(int i=0; i<arr.length-1; i++) {
        o++;
        if(arr[i+1]<arr[i]) { // [i-2], [i-1], [i], [i+1], [i+2]
            ...

I also implemented a different method which was essentially the Bubble sort provided in Wikipedia, and I changed your random array generator to produce arrays of different sizes:

private static int[] getIntArray() {
    int[] rand;
    if (MAX_SIZE - MIN_SIZE > 0) {
        rand = new int[new Random().nextInt(MAX_SIZE - MIN_SIZE) + MIN_SIZE + 1];
    } else {
        rand = new int[MIN_SIZE];
    }
    for (int i = 0; i < rand.length; i++) {
        rand[i] = new Random().nextInt(rand.length * 2);
    }
    return rand;
}

Here is an example result when the list is size 50. If you build the test code yourself you will see that not only is the Wikipedia implementation guaranteed to sort, but it also provides a good benchmark:

Unsorted: [52, 48, 62, 47, 42, ...]
n: 50
Bubble sort: [3, 4, 6, 6, 11, ...]
O(n): 1960
Wikipedia sort: [3, 4, 6, 6, 11, ...]
O(n): 2450
Recursive sort: [3, 4, 6, 6, 11, ...]
O(n): 27391

Interestingly, it can be clearly seen that the recursive sort is performing more iterations that the other sorting algorithms. Based on data from 10,000 sorts, which I dumped to a CSV it also would appear that for a max size of 30 the coefficient of the n^2 term is apx. 11.3 for a strictly order 2 polynomial fit, whereas for a max size of 50 it increased to roughly 19, which indicates that there is no such m such that m * n^2 is an upper bound for the runtime complexity of your algorithm.

tl;dr: Empirically, the recursive algorithm is worse than O(n^2) in time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM