排序问题

冒泡,选择,插入都属于原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。

 

 

1.冒泡排序

时间复杂度:O(n²)

原地排序(不需要额外空间来存放数据,所以空间复杂度是O(1))

思路:

  一个列表有n个数字就循环n-1次,循环一次就会将最大数字放到了最后(相邻数字进行比较,大的就放在右边,每次对比后数字大的都放右边,这样一行数字每两个相邻的数字都进行一次比较下来后,最大的数字就放到了最右边,

也就是本列表的-1索引位置,这样做n-1次,就把整个列表排序做好了,n是整个列表长度,因为最后一次就剩最左边一个元素了就不用再比较了.)

 

#冒泡排序完整代码
#上述代码操作需要作用n-1才可以将整个序列变成有序的
def sort(alist):
    length = len(alist)
    
    for j in range(length-1): #有几个数字就循环几次,确保每一个数字都移动到对应的位置
        #让两两元素进行比较(n-1)
        for i in range(length-1-j):#每相邻两个数字都做比较,大的就放到最后,依次下去,
                      直到对应的位置,这里减j是要省去每次循环都对比7个元素
                      (循环一次就有一个数字已经找到对应的位置,接下来再循环时相邻数字作比较就少一个就好了),
                      第一次对比7个数字,第二次减去一个上依次循环已经定位好的第7个数字,就只对6个数字最相邻数字比较
      #指针,先指0然后索引0和1的比较大小,直到指针直到n-2,还要留最后一个,用j+1来表示最后一个索引.

            if alist[i] > alist[i+1]:#第一个元素大于第二个元素,换成小于号就是降序排列
                #两个元素交换位置
                alist[i],alist[i+1] = alist[i+1],alist[i]
    return alist

alist = [3,8,5,2,0,7,6]
print(sort(alist))



#
代码优化:

如果某一次循环中,没有做过alist[i],alist[i+1] = alist[i+1],alist[i]这样的交换,就证明序列已经排好了,就不用在继续循环下去了.
def sort(alist): length = len(alist) for j in range(length - 1): # 有几个数字就循环几次,确保每一个数字都移动到对应的位置 # 让两两元素进行比较(n-1) exchange=False#做个标记,只要这次循环,有过交换,就置为True,没有过交换就还是False for i in range(length - 1 - j): # 每相邻两个数字都做比较,大的就放到最后,依次下去, if alist[i] > alist[i + 1]: # 第一个元素大于第二个元素,换成小于号就是降序排列 # 两个元素交换位置 alist[i], alist[i + 1] = alist[i + 1], alist[i] exchange=True print(alist)#将每次循环的结果打印出来 if not exchange: return alist alist = [3, 8, 2, 0, 5, 6, 7] print('这是函数return的列表:',sort(alist))

 

 

  

2.选择排序

时间复杂度:O(n²)

原地排序(不需要额外空间来存放数据,所以空间复杂度是O(1))

思路:

  先循环换找到最大的数字(并且获取到对应的索引),再将最后一个数字和最大数字交换位置,这样省去了每相邻两个数字做比较大小.

  选择排序改进了冒泡排序,每次遍历列表只做一次交换。为了做到这一点,一个选择排序在他遍历时寻找最大的值,并在完成遍历后,将其放置在正确的位置。

如图:

 

 思路一:选最大的,先放到最后

#选择排序完整代码
def sort(alist):
    for j in range(0,len(alist)-1):
        max_index = 0 #表示的是最大值的下标,一开始我们假设列表中的第0个元素为最大值
        for i in range(len(alist)-1-j):#控制比较的次数
            if alist[max_index] < alist[i+1]:
                max_index = i+1 
        #将最大值和最后一个元素交换位置,就可以将最大值放置到序列的最后位置
        alist[max_index],alist[len(alist)-1-j] = alist[len(alist)-1-j],alist[max_index]

    return alist
    
    
alist = [3,8,5,2,0,7,6]
print(sort(alist))

 

