2 算法基础

2.1插入排序

原理介绍

用扑克牌作为例子,我们左手为空并且桌子上的牌面向下。然后,每次从桌子上拿出一张牌并将它插入到左手中正确的位置。为了找到正确位置,需要从右到左将它与手中的牌做比较

思想

A= [3,2,4,6,7,1]

网上关于排序算法的资料非常多,但是要真正理解为自己的东西,哪怕实现的再简陋。
插入排序是将序列分为有序和无序两个部分,有序部分为A[0],无序部分A[1:6 ],我们要做的就是用两个循环,将无序部分插入到有序部分,当然,插入的时候涉及判断和移位

实现

本文的内容更像是比较for和while的不同之处,下面是python用for实现的算法

def insertion_sort_sub(A):
    for i in range(1,len(A)):
        key = A[i]    
        flag = 0  #设置标志位,在下面的range循环中判断到底有没有-1,如果有,置0,否则置1
        for j in range(i-1,-1,-1):
            if key < A[j]:
                A[j + 1] = A[j]
            else:
                flag = 1
                break
        if flag == 0:  
            A[j] = key
        else:
            A[j + 1] =key
    return A

用while实现

def insertion_sort_sub(A):
    i = 1
    while i < len(A):
        key=A[i]
        j = i - 1
        flag = 0
        while j > -1:
            if key < A[j]:
                A[j+1]=A[j]
                j -= 1
            else:
                flag = 1
                break
        if flag == 0:
            A[j+1]=key
        else:
            A[j] =key
    return A

下面用是while的变种

def insertion_sort(A):
    i = 1
    while  i < len(A):
        key = A[i]
        j = i - 1
        while key < A[j] and j > -1:
            if key < A[j]:
                A[j + 1] = A[j]
                j -= 1
        A[j + 1] = key
        i += 1
    return A

下面是for加上while

def insertion_sort(A):
    for i in range(1,len(A)):
        key = A[i]
        j = i - 1
        while key < A[j] and j > -1:
            if key < A[j]:
                A[j + 1] = A[j]
                j -= 1
        A[j + 1] = key
    return A

另一种思想

插入排序通常都是从右向左插入,如果是从左向右呢?

def insertion_sort_sub(A):
    for i in range(1,len(A)):
        key = A[i]
        for j in range(0,i-1):
            if key < A[j]:
                '''从j到i-1到要向后移动'''
                for k in range(j,i):
                    A[k + 1] = A[k]
        A[j + 1]  =key 
    return A

用while实现

def insertion_sort_sub(A):
    i = 1
    while i 

练习

2.1-1 以图2-2为模型,说明INSERATION-SORT在数组A={31,41,59,41,58}上的执行过程。
利用循环不变式
初始化:第一次循环迭代前,当j = 31时,子数组仅由单个元素构成,算法正确
保持:证明每次迭代,循环式保持不变,4-7行将A[ j - 1 ],A[ j - 2 ],A[ j - 3 ]等向右移动一个位置,直到找到合适的位置,第8行将A[ j ]的值插入到该位置,此时子数组还是由原来A[ 1 ... j ]中的元素构成,但是原A[ j ]元素已排序到其中。对for循环的下一次迭代增加j将保持循环不变式
终止:最后研究循环终止时发生了什么,导致for循环终止的条件是j > A.length。因为每次循环迭代j增加1,那么必有 j > A.length。因此:j 从 2到A.length都已排序,推断出整个数组都已排序,因此算法正确
2.1-2重写过程INSERTION-SORT,使之按非升序(降序)排序
def insertionSort(A):

    for i in range(1,len(A)):
        key = A[i]
        j = i - 1
        while j > -1 and key > A[j]:
            A[j + 1 ] = A[j]
            j -= 1  

        A[j + 1] = key
    return A
2.1-3考虑下面的查找问题:输入:一列数A=和一个值v。输出:下标i,使得A[i]与v相等,当v不出现在A中时,输出NIL。写出这个问题的线性查找的伪码,它顺序的扫描整个序列以找到v。利用LOOP VIARIANT证明其正确性。
def insertionSort(A,v):
    for i in range(0,len(A)):
        if v == A[i]:
            return i
    return None
