插入排序

排序是最基本的算法,大量数据的处理,都要求是排好序的数据。人们研究了很多种不同的算法,各有优势和代价。我们自己编写过最简单的排序算法:冒泡排序。现在介绍简单而常用的几种。
插入排序几乎人人都会用到,我们玩扑克牌的时候会不自觉地用这个算法。我们看一看这个过程。
刚开头,你左手上是空的,一张牌都没有。

轮到你摸牌了,摸了一个5,你把它交到左手上,好现在左手上的牌是5。

再摸一张牌,摸了一个8,你把它交到左手上,看到左手有了一张牌为5,你心里想了一下,8比5大,放到5后面吧。好,你现在左手上的牌是5,8。

又摸一了张牌,摸了一个3,你把它交到左手上,看到左手有了牌为5,8,你心里想了一下,3比5小,放到5前面吧。好,你现在左手上的牌是3,5,8。

又摸一了张牌,摸了一个6,你把它交到左手上,看到左手有了牌为3,5,8,你心里想了一下,6比3和5大,比8小,放到5和8之间吧。好,你现在左手上的牌是3,5,6,8。
… …
我们大部分人玩牌的时候都是不自觉的这么整理手上的牌的。这个整理办法简单地说就是每次都往一个已经排好序的数列里面再放入一个新数。
我们先用简单办法,拿出一个数,然后跟它之前排好序的数列里挨个比较,放到合适的位置:

class SimpleInsertSorter():
    def sort(arr):
        for j in range(1,len(arr)):
            k=j
            while arr[k]<arr[k-1] and k>0: #找这个数据之前的数据,插入合适位置
                arr[k],arr[k-1]=arr[k-1],arr[k]
                k-=1

测试一下:

a=[5,3,8,23,11,10,9,35,12,11,34,10,2,1,25,18,29]
SimpleInsertSorter.sort(a)
print(a)

简单插入排序的性能不高,需要逐个扫描,每一个还要循环处理,所以是O(n2)。
我们可以稍微改进一下,尽然某个数前面的序列已经排好序了,那么就不用一个一个找了,改成二分查找就可以了。找打这个位置后,要插入新数据,然后把别的数据挪动一个位置。
改进型插入排序的性能分析有点复杂,对N个数,每一次往排好序的数列里面查找合适的位置需要的次数为O(log2N),但是平均又需要移动位置N/2次,所以整个算法最坏的的情况是O(N2)次。还是跟冒泡排序一个级别的。不适合大数据的情况。
看程序如何实现,我们先写一个函数,把一个值放到一个已经排序的列表中,保持排序:

def sort(newarr, value):
    length=len(newarr)
    low=0
    high=length-1
    idx = (low + high) // 2
    while high>low: #二分查找
        if value>newarr[idx]:
            low = idx+1
            if high<low:
                break
            idx = (low + high) // 2
        elif value<newarr[idx]:
            high = idx-1
            if high<low:
                break
            idx = (low + high) // 2
        else :
            break;

    newarr.append(0) #增加一个位置

#跳出二分循环后,idx指向的是离value最近的那个位置。
    if newarr[idx]<value  and idx==length-1: #最后一个位置
        newarr[len(newarr)-1]=value
    elif newarr[idx]<value  and idx!=length-1: #中间位置,大,放后面
        for i in range(0,length-idx-1):
            newarr[len(newarr)-i-1]=newarr[len(newarr)-i-2]
        newarr[idx+1]=value
    else: #中间位置,小,放前面
        for i in range(0,length-idx):
            newarr[len(newarr)-i-1]=newarr[len(newarr)-i-2]
        newarr[idx]=value

    print(newarr)
    return newarr

因为newarr是一个已经排好序的列表,所以我们用二分查找,找最接近于value的位置,之后增加一个格,移动大于value的那些值,再把value放到合适的位置。
有了这个函数,排序就简单了:

def insertsort(arr):
    newarr=[]
    for i in arr:
        sort(newarr, i)

讲列表中的元素一个一个放到排序列表中就可以了。
测试一下:

a=[5,8,3,6,10,9,2,7]
print(a)
insertsort(a)

运行结果:

[5, 8, 3, 6, 10, 9, 2, 7]
[5]
[5, 8]
[3, 5, 8]
[3, 5, 6, 8]
[3, 5, 6, 8, 10]
[3, 5, 6, 8, 9, 10]
[2, 3, 5, 6, 8, 9, 10]
[2, 3, 5, 6, 7, 8, 9, 10]