思路二:选择最小的,先放到最前

 

# 思路:分两半,左边有序,右边无序,先把最左边的定义为最小的数字,
# 然后每循环一次都定位好一个最小数字,i表示的是无序数列的第一个数字的索引
alist = [3, 8, 2, 0, 5, 6, 7]
n=len(alist)
def xuanze(li):
    for i in  range(n-1):
        min_num=i
        for j in  range(i+1,n):
            if li[j]<li[min_num]:
                min_num=j
        li[i],li[min_num]=li[min_num],li[i]
        print( li)
    return li
print('最终结果:',xuanze(alist))

#这个代码暂时无法优化,必需全程走完.

 

 

3.插入排序 

时间复杂度:O(n²)

原地排序(不需要额外空间来存放数据,所以空间复杂度是O(1))

分析:

  • 需要将原始的序列假装拆分成两部分

    • 有序部分:默认为序列中的第一个元素

    • 无序部分:默认为序列中除了第一个元素剩下的元素

    • 关键:将无序部分的元素逐一插入到有序部分中即可

  • 定义一个变量叫做i

    • i表示的是有序部分元素的个数

    • i还可以表示无序部分中第一个元素的下标

  • 原始序列:[3,8,5,2,6,10,1]

  • [3,  8,5,2,6,10,1] ==》i=1

  • [3,8,  5,2,6,10,1] ==》i=2

思路:

  插入排序的主要思想是每次取一个列表元素与列表中已经排序好的列表段进行比较,然后插入从而得到新的排序好的列表段,最终获得排序好的列表。比如,待排序列表为[49,38,65,97,76,13,27,49],则比较的步骤和得到的新列表如下:(带有背景颜色的列表段是已经排序好的,红色背景标记的是执行插入并且进行过交换的元素)

 

#插入排序完整代码
def sort(alist):
    #i的取值范围是1-(n-1)
    for i in range(1,len(alist)):#假设左起第一个数字已经是排序好了的,然后就要拿左起第二个数字和这个数字作比较,
                    比第一个大,位置就不动,如果比第一个数字小就要放到第一个数字的左边,然后继续循环
                    (注意这里的range是顾头不顾尾,会比len(alist)少1)
#i = 2 #有序部分有两个元素 while i > 0: if alist[i] < alist[i-1]: alist[i],alist[i-1] = alist[i-1],alist[i] i -= 1       #i是不可以为负数,减一之后就是刚才的数字向左移了一位,这里你继续和他左边的数字作对比,
               对比大了就不动了,小了就继续移动有可能会直到移动到最左边.
else: break return alist alist = [3,8,5,2,0,7,6] print(sort(alist))

 

思路二:

想象成摸扑克牌,手里的牌你要排好序,从下面摸上来的牌要一个一个按顺序插入你手里的扑克牌中(从左到右是升序或者降序都可以)

 

alist = [3, 8, 2, 0, 5, 6, 7]
n=len(alist)

def insert_sort(li):
    for i in range(1,n):#i是摸到的牌的索引
        tmp=li[i] #把摸到的数字留住做对比用
        j=i-1 #j是你手里牌最后一张(或者说是最大或最小的一张)
        while j>=0 and li[j]>tmp:#j=0时就是最左端的数字和摸来的牌比大小,这里是升序排列,
            # 你摸到的牌比你手里最大的要小,就得重新插入,如果比你手里最大的牌都大,
            # 那就继续往下走,不用再往前面插入了

       li[j+1]=li[j]#将j这个手里最后一张牌向右挪一个位置,
# 这时候把原来li[i]的排顶掉了,如果没有tmp接着li[i]的话就少个数字了

       j-=1#将tmp继续和原来j左边的数字继续比较大小,直到到了最后一位索引为0的或者是找到一个比tmp数字小的就停下了 # 当while循环完后,j位置是没有数字的,j为空位,而tmp还在外边,因为最后j减了1,所以j+1就是空位了,这个是给tmp这个数字留的 li[j+1]=tmp insert_sort(alist) print(alist)

 

 

