算法经典考题

经典问题

1.寻找最大的子数组和
方法1:分治法。将n长数组中的最大数组和分为以下几种情况,在左子序列中,在右子序列中,横跨两个子序列中。
方法2:由[i,i+1,i+2,,,,j]到【i,i+1,i+2,,,,j+1】只有两种可能,第一种是最大数组仍是原始数组,第二种是加上j+1的新数组。

#方法1:分治法:
def find_max_across_subarray(A,low,mid,high):
    left_max=0
    max=-float('Inf')
    left=mid+1
    right=mid
    for i in range(mid,low-1,-1):
        left_max+=A[i]
        if left_max>max:
            max=left_max
            left=i
    right_max=max
    for i in range(mid+1,high+1):
        right_max+=A[i]
        if right_max>max:
            max=right_max
            right=i
    return left,right,max

def find_max_subarray(A,low,high):
    if low==high:                                               #基本函数
        return low,high,A[low]
    mid=(low+high)//2
    left_i,left_j,left_max=find_max_subarray(A,low,mid)          #分解
    right_i,right_j,right_max=find_max_subarray(A,mid+1,high)
    across_i,across_j,across_max=find_max_across_subarray(A,low,mid,high)
    if left_max>right_max and left_max>across_max:              #合并
        return left_i,left_j,left_max
    if right_max>left_max and right_max>across_max:
        return right_i,right_j,right_max
    return across_i,across_j,across_max
#方法2:设置一个边界最大值记录一直累加过来的最大值,设置一个当前最大值记录实际动态链接到J的当前最大值。
def find_max_subarray2(A):
    n=len(A)
    maxArray=0
    boundaryA=0
    for i in range(n):
        boundaryA+=A[i]
        if boundaryA<0:
            boundaryA=0
        if boundaryA>maxArray:
            maxArray=boundaryA
    return maxArray

排序问题

比较排序:各元素的次序依赖于它们之间的比较。

对n个输入的比较排序中,各算法的上届都要经过O(nlgn)次比较,因此堆排序和归并排序在算法上是渐进最优的,任何已知的比较排序最多就是在常数因子上优于它们。
1.冒泡排序

'''
冒泡排序:有两个版本,原始版本的最好和最坏时间复杂度为O(n^2),优化版本的最好时间复杂度为O(n),这样它的复杂度就与插入排序一样了。
方法:每次比较两个相邻的数,如果顺序不对就交换。
'''

def bubble_sort1(list):
    n=len(list)
    for i in range(n):
        for j in range(n-1,0,-1):     #注意这里是从最后到最前
            if list[j-1]>list[j]:
                list[j-1],list[j]=list[j],list[j-1]
    return list

'''改进后的版本'''
def bubble_sort2(list):
    n = len(list)
    flag=0
    for i in range(n):
        for j in range(n - 1, 0, -1):  # 注意这里是从最后到最前
            if list[j - 1] > list[j]:
                list[j - 1], list[j] = list[j], list[j - 1]
                flag=1
        if flag==0:
            return list
    return list

print(bubble_sort2([1,4,2,3]))

2.插入排序

'''
插入排序:稳定排序,最好情况的复杂度是O(N),最坏情况的复杂度是O(n^2),平均复杂度与最坏复杂度一样。一般来说,我们关心的都是算法的平均复杂度,即最坏情况下的复杂度。
具体方法:依次将一个数据插入到已经排好序的序列中。
本质:增量方法
'''
def insert_sort(list):
    Len=len(list)
    for i in range(1,Len):
        key=list[i]
        j=i-1
        while j>=0 and list[j]>key:
            list[j + 1]=list[j]
            j-=1
        list[j+1] = key
    return list

print(insert_sort([1,3,5,6,2,4]))

3.归并排序

