算法学习(一)——分治以及排序算法总结

分治策略:

  • 分解(Divide):将问题划分为若干子问题
  • 解决(Conquer):递归求解子问题
  • 合并(combine):子问题组合成原问题
主方法:T(n) = aT(n/b)+f(n)
分解成a个问题,每个子问题降b倍,合并为O(f(n))
主定理:
比较f(n)和aT(n/b)的阶,要求是多项式意义上的比较,即只能差n^x。
1.f(n)的阶大,T就是O(f(n))
2.反之,为O[n^logb(a)] 正则条件:(对常数c<1,有af(n/b)<=cf(n))
3.最后就是O(f(n)) = O[n^logb(a)],O(T) = O[n^logb(a)*lgn]

n^logb(a)就是满a叉树中叶子节点的个数,对应递归树上,以上三种情况可以解释为:1.树的总代价由根节点决定 2.树的总代价由叶节点决定 3.树的总代价均匀分布在树的所有层次上

举个反例,不能使用主定理的情况:
T(n) = 2T(n/2)+nlgn

f(n)/n^logb(a) = nlgn/n = lgn 它是渐近小于n^c.

排序算法总结

排序的分类:
(一)内部排序和外部排序:内部排序就是在内存中排序;外部排序涉及数据量大,内存不够用,需要对外存访问的排序(多路归并排序)。
(二)基于比较和不基于比较的排序(基数排序)
还有一个稳定性的问题,若存在次关键字(不唯一),排序后位置变化就是不稳定的。