4.快速排序

时间复杂度:O(nlogn)如果出现特殊情况就是O(n²)

原地排序(不需要额外空间来存放数据,所以空间复杂度是0)

最坏情况(这种情况会非常少,但是并不是不会出现):

  当你升序快排时如果,给你一个列表是逆序的例如:[9,8,7,6,5,4,3,2,1,0],你选择的中间值数字,默认一般是第一个,也就是索引为0的数字,这样,你往下走,

  每次找的数字不是剩下的列表中最大的数字,就是最小的数字,每次都只能分出一堆的数字,而不是像快排要求的左边一半,

  右边一半(全部在左边或者全部在右边),这样,你的时间复杂度就变成了O(n²)而不是O(nlogn),

  解决的办法是每次选中间值时,都随机挑选一个数字做中间值,而不是每次都招最左边的数字,但是这样也不是100%能避免上面的问题.

 

 注意:快排用的是递归,它会消耗电脑资源,而且递归次数有限制(1000以内吧)

修改递归最大次数:

#在递归前插入这两行
import sys
sys.setrecursionlimit(1500)  # 修改最大递归次数为 1500

 

 

分析:

  • alist = [66,13,51,76,81,26,57,69,23]

  • 基数:

    • 默认情况下序列中第一个元素作为基数

    • 原始序列中比基数大的值放置到基数右侧,比基数小的值放置到基数左侧

  • 将列表中第一个元素设定为基准数字,赋值给mid变量,然后将整个列表中比基准小的数值放在基准的左侧,比基准大的数字放在基准右侧。然后将基准数字左右两侧的序列在根据此方法进行排放。

  • 定义两个指针,low指向最左侧,high指向最右侧

  • 然后对最右侧指针进行向左移动,移动法则是,如果指针指向的数值比基准小,则将指针指向的数字移动到基准数字原始的位置,否则继续移动指针。

  • 如果最右侧指针指向的数值移动到基准位置时,开始移动最左侧指针,将其向右移动,如果该指针指向的数值大于基准则将该数值移动到最右侧指针指向的位置,然后停止移动。

  • 如果左右侧指针重复则,将基准放入左右指针重复的位置,则基准左侧为比其小的数值,右侧为比其大的数值。

 

思路:

  • 一排数字(这些数字最好不要随便拿因为有时候会出bug),快排是所有排序算法里面速度最快的.

  • 一排数字,先取第一个数字作为基数,这样第一个位置就空了,然后定义两个变量low和high,low暂时定义为0,high暂时为列表的长度(其实low和high就是索引)

  • 先从右边起,将high对应的数字与基数比大小,如果high比基数大,那么high对应的数字的位置就不动,将high重新赋值(向左挪一个数字),如果high比基数小,那么就将这个数字赋值给low,而这里的high对应的数字就算是空了,再往下,就从low这边开始算,刚赋值给了low一个数字(从右边第二个数字移动到了最左端第一个数字),现在再将low向右移动一个数字,将最左端第二个数字赋值给low,再把low和基数对比,如果low小于基数,就继续将low向左移动,如果low大于基数,就将low对应的数字移动到刚才high对应的数字的位置上去,移动之后,再从high向左推算,后面依次继续.

  • 直到low等于了high,这俩索引最终就指向了基数,基数左边都比基数小,右边都比基数大,但是左边一半(可能左右数字个数不一样多)和右边一半还没排序,左边一半继续用刚才的方法,左端low定为0,右端high定为基数左边的第一个数字的索引(就是刚才low和high),右边一半也像刚才的方法,左边的low数字定位为基数右边第一个数字的索引,high就用列表总长度,和最初的一样,再继续下去迭代.直到出现low>high,迭代才停止. 

 

