Here is the simplified version of Bubble Sort, a very famous (but inefficient) sorting algorithm:

In [1]:
def bubble_sort(L):
    for i in range(len(L)):
        for j in range(len(L)-1):
            if L[j] > L[j+1]:
                L[j], L[j+1] = L[j+1], L[j]

Let's try running this algorithm (adding output along the way)

In [2]:
def bubble_sort(L):
    for i in range(len(L)):
        for j in range(len(L)-1):
            if L[j] > L[j+1]:
                L[j], L[j+1] = L[j+1], L[j]
                print("Swapped", L[j], "and", L[j+1])
                print(L)
            else:
                print("No need to swap", L[j], "and", L[j+1])
                print(L)

        print("====================================")
if __name__ == '__main__':
    L = [5, 3, 2, 4, 1]
    bubble_sort(L)
Swapped 3 and 5
[3, 5, 2, 4, 1]
Swapped 2 and 5
[3, 2, 5, 4, 1]
Swapped 4 and 5
[3, 2, 4, 5, 1]
Swapped 1 and 5
[3, 2, 4, 1, 5]
====================================
Swapped 2 and 3
[2, 3, 4, 1, 5]
No need to swap 3 and 4
[2, 3, 4, 1, 5]
Swapped 1 and 4
[2, 3, 1, 4, 5]
No need to swap 4 and 5
[2, 3, 1, 4, 5]
====================================
No need to swap 2 and 3
[2, 3, 1, 4, 5]
Swapped 1 and 3
[2, 1, 3, 4, 5]
No need to swap 3 and 4
[2, 1, 3, 4, 5]
No need to swap 4 and 5
[2, 1, 3, 4, 5]
====================================
Swapped 1 and 2
[1, 2, 3, 4, 5]
No need to swap 2 and 3
[1, 2, 3, 4, 5]
No need to swap 3 and 4
[1, 2, 3, 4, 5]
No need to swap 4 and 5
[1, 2, 3, 4, 5]
====================================
No need to swap 1 and 2
[1, 2, 3, 4, 5]
No need to swap 2 and 3
[1, 2, 3, 4, 5]
No need to swap 3 and 4
[1, 2, 3, 4, 5]
No need to swap 4 and 5
[1, 2, 3, 4, 5]
====================================

What happens here in general? We go through the list in "sweeps," and swap elements that are out of order. We repeat the "sweeps.'

The name Bubble Sort is due to the fact that numbers "bubble up" to the top of the list. In particular, notice how 5 will always end up at the end of the list after the first "sweep."

Once 5 starts being compared to other numbers, it "bubbles up" always, since it's the largerst number.

Notice that after the second sweep, the second-largest element (here, 4) will be at L[-2], and after the third sweep, the third-largest element (here, 3) will always be at L[-3]. This suggests that we don't have to run the inner loop for n-1 times every time.

Notice also that we can stop the algorithm once no changes are made during a sweep, since that means that no further changes will ever be made. (Because the list is sorted!)

Here is the final version of Bubble Sort.

In [3]:
def bubble_sort(L):
    for i in range(len(L)):
        #Set swapped to False. If nothing changes during a pass, we break.
        #If that happens, the runtime is better than the worst-case runtime
        #for a list of size n
        swapped = False
        
        #in the worst case, we need n-1 passes
        #The worst case happens when the smallest element is at L[-1], since
        #the smallest element only shifts one position to the left with each pass
        for j in range(len(L)-1-i):
            if L[j] > L[j+1]:
                L[j], L[j+1] = L[j+1], L[j]
                swapped = True
        if not swapped:
            break