内部排序的分类(下面只标注时间复杂度不是O(n^2)的算法):
1.交换排序:冒泡排序(稳定),快速排序 (不稳定 O(nlgn) 空间复杂度:O(nlgn~n))
2.插入排序:直接插入排序(稳定), 折半插入排序(稳定),
希尔排序/缩小增量排序(不稳定)
3.选择排序:简单选择排序(不稳定),堆排序(不稳定 O(nlgn) ),二路归并排序(稳定 O(nlgn) 空间复杂度:O(n) )
4.不基于比较的基数排序(稳定 O(d(n+r))r:基数 n:节点数  d:每个节点关键字的位数

先介绍三个时间复杂度为O(n^2)的排序算法
冒泡,选择排序,插入排序

1.冒泡
def bubbleSort(alist):
    n = len(alist)
    for i in range(n-1, 0, -1):
        for j in range(0, i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
    return alist
    # 改进的冒泡排序
def bubbleSort(alist):
    n = len(alist)
    exchange = False
    for i in range(n-1, 0, -1):
        for j in range(0, i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]
                exchange = True
        # 如果发现整个排序过程中没有交换,提前结束
        if not exchange:
            break
    return alist
    
2. 选择排序 
它与冒泡的不同之处在于,需要一个多出变量存储遍历当前序列的最小值下标,每轮遍历只需要交换一次
def selectionSort(alist):
    n = len(alist)

    for i in range(n - 1):
        # 寻找[i,n]区间里的最小值
        min_index = i
        for j in range(i+1, n):
            if alist[j] < alist[min_index]:
                min_index = j
        alist[i], alist[min_index] = alist[min_index], alist[i]
    return alist 
#这是最简单的选择排序,另外还有堆排序
3. 插入排序 
它是将新元素插入已排好序的序列中,同样需要存储排好序的队尾元素下标,找到第一个比它小的位置之前插入,关键就是找到比currentvalue小的元素的位置,比currentvalue大就后移。它是需要比较和移动的。
移动次数会比选择排序多,但是它大大减少了比较次数,只要在已经基本有序的序列中找到位置就停止比较和移动了。
def insertionSort(alist):
    for i in range(1,len(alist)):
        currentvalue=alist[i]
        position=i
        while alist[position-1]>currentvalue and position>0:
            alist[position]=alist[position-1]
            position=position-1
        alist[position]=currentvalue
    return alist
    
希尔排序
#基于插入排序的性能提升:需要待排记录基本有序和n值较小,可以大大减少移动比较次数。(如果序列已经有序,只需要比较n-1次)
#增量序列函数有很多,没有最好,它的好坏直接影响时间复杂度。
# 希尔排序
def shellSort(alist):
    n = len(alist)
    gap = n // 2
     #" / "就表示 浮点数除法,返回浮点结果;" // "表示整数除法,代表不大于n/2的整数
    while gap > 0:
        for i in range(gap):   #每轮gap个子列,gap//2产生子列的函数
            gapInsetionSort(alist, i, gap)  #每个子列分别调直接插入排序
        gap = gap // 2
    return alist

# # start子数列开始的起始位置, gap表示间隔

def gapInsetionSort(alist,startpos,gap):
    #希尔排序的辅助函数
    for i in range(startpos+gap,len(alist),gap):
    #每轮有gap个子序列,每个子列有len(alist)/gap个元素,
        position=i
        currentvalue=alist[i]

        while position>startpos and alist[position-gap]>currentvalue:
            alist[position]=alist[position-gap]
            position=position-gap
        alist[position]=currentvalue
        
        

下面介绍使用分治法的经典算法,快速排序

快速排序

快排充分体现了分治法的思想,运用递归,每次将待排序列一分为二,最终分到不可分。
就是三个循环只需要一个变量的额外存储空间,选取枢轴量,空出一个元素,然后“左右互搏”,左右指针从序列两端分别遍历,交换元素。快排需要一个栈空间实现递归,深度为lgn~n

改进:
(1)随机化改进:不是选取第一个值为基准,而是随机选取。
平衡化改进:取第一个、最后一个和中间点三个值中中间值为基准进行排序。
设置阀值–混合排序:当数组长度小于某一值时使用其他较快的排序。
(2)可以改进版的冒泡+快速排序,即在左右指针向中间移动时,同时执行冒泡排序,如果碰头时没有交换,证明该半边已经基本有序,那么就不需要再操作该半边元素。
(3)每次只对元素较少的半边进行快排

1.
def quick_sort(nums, start, end):
    if start >= end:
        return
    pivot = nums[start]  # 基准值
    low = start  # 左指针
    high = end  # 右指针
    while low < high:
        while low < high and nums[high] >= pivot:
            high -= 1
        nums[low] = nums[high]

        while low < high and nums[low] <= pivot:
            low += 1
        nums[high] = nums[low]
    nums[low] = pivot
    quick_sort(nums, start, low - 1)
    quick_sort(nums, low + 1, end)
    
nums = [1,3,54, 26, 93, 44,17, 77, 31, 1,321,44, 55, 20]
quick_sort(nums, 0, len(nums) - 1)
print(nums)

#结果:
[1, 1, 3, 17, 20, 26, 31, 44, 44, 54, 55, 77, 93, 321]

2.廖雪峰版本
from random import Random

def quick_sort(arr):
    if len(arr) > 1:
        qsort(arr, 0, len(arr) - 1)

def qsort(arr, start, end):
    base = arr[start]
    pl = start
    pr = end
    while pl < pr:
        while pl < pr and arr[pr] >= base:
            pr -= 1
        if pl == pr:
            break
        else:
            arr[pl], arr[pr] = arr[pr], arr[pl]

        while pl < pr and arr[pl] <= base:
            pl += 1
        if pl == pr:
            break
        else:
            arr[pl], arr[pr] = arr[pr], arr[pl]

    # now pl == pr
    if pl - 1 > start:
        qsort(arr, start, pl - 1)
    if pr + 1 < end:
        qsort(arr, pr + 1, end)

r = Random()
a = []
for i in range(20):
    a.append(r.randint(0, 100))

print a
quick_sort(a)
print a

#结果:
[40, 57, 29, 21, 98, 37, 16, 77, 63, 86, 63, 12, 23, 68, 38, 66, 64, 13, 63, 36]
[12, 13, 16, 21, 23, 29, 36, 37, 38, 40, 57, 63, 63, 63, 64, 66, 68, 77, 86, 98]

如果设f(n)为数组长为n时的比较次数,则f(n)=[(f(1)+f(n-1))+(f(2)+f(n-2))+…+(f(n-1)+f(1))]/n.
利用数学知识易知f(n)=(n+1)*[1/2+1/3+…+1/(n+1)]-2n~1.386nlog(n).

归并排序

归并也体现分治的思想
详见这篇博客

# 归并排序
def mergesort(seq):
    """归并排序"""
    if len(seq) <= 1:
        return seq
    mid = len(seq) / 2  # 将列表分成更小的两个列表
    # 分别对左右两个列表进行处理,分别返回两个排序好的列表
    left = mergesort(seq[:mid])
    right = mergesort(seq[mid:])
    # 对排序好的两个列表合并,产生一个新的排序好的列表
    return merge(left, right)

def merge(left, right):
    """合并两个已排序好的列表,产生一个新的已排序好的列表"""
    result = []  # 新的已排序好的列表
    i = 0  # 下标
    j = 0
    # 对两个列表中的元素 两两对比。
    # 将最小的元素,放到result中,并对当前列表下标加1
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result += left[i:]
    result += right[j:]
    return result



自底向上(非递归法)方法

# 自底向上的归并算法
def mergeBU(alist):
    n = len(alist)
    #表示归并的大小
    size = 1
    while size <= n:
        for i in range(0, n-size, size+size):
            merge(alist, i, i+size-1, min(i+size+size-1, n-1))
        size += size
    return alist

# 合并有序数列alist[start....mid] 和 alist[mid+1...end],使之成为有序数列
def merge(alist, start, mid, end):
    # 复制一份
    blist = alist[start:end+1]
    l = start
    k = mid + 1
    pos = start

    while pos <= end:
        if (l > mid):
            alist[pos] = blist[k-start]
            k += 1
        elif (k > end):
            alist[pos] = blist[l-start]
            l += 1
        elif blist[l-start] <= blist[k-start]:
            alist[pos] = blist[l-start]
            l += 1
        else:
            alist[pos] = blist[k-start]
            k += 1
        pos += 1        

猜你喜欢

转载自blog.csdn.net/qq_33330524/article/details/84974824