#加入递归后完整的快速排序代码
def sort(alist,left,right):
    #low&hight表示的序列的起始位置的下标
    low = left
    high = right 
    
    #结束递归的条件
    #这里有点难理解,只要记住到了极点出现low小于high就是递归结束的时候,不加他就会无限循环
    if low > high:
        return
    
    mid = alist[low] #基数
    
    while low < high:
        #将原始序列中比基数小的值放置在基数的左侧,比基数大的值放置在基数的右侧
        #注意:一开始的时候先将high向左偏移
        #向左偏移
        while low < high:
            if alist[high] < mid: #high小于基数
                alist[low] = alist[high]
                break
            else:#基数如果小于了high,将high向左偏移
                high -= 1 #high想左偏移了以为
        #low向右偏移
        while low <high:
            if alist[low] < mid:#如果low小于mid则让low想右偏移
                low += 1#让low向右偏移一位
            else:
                alist[high] = alist[low]
                break
    if low == high:#low和high重复,将基数赋值到low或者high的位置,
        alist[low] = mid #alist[high] = mid,每循环一次(将mid值左半部分和右半部分已经区分开了,low索引左边的比mid小,low索引右边的比mid大,注意此时low和high相等,这时候就要把low索引对应的值写成mid最早的值,即是中间值)
    
    #指定操作作用到基数左侧的子序列中
    sort(alist,left,low-1)
    #指定操作作用到基数右侧的子序列中
    sort(alist,high+1,right)
    return alist


alist = [66,13,51,76,81,26,57,69,23]
print(sort(alist,0,len(alist)-1))

 

代码二:

#你选择列表的第一个数字,就是索引为left的数字为中间值,你现在要找他排序后正确的索引位置
def
partition(li, left, right):#求出中间值的索引,最终就是left=right时的索引 tmp = li[left] while left < right: while left < right and li[right] >= tmp: #从右面找比tmp小的数 right -= 1 # 往左走一步 li[left] = li[right] #把右边的值写到左边空位上 # print(li, 'right') while left < right and li[left] <= tmp: left += 1 li[right] = li[left] #把左边的值写到右边空位上 # print(li, 'left') li[left] = tmp # 把tmp归位 return left def quick_sort(li, left, right):#把索引带进递归进行计算 if left<right: # 至少两个元素 mid = partition(li, left, right) quick_sort(li, left, mid-1) quick_sort(li, mid+1, right) lst=[3,5,7,8,9,10,6,1,2,4,0] quick_sort(lst,0,len(lst)-1) print(lst)

 

 

5.希尔排序

时间复杂度:有很多种情况,直到现在也没有具体的一种结论,像咱们这种一直除2的话:O(n/(2ⁿ)),如果遇到特殊情况那么时间复杂度就是O(n²) 

思路:

   希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本,该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量(gap)”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率比直接插入排序有较大提高。下面借用一下网上找的图:

 

 

 

#希尔排序:插入排序是一种特殊的希尔排序
def sort(alist):
    gap = len(alist) // 2
    while gap >= 1:
        #将增量设置成gap
        for i in range(gap,len(alist)):
            while i > 0 :
                if alist[i] < alist[i-gap]:
                    alist[i],alist[i-gap] = alist[i-gap],alist[i]
                    i -= gap
                else:
                    break
        gap //= 2
    return alist

alist = [4,3,6,1,9,2]
print(sort(alist))

 

 

6.归并排序

python中的sort方法内部是基于归并排序和插入排序的,并做了一些优化,用C写的

 时间复杂度:O(nlogn),每次都要分层,一层分成2组,2组分4组,...这些层就是logn(虽说分解是一个logn,合并是一个logn,但是这里只算一个),每一层,都需要遍历所有元素,这就是n

空间复杂度:O(n),它是指你除了本身列表占用的内存外,你另外开辟的内存就是空间复杂度,你开个列表装原来的列表整体所有元素,这就是n的长度,所以是O(n)

