其他算法(待完善)

内容概要

  一、希尔算法

  二、计数算法

  三、桶排序

  四、基数算法

 

1、希尔算法

  希尔算法是插入算法的一种优化。

  希尔算法每遍历一次,并不使得列表某些元素之间有序,只是使得整体的元素趋向于有序

 

  希尔算法的一种实现方式

    对于n个元素的列表,将列表分为d1个组,d1等于(n // 2),每组每个元素的间隔也为d1,对这d1组都进行插入排序

    再将n个元素的列表,分为d2个组,d2等于(d1 // 2),每组每个元素的间隔为d2,对这d2组都进行插入排序

    ...

    将n个元素的列表分为dn个组,dn等于(dn-1 // 2),每组每个元素的间隔为dn,对这dn组都进行插入排序

    直到dn = 1,对整个列表进行最后一次插入排序,列表排序完成

 

  希尔算法代码实现

def insert_sort_gap(li, x=2):
    n = len(li)
    gap = n // x
    while gap >= 1:
        for i in range(gap, n):
            tmp = li[i]
            for j in range(i-gap, -1, -gap):
                if li[j] > tmp:
                    li[j+gap] = li[j]
                else:
                    li[j+gap] = tmp
                    break
            else:
                li[i % gap] = tmp  # 利用余数可以找到每组有序区中一开始元素的下标

        gap = gap // 2

  希尔算法分组示意图

    **待补充**

 

   希尔算法的时间复杂度

    希尔算法的时间复杂度可能涉及数学问题,比较复杂,它的时间复杂度跟如何分组有关

    **待补充**

 

2、计数算法

  计数算法之前的算法都属于比较算法,比较算法经过数学证明,最快的时间复杂度为O(nlogn)

 

  在知道要排序的列表中数值的范围时(比如所有数都在1~100之间),就可以使用计数算法

 

  计数算法代码实现

def count_sort(li, max_num):
    tmp_li = [0 for _ in range(max_num+1)]  # 创建一个用于存储信息的列表,索引对应原列表的值,值对应原列表中的值出现的次数
    for i in li:
        tmp_li[i] += 1
    li.clear()

    for index, val in enumerate(tmp_li):
        for _ in range(val):
            li.append(index)

import random
# li = [random.randrange(101) for _ in range(100)]
li = [i for i in range(101)]
random.shuffle(li)
print(li)

count_sort(li, 100)
print(li)

 

  计数算法示意图解

    **待补充**

 

  计数算法的时间复杂度

    计数算法的时间复杂度很快,为O(n),但是使用计数算法是有限制的

      一、是必须知道数据的范围

      二、计数算法消耗内存,它的空间复杂度为O(m),m指的是数据最大值与数据最小值的差值,也就是数据跨度

        比如存在5个数的列表,知道它的范围为1~10000000,计数排序便要创建一个长度为10000000的列表,之后还要遍历这10000000长的列表

 

    所以只有部分情况下可以使用计数排序

      一、已知数据的跨度

      二、数据的跨度尽量小

      三、数据中存在小数尽量不使用,存在负数是有办法排序的(在索引对应值中修改下就好)

 

3、桶排序

  桶排序可以看作是计数排序的改良

  计数排序将列表的索引看作是一个桶,用来装和它索引相同的值

  桶排序将一个容器类型看作桶(这里用列表),它不止存放唯一值,而是存放一个范围的值

 

  桶排序代码实现

import random
li1 = list(range(100001))
random.shuffle(li1)


def bucket_sort(li, max_num, bucket_size):
    buckets = [[] for _ in range(max_num // bucket_size)]  # 创建一个二级列表
    for num in li:
        if num == max_num:
            buckets[-1].append(num)
            continue
        i = num // bucket_size
        buckets[i].append(num)
        # 放入桶时,就对桶内元素进行排序(插入排序)
        for j in range(len(buckets[i])-2, -1, -1):
            if num < buckets[i][j]:
                buckets[i][j+1] = buckets[i][j]
            else:
                buckets[i][j+1] = num
                break
        else:
            buckets[i][0] = num
    
    # 将所有元素放入桶后,再对每个桶内的元素进行排序
    # for buc in buckets:
    #     heap_sort(buc, 0, len(buc)-1)

    # 依次将桶内元素放回原列表
    li.clear()
    for buc in buckets:
        li.extend(buc)


print(li1)
bucket_sort(li1, 100000, 100)
print("==============================================")
print(li1)

 

 

  桶排序示意图解

    **待补充**

 

  桶排序的时间复杂度

    桶排序的时间复杂度比较复杂,它的时间复杂度与数据的分布有关

      数据分布得越均匀,它的效率越高;

      如果数据分布得不均匀

        比如对于一个列表中的数据,最大值为10000,跨度设为1000,其中1%的数在0~8999中,99%的数在9000~10000中

        按照上面代码的逻辑,1%的数将分配到9个桶中,99%的数分配到一个桶中;那么对于最后一个桶进行插入排序的话,就相当于对99%的数进行插入排序(效率很慢)

 

        正确的分法是将0~8999的数用一个桶存放,将9000-10000的数据再细分多个桶存放

 

    将创建列表的式子改为,桶排序可能发生最坏情况

 

li1 = [random.randrange(10001) for _ in range(10001)]

 

    **待补充,桶排序的具体时间复杂度**

 

  总之,桶排序不太好用,使用得少

 

4、基数算法

  基数算法是通过对数据得个位进行排序,再对十位进行稳定的排序,再对百位进行稳定的排序...以此类推,最后依次取出,排序完成

  基数算法的思想与多关键字查询有关

    比如,要根据用户的年龄升序排序,年龄相同的根据薪资排序。

      可能与想象的不太一样,这里是先要按照薪资进行一次排序(并不是先按照年龄排序)

      再对年龄进行稳定的排序(年龄相同的数据,将会保证保持按薪资排序后的相对位置不变;年龄不同的数据,不受薪资排序的影响,相对位置可能发生改变——后排序的字段优先级更高)

 

    如果先按照年龄排序,再按照薪资排序将会遇到一个问题

      按照年龄排序后的列表可能存在多处,年龄相同的地方,要怎么记录这些相同的位置(也能够实现,但没有上面方法简单)。

 

  基数算法代码实现

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


def radix_sort(li):
    buckets = [[] for _ in range(10)]
    max_num = max(li)
    n = 1
    
    while 10**(n-1) <= max_num:  # 基数循环的次数是依照最大值的位数决定的,比如1024,就执行4次循环;15474就执行5次循环
        for num in li:
            i = num // 10**(n-1) % 10  # i获取到个位、十位、或者百位上的数
            buckets[i].append(num)

        li.clear()
        for buc in buckets:  # 将桶里面的数据放到列表中
            li.extend(buc)
            
        for buc in buckets:  # 清空桶
            buc.clear()

        n += 1


radix_sort(li1)
print(li1)

 

  基数算法示意图解

    **待补充**

 

  基数算法的时间复杂度

    综上,基数算法的时间复杂度为O(kn),n指的是列表的长度,k根据最大值而来,k等于log max_num(以10为底的,以max_num为真数的对数)

    当列表的最大值较小时,基数排序比快速排序更快

import random
import time


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

li1 = li.copy()
li2 = li.copy()


def tell_time(func):
    def wrapper(*args, **kwargs):
        s_time = time.time()
        res = func(*args, **kwargs)
        e_time = time.time()
        print(e_time - s_time)
        return res
    return wrapper


@tell_time
def radix_sort(li):
    buckets = [[] for _ in range(10)]
    max_num = max(li)
    n = 1

    while 10**(n-1) <= max_num:
        for num in li:
            i = num // 10**(n-1) % 10
            buckets[i].append(num)

        li.clear()
        for buc in buckets:
            li.extend(buc)

        for buc in buckets:
            buc.clear()

        n += 1


def position(li, head, end):
    tmp = li[head]
    i = head
    j = end
    while i < j:
        while i < j and li[j] >= tmp:
            j -= 1
        li[i] = li[j]
        while i < j and li[i] <= tmp:
            i += 1
        li[j] = li[i]
    else:
        li[i] = tmp
    return i


def _quick_sort(li, head, end):
    if head < end:
        mid = position(li, head, end)
        _quick_sort(li, head, mid-1)
        _quick_sort(li, mid+1, end)


@tell_time
def quick_sort(li, head, end):
    _quick_sort(li, head, end)


quick_sort(li1, 0, len(li1)-1)
radix_sort(li2)

结果:
0.3480687141418457
0.17054438591003418
View Code

 

  但是基数算法也有缺点,比如它适合数字跨度较小的数字结合,并且无法比较小数。在比较字符串时要注意,对于不同位数的整数,要在前置位补0;对于字符串,要在后置位补0。

91110
120

#前置位补0
91110
00120

"abcdwewat"
"abcd"

#后置位补0
"abcdwewat"
"abcd00000"

 

***待完善***

posted @ 2021-04-06 22:01  口乞厂几  阅读(111)  评论(0)    收藏  举报