'''
归并排序:最坏的时间复杂度为O(nlgn) 主要时间花在“合并”步骤上。
具体方法:将每个长度为n的序列分解为2个长度为n/2的序列,然后对子序列进行排序,最后合并这两个子序列。
本质:分治法,三个步骤:分解,解决,合并。

程序注意两点:1.因为使用分治法最后需要合并,因此序列需要输入下标;2.需要从原序列copy出一个左子序列和右子序列,
为了简化代码,避免每一次都检测两个子序列是否为空,在两个子序列最后加上一个Inf值的标志,每次仍只需判断子序列的相应大小即可。
'''
def merge(A,s,m,e):
    n1=m-s+1
    n2=e-m
    L=A[s:m+1].copy()
    R=A[m+1:e+1].copy()
    L.append(float('Inf'))
    R.append(float('Inf'))
    i=0
    j=0
    for k in range(n1+n2):
        if L[i]<=R[j]:
            A[s+k]=L[i]
            i+=1
        else:
            A[s+k]=R[j]
            j+=1
    return

def merge_sort(A,s,e):
    if s<e:                     #若分解到最小规模,则直接输出
        m=(s+e)//2
        merge_sort(A,s,m)       #分解,因为使用递归,因此也在解决。
        merge_sort(A,m+1,e)
        merge(A,s,m,e)           #合并
    return A

print(merge_sort([1,3,5,7,2,10,6],0,6))

4.堆排序

'''
堆排序:集合了插入排序和归并排序的优点,时间复杂度为O(nlgn),且为原址排序。 所有优先队列的复杂度为O(lgn)
堆可以以一个完全二叉树和数组来表示,分为最大堆和最小堆,最大堆规定子节点值小于等于父节点值,最小堆相反。
'''
#维护一个最大堆,对破坏规则的结点进行数值交换,时间复杂度为O(lgn)
def max_heapify(A,i,heapsize):
    #输入i是以0位开始的index
    left=i*2+1            #写出左右结点
    right=left+1
    largest=i
    if left<heapsize and A[left]>A[i]:            #这里用heapsize约束的原因是:在排序时最后一个值与最大值交换后,最大值到数组的最后一位,此时它不应再参与规则。
        largest=left
    if right<heapsize and A[right]>A[largest]:
        largest=right
    if largest==i:
        return
    A[i],A[largest]=A[largest],A[i]
    max_heapify(A,largest,heapsize)