写法一:

  • 归并排序采用分而治之的原理:
    • 将一个序列从中间位置分成两个序列;
    • 在将这两个子序列按照第一步继续二分下去;
    •  直到所有子序列的长度都为1,也就是不可以再二分截止。这时候再两两合并成一个有序序列即可。

      如何合并?

        下图中的倒数第三行表示为第一次合并后的数据。其中一组数据为 4 8  ,  5 7。该两组数据合并方式为:每一小组数据中指定一个指针,指针指向每小组数据的第一个元素,通过指针的偏移指定数据进行有序排列。排列情况如下:

      1. p1指向4,p2指向5,p1和p2指向的元素4和5进行比较,较小的数据归并到一个新的列表中。经过比较p1指向的4会被添加到新的列表中,则p1向后偏移一位,指向了8,p2不变。

      2.p1和p2指向的元素8,5继续比较,则p2指向的5较小,添加到新列表中,p2向后偏移一位,指向了7。

      3.p1和p2指向的元素8,7继续比较,7添加到新列表中,p2偏移指向NULL,比较结束。

      4.最后剩下的指针指向的数据(包含该指针指向数据后面所有的数据)直接添加到新列表中即可。

借用一下网上找的图:

 

 

def merge_sort(alist):
    n = len(alist)
    #结束递归的条件
    if n <= 1:
        return alist
    #中间索引
    mid = n//2

    left_li = merge_sort(alist[:mid])
    right_li = merge_sort(alist[mid:])

    #指向左右表中第一个元素的指针
    left_pointer,right_pointer = 0,0
    #合并数据对应的列表:该表中存储的为排序后的数据
    result = []
    while left_pointer < len(left_li) and right_pointer < len(right_li):
        #比较最小集合中的元素,将最小元素添加到result列表中
        if left_li[left_pointer] < right_li[right_pointer]:
            result.append(left_li[left_pointer])
            left_pointer += 1
        else:
            result.append(right_li[right_pointer])
            right_pointer += 1
    #当左右表的某一个表的指针偏移到末尾的时候,比较大小结束,将另一张表中的数据(有序)添加到result中
    result += left_li[left_pointer:]
    result += right_li[right_pointer:]

    return result

alist = [3,8,5,7,6]
print(merge_sort(alist))

 

写法二: 

思路:将两边分别有序(升序逆序要最好一致),的列表合并成一个有序的列表.

i和mid+1两个索引对应的值对比大小,谁小就把谁拿出来,然后空了的那一个加1,再继续比,直到i=mid或者mid+=j就停止,等于之后,那一半的数字就空了,就把另一半的数字添加到刚才新建的列表中就好了.

 

 

 

#假设你现在有这样的列表,两边分别都已经排好了顺序,执行下面的函数,就能排好序了
def merge(li, low, mid, high):
    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执行完,肯定有一部分没数了
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high+1] = ltmp #用切片,往里面写值,这里没用0到n-1是因为后面要用递归

li = [2,4,5,7,1,3,6,8]
merge(li, 0, 3, 7)
print(li)

 

思路:

  ①分解:将列表越分越小,直到分成生一个元素

  ②终止条件:一个元素,并且该列表是有序的

  ③:合并:将两个只含一个元素的列表进行合并,然后是含两个元素的列表合并,最终越来越大直至整个列表.

 

参考下图:

 

 

 

完整代码:

def merge(li, low, mid, high):
    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执行完,肯定有一部分没数了
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high+1] = ltmp #用切片,往里面写值,这里没用0到n-1是因为后面要用递归

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)


li = list(range(1000))
import random
random.shuffle(li)
print(li)
merge_sort(li, 0, len(li)-1)
print(li)

 

7.顺序查找

lst=[12,4325345,45,65,3425,76,90,23,434,456,567,56,]

def sc(lis,val): #lis是要查的列表,val是要查的元素,看元素在不在这个列表中
    for i,v in enumerate(lis): #从头到尾一个一个找,如果有对应的值,就返回索引,如果没有就返回'没有'
        if v==val:
            return i
    else:
        return '没有'

print(sc(lst,90))

 

 

8.二分法查找:

时间复杂度:O(logn) 一直除二

