算法学习第五篇:排序算法与二分法

Posted 596014054-yangdongsheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法学习第五篇:排序算法与二分法相关的知识,希望对你有一定的参考价值。

排序算法稳定性

技术图片

冒泡排序

思路:首先,列表每两个相邻的数比较大小,如果前边的比后边的大,那么这两个数就互换位置。就像是冒泡一样

代码关键点

趟数:n-1趟
无序区

依次类推就会得到排序结果。冒泡排序的效率还是很低的

代码示例    (这是基于顺序表实现的,链表还要关注一下节点)

# 思路:列表中两个相邻的数比较大小,如果前边的比后边的大,那么这两个就互换位置
def bubblr_sort(li):
    for i in range(1,len(li)-1):#表示趟数
        for j in range(len(li)-i):  #表示无序区,无序区的范围为0,len(li)-i
           if li[j] > li[j+1]:
               li[j],li[j+1] = li[j+1],li[j]


li = list(range(10))
import random
random.shuffle(li)
print(li)
bubblr_sort(li)
print(li)

最坏时间复杂度:O(n2)

最优时间复杂度:O(n)

冒泡优化

# 思路:列表中两个相邻的数比较大小,如果前边的比后边的大,那么这两个就互换位置
def bubblr_sort(li):
    for i in range(1,len(li)-1):#表示趟数
        change = False
        for j in range(len(li)-i):  #表示无序区,无序区的范围为0,len(li)-i
            if li[j] > li[j+1]:
                li[j],li[j+1] = li[j+1],li[j]    
                change = True    如果从头到尾(内层循环一遍)都没有交换过,就不需要走第二趟了
        if not change:
            return

li = list(range(10))
import random
random.shuffle(li)
print(li)
bubblr_sort(li)
print(li)

选择排序

思路:一趟遍历完记录最小的数,放到第一个位置;在一趟遍历记录剩余列表中的最小的数,继续放置

代码关键点

无序区
最小数的位置

问题:怎么选出最小的数

import random
def select_sort(li):
    for i in range(len(li)-1):
       #i 表示趟数,也表示无序区开始的位置
       min_loc = i  #最小数的位置
       for j in range(i+1,len(li)):  #i  ,i+1,就是后一个位置的范围
           if li[j] <li[min_loc]:    #两个位置进行比较,如果后面的一个比最小的那个位置还小,说明就找到最小的了
                min_loc = j          #找到最小的位置下标
       li[i],li[min_loc] = li[min_loc],li[i]  #吧找到的两个值进行互换位置
li = list(range(10))
random.shuffle(li)
print(li)
select_sort(li)
print(li)

时间复杂度

最优时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
稳定性:不稳定(考虑升序每次选择最大的情
况)

插入排序

思路:元素被分为有序区和无序区两部分。最初有序区只有一个元素。每次从无序区中选择一个元素,插入到有序区的位置,直到无序区变空。

代码关键点

摸到的牌
手里的牌

代码示例一

import random
def insert_sort(li):
    for i in range(1,len(li)):
       #i 表示无序区的第一个数
       tmp = li[i]      #摸到的牌
       j = i-1       #指向有序区最后一个位置
       while li[j] >tmp and j>=0:
                    #循环终止条件 li[j]<=tmp  and j==-1
           li[j+1] = li[j]  #向后移动
            j-=1
       li[j+1] = tmp
        
li = list(range(10))
random.shuffle(li)
print(li)
insert_sort(li)
print(li)

代码示例二

技术图片

代码示例三

技术图片

快速排序

思路

1、取一个元素p(第一个元素),是元素p归位(去它该去的地方);
2、列表被p分成两部分,左边的都比p小,右边的都比p大;
3、递归完成排序

算法关键点

归位
递归

图示说明

技术图片

技术图片

技术图片

怎么归并呢?先把5取出来,这时候就会有一个空位,从右边找比5小的数填充过来,现在右边有一个空位了,从左边找比5大的放到右边的空位上。依次类推,

只要left和right碰在一起,这样就找打5的位置了