A = [31,41,59,41,58]
初始化:在第一次迭代循环之前,i = 0,此时若v = A[i],返回i,否则返回None,算法正确
保持:for循环3-4行将A[i]与v比较,相同则返回i,否则for循环的下一次迭代增加i将保持循环不变式
终止:for循环的终止条件是i >=len(A),因此每次循环i = i + 1,条件必将成立,且若未在循环中找到i使得A[i] = v,将返回None。符合算法要求

2.2分析算法

分析一下插入排序需要的执行时间,时间由两个概念构成,每一个步骤代价及次数。

def insertion_sort(A):                    #代价      次数     
    for i in range(1,len(A)):             #c1         n
        key = A[i]                        #c2         n-1
        j = i - 1                         #c3         n-1
        while key < A[j] and j > -1:      #c4         Σ[1-n]tj
            if key < A[j]:                #c5         Σ[1-n](tj-1)
                A[j + 1] = A[j]           #c6         Σ[1-n](tj-1)
                j -= 1                    #c7         Σ[1-n](tj-1)
        A[j + 1] = key                    #c8         n-1
    return A

来计算一下cost
T(n) = c1n + c2(n-1) + c3(n-1) + c4(Σ[1-n]tj) + c5(Σ1-n) + c6(Σ1-n) + c7(Σ1-n) + c8(n-1)
显然在while循环中,tj = 1时,即每次只要比较,不需要替换的时候,执行时间最少
T(n) = c1n + c2(n-1) + c3(n-1) + c4(n-1) + c8(n-1)
= (c1 + c2 + c3 +c4 +c8)n - (c2+c3+c4+c8)
时间复杂度为n
最坏情况下是,tj = j,此时
T(n) = c1n + c2(n-1) + c3(n-1) + c4(Σ[1-n]tj) + c5(Σ1-n) + c6(Σ1-n) + c7(Σ1-n) + c8(n-1)
= c1n + c2(n-1) + c3(n-1) + c4(n-1)n/2 + (c5+c6+c7)(n-2)(n-1)/2 + c8(n-1)
很明显,它是n的二次函数

练习

2.2-1用(H)记号表示函数n^3/1000 - 100n^2 -100n +3
(H)(n^3)
2.2-2考虑对数组A中的n个数的排序问题:首先找出A中的最小元素,并将其与A[1]中的元素交换;接着,找出A中的次小元素,并将其与A[2]中的元素进行交换。对A中头n-1个元素继续这一过程。写出这个算法的伪代码,这个算法称为选择排序(selection sort)。对于这个算法来说,循环不变式是什么?为什么它仅需要在头n-1个元素上运行,而不是在所有n个元素上运行?用(H)给出选择排序的最佳运行时间和最坏运行时间。
def selectionSort(A):                      # 代价      次数
    for i in range(0,len(A)-1):            #  c1        n
        min = if                           #  c2        n-1
        for j in range(i + 1,len(A)):      #  c3        Σ(1,n)tj
            if A[min] > A[j]:              #  c4        Σ(1,n)(tj)
                min = j                    #  c5        Σ(1,n)(tj-1)
        A[min],A[i] = A[i],A[min]          #  c6        n-1
    return A

循环不变式

初始化:当i = 0时,最小值为A[min],将A[min] 与A[1:len(A)]相比较,找出最小值并将下标赋值给min,然后交换A[min]和A[i]
保持:for循环将i从1到len(A)-1开始迭代,最出最小值并交换,否则for循环的下一次迭代将i增加1
终止:当迭代到len(A)-1时,A[len(A)]已经是最大值。而i从0到len(A)都已经完成迭代,A已经是从小到大排列的数组,且若数组中有相同值,则不做移动。
T(n)  = c1n + c2(n-1) + c3Σ(1,n)tj + c4Σ(1,n)(tj) + c5Σ(1,n)(tj-1) + c6(n-1)
最好情况下:tj = 1
T(n) = c1n + c2(n-1) + c3(n)(n+1)/2 + c4(n)(n-1)/2+ c6(n-1)
(H) = n^2/2

最坏情况下:tj = n
T(n) = c1n + c2(n-1) + c3(n)(n+1)/2 + c4(n)(n-1)/2+c5(n-1)(n-2)/2 + c6(n-1)
(H) = n^2/2
2.2-3
最好情况下,元素为第一个元素
(H) = 1
最坏情况,最后一个元素
(H) = n
平均需要检查一半元素
2.2-4

你可能感兴趣的:(2 算法基础)