前提:必须是有序的序列才能用二分查找

def dichotomy_search(li, val):#li是查找的序列,val是要找的值
    left = 0
    right = len(li) - 1
    while left <= right:    # 候选区有值
        mid = (left + right) // 2  #mid就是你要找的值所对应的索引,这里每次都等于left和right中间值
        if li[mid] == val:
            return mid
        elif li[mid] > val: # 待查找的值在mid左侧
            right = mid - 1
        else: # li[mid] < val 待查找的值在mid右侧
            left = mid + 1
    else:
        return None

li = list(range(100000000))
dichotomy_search(li, 38900000)

对比普通查找与二分法查找时间(用了装饰器):

 

import time

#计算代码运行时间的装饰器
def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
     #time.sleep(2) #如果时间太短可能显示的时间是0.0,暂停2秒,最后在结果中自己减2秒即可
        result = func(*args, **kwargs)
        t2 = time.time()
        # print(t1)
        # print(t2)
        print("%s running time: %s secs." % (func.__name__, t2 - t1))
        return result

    return wrapper

#普通查找
@cal_time
def linear_search(li, val):
    for ind, v in enumerate(li):
        if v == val:
            return ind
    else:
        return None


#二分法查找(一定最快)
@cal_time
def binary_search(li, val):
    left = 0
    right = len(li) - 1
    while left <= right:    # 候选区有值
        mid = (left + right) // 2
        if li[mid] == val:
            return mid
        elif li[mid] > val: # 待查找的值在mid左侧
            right = mid - 1
        else: # li[mid] < val 待查找的值在mid右侧
            left = mid + 1
    else:
        return None

li = list(range(100000000))
linear_search(li, 38900000)
binary_search(li, 38900000)

 

小总结:

low方法:冒泡,插入,选择

NB方法:快排,堆排,归并

NB三人组(快排,堆排,归并):

  ①时间复杂度都是nlogn

  ②运行时间:

    快排<归并<堆排

  ③缺点

    快排:极端情况下效率低

    归并:需要额外的内存开销

    堆排:再快的排序算法中相对较慢,运行时间长

 

 

 

注意:

  快排中如果用递归会消耗空间,一层就是1,递归logn层,空间复杂度就是logn,如果到了最坏情况要走n层,时间复杂度是n²,所以空间复杂度就是n.

 

9.计数排序

时间复杂度为:O(n) 其实是O(n)+O(n)

注意:  

  ①这种排序方法有前提,必须已知该列表数字的极值(范围,例如0-100),他的运算时间要比sort短

  ②它主要处理的数字有很多重复值,例如:该列表中右10个2,23个4,40个8等等

  ③他消耗内存,需要新建个空列表,如果就3个数,范围是1-1亿,那就很麻烦了

 

代码:

 

# 一堆数字,范围是在100以内,要求从小到大排序

def count_sort(li, max_count=100):
    count = [0 for _ in range(max_count+1)]#新建个列表与原来的列表最大值等长,并且里面的元素均为0
    for val in li:
        count[val] += 1#有一个对应位置的数,对应位置就加一
    li.clear()#清空原来的列表
    for ind, val in enumerate(count):#索引ind就是数字,值val就是对应数字的个数
        for i in range(val):
            li.append(ind)

import random
li = [random.randint(1,100) for _ in range(10000)]#随机生成1-100的数字10000个
print('原始:',li)
count_sort(li)
print('排序:',li)

 

 

10.桶排序(用的不是很多)

时间复杂度:

  取决于原列表中数据的分布,也就是对不同数据排序时采取不同的分桶策略

  平均时间复杂度:O(n+k)k是一个小桶的长度

  最坏情况时间复杂度:O(n²k)例如n个数字,n-1个数字都在一个桶,其他的桶里只有一个数字

 

空间复杂度:

  O(nk) 你需要新建空列表(像桶一样里面装多个小桶)来装你原来的数据