如图示:

图一技术图片

图二技术图片

 图三技术图片

图四技术图片

 这样在把找到的5的位置放进去去ok了

技术图片

代码示例一

import time
def wrapper(func):
   def inner(*args,**kwargs):
       start = time.time()
       ret = func(*args,**kwargs)
       end = time.time()
       print(‘%s running time :%s‘%(func.__name__,start-end))
           return ret
   return inner


def partition(li,left,right):
   ‘‘‘归位函数‘‘‘
   tmp = li[left]  #先把5取出来
   while left < right:
        while left < right and li[right] >= tmp:  #如果降序排列修改li[right] <= tmp
               right -= 1 #从右边找比5小的数,填充到5的位置
       li[left] = li[right]
       while left < right and li[left] <= tmp:  #如果降序排列修改li[right] >= tmp
               left += 1# 从左边找比5大的数字放在右边的空位
       li[right] = li[left]
   li[left] = tmp  #当跳出循环条件的时候说明找到了,并且把拿出来的5在放进去
    return left


def _quick_sort(li,left,right):
    ‘‘‘快速排序的两个关键点:归位,递归‘‘‘
    if left < right:  #至少有两个元素,才能进行递归
        mid = partition(li,left,right)  #找到归位的位置
        _quick_sort(li,left,mid-1)  #递归,右边的-1
         _quick_sort(li,mid+1,right) #递归,左边的+1

@wrapper
def quick_sort(li):
    return _quick_sort(li, 0, len(li)-1)

@wrapper
def sys_sort(li):
    ‘‘‘系统排序‘‘‘
    li.sort()

import random
li = list(range(100000))
random.shuffle(li)
# print(li)
quick_sort(li)
# print(li)

sys_sort(li)  
 
#结论:系统的排序要比快排的时间快的多
# quick_sort running time :-0.6240355968475342
# sys_sort running time :-0.002000093460083008 

快速排序的时间复杂度O(nlogn)

 技术图片

 代码示例二

技术图片

代码示例三

#quick sort
def quickSort(array):
    if len(array) < 2:  # 基线条件(停止递归的条件)
        return array
    else:  # 递归条件
        baseValue = array[0]  # 选择基准值
        less, equal, greater = [], [baseValue], []
        for m in array[1:]:    
            if m < baseValue:        
                # 由所有小于基准值的元素组成的子数组
                less.append(m)         
            elif m > baseValue:        
                # 由所有大于基准值的元素组成的子数组
                greater.append(m)    
            else:        
                # 包括基准在内的同时和基准相等的元素,在上一个版本的百科当中,并没有考虑相等元素
                equal.append(m)
        return quickSort(less) + equal + quickSort(greater)
# 示例:
array = [2,3,5,7,1,4,6,15,5,2,7,9,10,15,9,17,12]
print(quickSort(array))
# 输出为[1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 9, 9, 10, 12, 15, 15, 17]

  

堆排序

有关对的了解:http://www.cnblogs.com/haiyan123/p/8400537.html

堆排序过程

1、建立堆
2、得到堆顶元素,为最大元素
3、去掉堆顶,将堆最后一个元素放在堆顶,此时可通过一次调整重新使堆有序
4、堆顶元素为第二大元素
5、重复步骤3,直到堆变空

代码示例

import random

def _sift(li, low, high):
    """
    :param li:
    :param low: 堆根节点的位置
    :param high: 堆最有一个节点的位置
    :return:
    """
    i = low  # 父亲的位置
    j = 2 * i + 1  # 孩子的位置
    tmp = li[low]  # 原省长
    while j <= high:
        if j + 1 <= high and li[j + 1] > li[j]:  # 如果右孩子存在并且右孩子更大
            j += 1
        if tmp < li[j]:  # 如果原省长比孩子小
            li[i] = li[j]  # 把孩子向上移动一层
            i = j
            j = 2 * i + 1
        else:
            li[i] = tmp  # 省长放到对应的位置上(干部)
            break
    else:
        li[i] = tmp  # 省长放到对应的位置上(村民/叶子节点)