#1:创建一个最大堆,时间复杂度为O(n)
def build_max_heap(A):
    n=len(A)
    for i in range(n//2-1,-1,-1):
        max_heapify(A,i,n)
    return A
#2:堆排序:时间复杂度为O(nlgn)
def heapsort(A):
    build_max_heap(A)
    for i in range(len(A)-1,0,-1):
        A[0],A[i]=A[i],A[0]
        max_heapify(A,0,i)           #这里的i是heapsize,每次移走一个最大值到数组最后的位置后,堆的大小为heapsize-1。
    return A

A=[5,13,2,25,7,17,20,8,4]
print(build_max_heap(A))
print(heapsort(A))

5.快速排序

'''
快速排序:最坏的时间复杂度为O(n^2),平均时间复杂度为O(nlgn),是最常用的一种排序算法,因为它的平均复杂度的隐含常数因子很小,且为原址排序。
这里的最坏情况是指利用主元素划分的数据及其不平衡,而只要数据以常数比例划分,就算是9:1划分,性能也会达到O(nlgn),
另外,为了使最坏情况尽量不发生,可以使用改进的随机化版本达到期望平均复杂度。
与归并排序类似,快速排序也使用了分治思想。
'''
def quicksort(list,start,end):
    if start<end:
        q=random_partition(list,start,end)   #计算下标q,也就是划分的数值下标
        quicksort(list,start,q-1)         #分解并解决,不需要合并
        quicksort(list,q+1,end)
    return list

#写法1
def partition(list,start,end):
    x=list[end]                     #选择一个主元素
    left=start
    right=end
    for i in range(start,end):
        if list[i]>x:
            list[right]=list[i]
            right-=1
        else:
            list[left]=list[i]
            left+=1
    list[left]=x
    return left
#写法2:交换比主元素大的和比主元素小的数
def partition2(list,start,end):
    x=list[end]
    i=start-1     #记录大于x的位置
    for j in range(start,end):
        if list[j]<x:
            i+=1
            list[i],list[j]=list[j],list[i]
    list[i+1],list[end]=list[end],list[i+1]
    return i+1
#写法3:改进的随机版本,可以达到期望时间复杂度
import random
def random_partition(list,start,end):
    rand=random.randint(start,end)
    list[rand],list[end]=list[end],list[rand]
    return partition(list,start,end)

A=[13,19,9,5,12,8,7,4,21,2,6,11]
n=len(A)
print(quicksort(A,0,n-1))

非比较排序

线性时间复杂度的排序算法:计数排序,基数排序和桶排序,是通过运算而不是比较来确定排序顺序的。
6.计数排序:输入数组为A,输出数组为B,临时存放数据的数组为C。
适用于输入数组中的n个数均小于k时,若k=O(n),则此时的算法复杂度为O(n).

def counting_sort(A,k):
    '''
    :param A: 输入序列;
    :param k: 输入序列中数据的最大值;
    :return:已经排好序的输出。
    '''
    n=len(A)
    B=[0]*n;
    C=[0]*(k+1);
    for i in range(n):
        C[A[i]]+=1                #统计每一位上有多少数
    for i in range(1,k+1):
        C[i]=C[i]+C[i-1]          #累积小于i的有多少数
    for i in range(n-1,-1,-1):
        B[C[A[i]]-1]=A[i]         #根据小于i的数量找到它在B中的位置。
        C[A[i]]-=1
    return B

A=[2,5,3,0,2,3,0,3]
print(counting_sort(A,5))

7.桶排序
桶排序的最坏时间复杂度为O(n^2),桶排序假设数据服从均匀分布,平均情况下它的时间复杂度为O(n).
桶排序对于相同对象特别多的列表速度特别快。但是遗憾的是需要排序的对象必须是已知的数值。

def bucket_sort(A):
    n=max(A)-min(A)+1
    B=[0]*n
    for i in range(len(A)):
        B[A[i]-min(A)]+=1
    outlist=[]
    for i in range(n):
        if B[i]!=0:
          outlist+=[i+min(A)]*B[i]
    return outlist

A=[2,5,3,0,2,3,0,3]
print(bucket_sort(A))

搜索算法

搜索:1,在一个列表中找到第i小的元素(随机选择);2.在一个列表中找到元素a的下标(二分查找)
注意!!采用分治问题对数组做排序或者选择时,要输入列表的start和end索引,并且要记住用它们来切分数据!
1.随机选择:期望时间复杂度为线性

import random
def random_partition(A,s,e):
    rand=random.randint(s,e)
    x=A[rand]
    A[rand],A[e]=A[e],A[rand]
    j=s-1
    for i in range(s,e):
        if A[i]<x:
            j+=1
            A[j],A[i]=A[i],A[j]
    A[j+1],A[e]=A[e],A[j+1]
    return j+1

def random_select(A,s,e,i):
    if s==e:
        return A[s]
    q=random_partition(A,s,e)
    k=q-s+1
    if i==k:
        return A[q]
    if i>k:
        return random_select(A,q+1,e,i-k)
    else:
        return random_select(A,s,q-1,i)

A=[1,3,4,6,12,2,7,6]
print(random_select(A,0,7,3))

2.二分查找:二分查找的待查表是一个有序表。

def binary_search(A,value):
    low=0
    high=len(A)-1
    while low<=high:
        mid=(low+high)//2
        if A[mid]==value:
            return mid
        if A[mid]>value:
            high=mid-1
        else:
            low=mid+1
    return None

A=[1,3,3,6,8]
print(binary_search(A,4))

猜你喜欢

转载自blog.csdn.net/zhouhong0284/article/details/81739104