桶排序是在计数排序之上发展而来,把一堆数字(最好知道大部分数字的所在范围最好),然后把一个大区间分成多个小区间,每个小区间都当成计数排序那样来算,小区间都排序后,最后把所有的小区间按照顺序加到一起就成了桶排序.

代码:

# 桶排序

import random


def bucket_sort(li, n=100, max_num=10000):#n表示把10000分成几个桶,max_num是列表中这些元素最大的数是10000
    buckets = [[] for _ in range(n)] # 创建桶,一个大列表,里面套着很多空列表
    for var in li:
        i = min(var // (max_num // n), n-1)
        # i 表示var放到几号桶里,因为最后一个桶的最后一个数字是9999,
        # 而10000超了,所以这里取最小,当你到最后一个数字时,也让你放到最后一个桶里
        #这里你再有大于10000的数字也会自然放到最后一个桶里,不会在乎超过范围而排不了了
        buckets[i].append(var) # 把var加到桶里边
        # 保持桶内的顺序
        for j in range(len(buckets[i])-1, 0, -1):
            if buckets[i][j] < buckets[i][j-1]:
                buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]
            else:
                break
    sorted_li = []
    for buc in buckets:
        sorted_li.extend(buc)
    return sorted_li


li = [random.randint(0,10000) for i in range(100000)]
l1=[99080,65432,123123321,55555,666666,10001,2143234,67867,12321]
#l1中的几个数字超了10000,但是也都能放到最后一个桶里面来进行排序,
# 因为上面的min内置函数把他们都放到了最后一个桶里面了
li.extend(l1)
print('初始:',li)
li = bucket_sort(li)
print('排序后:',li)

 

 

11.基数排序

时间复杂度:O(kn)

他和快排比时间快慢,与k有关或者说是数字分布有关,快排确实快,但这里的k相当于是lgn 是以10为底数,但是快排nlogn,他的log是以2为底,所以,如果k不是很大,或者说列表中最大数字不是很大(数字数量没关系多少都无所谓),这时基数排列要比快排还要快.

但是基数排序要占空间,快排不占空间.

空间复杂度:O(k+n)

(k是最大数字的位数)

 

思路:

  他的思路类在桶排序基础之上,给你一个列表,把这些数字按照个位从小到大排序(0,1,2,3...9),然后将排序完的数列拿出来,再按照十位如刚才排序...直到最大数字的最高位也排序过,这样拿到的列表就是排好序的了

 

注意:

  这里不需要对比某两个数字的大小关系,只需要按照顺序多次排数,就可以把列表顺序排好,他的桶本身是有顺序的.

 

代码:

# 基数排序