def sift(li, low, high):
    """
    :param li:
    :param low: 堆根节点的位置
    :param high: 堆最有一个节点的位置
    :return:
    """
    i = low         # 父亲的位置
    j = 2 * i + 1   # 孩子的位置
    tmp = li[low]   # 原省长
    while j <= high:
        if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子存在并且右孩子更大
            j += 1
        if tmp < li[j]: # 如果原省长比孩子小
            li[i] = li[j]  # 把孩子向上移动一层
            i = j
            j = 2 * i + 1
        else:
            break
    li[i] = tmp
 
 

def heap_sort(li):
    n = len(li)
    # 1. 建堆
    for i in range(n//2-1, -1, -1):
        sift(li, i, n-1)
    # 2. 挨个出数
    for j in range(n-1, -1, -1):    # j表示堆最后一个元素的位置
        li[0], li[j] = li[j], li[0]
        # 堆的大小少了一个元素 (j-1)
        sift(li, 0, j-1)


li = list(range(10))
random.shuffle(li)
print(li)
heap_sort(li)
print(li)

# li=[2,9,7,8,5,0,1,6,4,3]
# sift(li, 0, len(li)-1)
# print(li)

归并排序

假设现在的列表分两段有序,如何将其合成为一个有序列表。这种操作称为一次归并

技术图片

思路:

技术图片

  • 分解:将列表越分越小,直至分成一个元素
  • 终止条件:一个元素是有序的
  • 合并:将两个有序列表归并,列表越来越大

图实示例:https://www.cnblogs.com/chengxiao/p/6194356.html

代码示例

import random
def merge(li, low, mid, high):
   # 一次归并
   ‘‘‘
    :param li: 列表
    :param low: 起始位置
   :param mid: 按照那个位置分
  :param high: 最后位置
   :return:
    ‘‘‘
    i = low
    j = mid + 1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] < li[j]:
            ltmp.append(li[i])
            i += 1
       else:
           ltmp.append(li[j])
            j += 1
    while i <= mid:
       ltmp.append(li[i])
        i += 1
   while j <= high:
        ltmp.append(li[j])
       j += 1
    li[low:high+1] = ltmp


def _merge_sort(li, low, high):
    if low < high:  # 至少两个元素
        mid = (low + high) // 2
        _merge_sort(li, low, mid)
       _merge_sort(li, mid+1, high)
        merge(li, low, mid, high)
       print(li[low:high+1])


def merge_sort(li):
    return _merge_sort(li, 0, len(li)-1)


li = list(range(16))
random.shuffle(li)
print(li)
merge_sort(li)

print(li)

 

技术图片

 

技术图片

 

时间复杂度

·最优时间复杂度:O(nlogn)
·最坏时间复杂度:O(nlogn)
·稳定性:稳定

基数排序

import random
from timewrap import *

def list_to_buckets(li, iteration):#这个是用来比较每个位置的大小的数字

    """
    因为分成10个本来就是有序的所以排出来就是有序的。
    :param li: 列表
    :param iteration: 装桶是第几次迭代
    :return:
    """
    buckets = [[] for _ in range(10)]
    print(‘buckests‘,buckets)
    for num in li:
        digit = (num // (10 ** iteration)) % 10
        buckets[digit].append(num)
    print(buckets)
    return buckets

def buckets_to_list(buckets):#这个是用来出数的
    return [num for bucket in buckets for num in bucket]
    # li = []
    # for bucket in buckets:
    #     for num in bucket:
    #         li.append(num)

@cal_time
def radix_sort(li):
    maxval = max(li) # 10000
    it = 0
    while 10 ** it <= maxval:#这个是循环用来,在以前一次排序的基础上在排序。
        li = buckets_to_list(list_to_buckets(li, it))
        it += 1
    return li

# li = [random.randint(0,1000) for _ in range(100000)]
li = [random.randint(0,10) for _ in range(10)]
li=[5555,5525,9939,9999,6,3,8,9]
s=radix_sort(li)
print(s)

希尔排序

思路

希尔排序是一种分组插入排序算法
首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;
取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同一组
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。

代码实现

def insert_sort(li):      #插入排序
    for i in range(1, len(li)):
                  # i 表示无序区第一个数
        tmp = li[i]   # 摸到的牌
        j = i - 1     # j 指向有序区最后位置
        while li[j] > tmp and j >= 0:
                  #循环终止条件: 1. li[j] <= tmp; 2. j == -1
            li[j+1] = li[j]
            j -= 1
        li[j+1] = tmp

def shell_sort(li):    #希尔排序 与插入排序区别就是把1变成d d = len(li) // 2 while d > 0: for i in range(d, len(li)): #通过一个for循环把所有子序列全部比较了 tmp = li[i] j = i - d while li[j] > tmp and j >= 0: li[j+d] = li[j] j -= d li[j+d] = tmp d = d >> 1 li=[5,2,1,4,5,69,20,11] shell_sort(li) print(li)

  

 技术图片

希尔排序的复杂度特别复杂,取决于d,分组的长度二、位移运算符

最坏o(n^2)

最好大概O(n^1.3)

计数排序

统计每个数字出现了几次

#计数排序
# 0 0 1 1 2 4 3 3 1 4 5 5
import random
import copy
from timewrap import *

@cal_time
def count_sort(li, max_num = 100):
    count = [0 for i in range(max_num+1)]
    for num in li:
        count[num]+=1
    li.clear()
    for i, val in enumerate(count):
        for _ in range(val):
            li.append(i)

@cal_time
def sys_sort(li):
    li.sort()

li = [random.randint(0,100) for i in range(100000)]
li1 = copy.deepcopy(li)
count_sort(li)
sys_sort(li1)

计数排序这么快,为什么不用计数排序呢?因为他是有限制的,你要知道列表中的最大数

如果一下来了一个很大的数,比如10000,那么占的空间就的这么大,

计数排序占用的空间和列表的范围有关系

解决这种问题的方法,可以用桶排序,都放进去可以在进行其他的排序。比如插入排序。

桶排序

在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?

桶排序,首先将将元素分在不同的桶中,在对每个桶中的元素排序。

技术图片

多关键字排序

先对十位进行排序,再根据 十位进行排序

要用两个函数,一个用来装桶,一个用来出桶

默认10个桶,找到个位,十位,分别放在对应的桶里的位置

 

桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。

平均情况时间复杂度:O(n+k)

最坏情况时间复杂度:O(n+k)

空间复杂度:O(nk)

分成若干个桶,桶内用插入排序

搜索

搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存
在。搜索的几种常见方法:顺序查找、二分法查找、二又树查找、哈希查找

二分法查找

二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升
序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进
一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功

  

技术图片

 

技术图片

时间复杂度

·最优时间复杂度:O(1)
·最坏时间复杂度:Ologn)

总结

Low B 三人组

冒泡排序,选择排序,直接插入排序他们的时间复杂度都是O(n^2),空间复杂度是O(1)

NB 三人组

快速排序,归并排序,堆排序他们的时间复杂度都是O(nlogn)
三种排序算法的缺点
快速排序:极端情况下排序效率低
归并排序:需要额外的内存开销
堆排序:在快的排序算法中相对较慢

技术图片

挨着换的稳定,不挨着换的不稳定

以上是关于算法学习第五篇:排序算法与二分法的主要内容,如果未能解决你的问题,请参考以下文章

[ 数据结构 -- 手撕排序算法第五篇 ] 快速排序 <包含hoare法,挖坑法,前后指针法> 及其算法优化

Python 第五篇:冒泡算法系统标准模块

贪心算法第五篇: 区间问题(右边界总是大于左边界-隐含条件)

贪心算法第五篇: 区间问题(右边界总是大于左边界-隐含条件)

程序设计与算法算法基础》《第五周 二分》分治

网易云课堂_C语言程序设计进阶_第五周:递归与简单算法:递归搜索二分搜索简单排序