def radix_sort(li):
    max_num = max(li) # 先找到这个列表中的最大值,最大值 9->1, 99->2, 888->3, 10000->5
    it = 0
    while 10 ** it <=max_num:#其实这里可以用log()函数取10的对数,其实就是判断你最大数字有几位
        buckets = [[] for _ in range(10)]
        #每次排序都要减建10个桶,因为是十进制,每一位都要排序排序,每一位上都是0,1,2,3...9
        for var in li:#这里是循环数列中的每一个数字
            # 987 it=1  987//10->98 98%10->8;    it=2  987//100->9 9%10=9
            digit =(var // 10 ** it) % 10
            #这里根据it的值来确定这次循环取的是哪一位的数字,%10就是除以10取余数
            buckets[digit].append(var)
        # 分桶完成
        li.clear()#清空原列表
        for buc in buckets:
            li.extend(buc)#安找0,1,2,...9的顺序将每个桶的数字都添加到空列表中即可
        # 把数重新写回li,只要没超过最大数字就一直向下走一位并排序
        it += 1

import random
li = [random.randint(0,1000) for _ in range(10000)]
random.shuffle(li)
print('原数据:',li)
radix_sort(li)
print('排序完的数据:',li)

 

12.排序练习题:

第一题:

 

# 给两个字符串:s和t,判断t是不是s重新排列得到的字符串
# s='ana'
# t='aan'
# 返回true
#
s='cat'
t='wwe'
# 返回false

# 法一:
def xx(s,t):
    ss=list(s)
    tt=list(t)
    ss.sort()
    tt.sort()
    return  ss==tt
print(xx(s,t))


# 法一(简写):
def xx(s,t):
    return  sorted(list(s))==sorted(list(t))
print(xx(s,t))


# 法二:
def xx(s,t):
    dict1={} #记录样式: {'a':1,'b':2}某个字母出现的次数用他的值来表示
    dict2={}
    for i in s:
        dict1[i]=dict1.get(i,0)+1
        #get的意思是字典里有i这个键就获取他的值,如果没有这个键(i),就写个0放进去,再取出来时+1
    for i in t:
        dict2[i]=dict2.get(i,0)+1
    return dict1==dict2
print(xx(s,t))

 

第二题:

 

# 给一个m*n的二维列表lst,查找一个数k是否在其中
# m*n的列表有以下特性:
# ①每一行的列表都已经从左到右顺序排好
# ②每一行第一个数都比上一行的最后一个数大
lst=[
    [1 , 3, 5, 7],
    [10,11,16,20],
    [23,30,34,50]
]
k=5
# 法一:
def xx(l,k):
    for l1 in l:
        if k in l1:
            return True
    return False

print(xx(lst,k))


# 法二:
def xx(l,k):
    h=len(l)#相当于是m,表示有多少行
    if h==0:
        return False #给的列表是空的,什么也找不到
    w=len(l[0])#相当于是n,表示有多少列
    if w==0:
        return False
    left =0 #第一个索引
    right =w*h-1#列表最后一个索引
    while left <=right:
        mid=(left+right)//2
        i=mid//w#求行数
        j=mid%w
        if l[i][j]==k:
            return True
        elif l[i][j]>k:
            right=mid-1
        else:
            left=mid+1
    else:
        return False
print(xx(lst,k))

 

第三题:

# 给一个整数k,和一个列表lst(注意:这里的列表不一定是有序的),在列表中找到俩数的和为k,并返回这俩数的索引[i,j]
lst=[1,2,3,4,5,0,9,7]
k=3
# 返回[0,1]

# 法一:
def xx(l,m):
    n=len(l)
    for i in range(n):
        for j in range(i):
            #第二个for循环如果写range(n),那么会循环两次,浪费时间,并且会出现重复的情况
            #如返回[1,2]和[2,1],还有就是[3,3]正好找的数是索引3的树的二倍
            #这里写i表示第二次只找i之前的数字和i相加,不会重复
            #如果写range(i+1,n)那就是找i后面的数字和i相加,也不会重复
            if l[i]+l[j]==m:
                return sorted([i,j])
print(xx(lst,k))

# 法二:

# 用二分查找辅助

def binary_serch(l,left,right,val):
    while left <= right:#保证查找的地方有值
        mid=(left+right)//2
        if l[mid][0]==val:
            return mid
        elif l[mid][0]>val:#查找的值在mid的左边
            right=mid-1
        else:             #l[mid][0]<val查找的值在mid的右边
            left=mid+1
    else:
        return '没有'

def xx(l,k):
    new_list=[[num,i] for i , num in enumerate(l)]#将原来的列表中的元素和他的索引存到一起
    new_list.sort(key=lambda  x:x[0])#用new_list中的num来排序
    for i in range(len(new_list)):
        a=new_list[i][0]#找到其中一个值
        b=k-a #用减法去找b
        if b>=a:#b大于a,就在a的右边的部分用二分查找,来找b
            j=binary_serch(new_list,i+1,len(new_list)-1,b)
        else:#b小于等于a,就在a的左边找b
            j = binary_serch(new_list, 0,  i- 1, b)

        if j: #找到b就结束了
            return sorted([new_list[i][1],new_list[j][1]])

print(xx(lst,k))

 

posted @ 2020-04-20 20:35  圣君灬七夜  阅读(631)  评论(0编辑  收藏  举报