查找/排序算法示例(基于Python)

查找

二分查找
import time

def binary_find(test_list, value):      #迭代法
   start_time = time.time()
   low = 0
   high = len(test_list) - 1
   while low <= high:
      mid = (low + high) // 2
      if test_list[mid] == value:
         print("binary find cost time:", time.time() - start_time)
         return 1
      if test_list[mid] < value:
         low = mid + 1
      else:
         high = mid - 1

def recur_find(test_list, value):       #递归法
   start_time = time.time()
   low = 0
   high = len(test_list) - 1
   mid = (low + high) // 2
   if test_list[mid] == value:
      print("recursion find cost time:", time.time() - start_time)
      return 1
   if test_list[mid] < value:
      recur_find(test_list[mid+1:], value)
   else:
      recur_find(test_list[:mid+1], value)

test_list = []
for each in range(9999999):
   test_list.append(each)

binary_return = binary_find(test_list, 166)
recur_return = recur_find(test_list, 166)

排序

对于排序来说,首先最简单同时也是效率最低的三个就是冒泡、选择和插入,其中冒泡是最简单和实用的,时间复杂度都是n^2级;然后快排、堆排和归并平均时间复杂度都是nlogn级,所以相对较快,而快排是这三个里最简单的,也是用的最多的排序,但一般情况下这三者快慢程度为:快排>归并>堆排,其中快排在极端下效率低,归并需要额外的内存开销,堆排相对较慢,而且复杂,但堆排也是最稳定的,不会因为极端情况而导致效率反差大。

冒泡排序

(顾名思义,就是从下往上,两个两个相邻的相比,小的就上去,大的就不变,比如把5,3,2,1,6,4,7从小到大排序,那么就5比3大,所以5和3对调,接着5和2相邻,5比2大,又和2对调,一直到6,比6小,所以第一轮排序后结果为:3,2,1,5,6,4,7)

import random

def bubble_sort(li):
   for i in range(len(li) - 1):
      for each in range(len(li) - i - 1):       #每一轮结束就有一个数已经排好,所以i轮可以减去多余的i
        if li[each] > li[each + 1]:
            li[each], li[each + 1] = li[each + 1], li[each]     #相邻两个交换

def bubble_sort_plus(li):       #对上面算法的优化
    for i in range(len(li) - 1):
        exchange = False      #判断是否本轮交换过,没交换说明已经排序完成
        for each in range(len(li) - i - 1):
            if li[each] > li[each + 1]:
                li[each], li[each + 1] = li[each + 1], li[each]
                exchange = True
        if not exchange:
            break          #排序已完成,直接退出,减少多余无用排序

li = list(range(1000))
li2 = li.copy()
random.shuffle(li)      #打乱列表
bubble_sort_plus(li)
print(li == li2)
选择排序

(每一轮找出最小的数,放在第一位)

import random

def select_sort(li):
   for i in range(len(li) - 1):
      min_li = i
      for j in range(i+1, len(li)):
      #因为range范围里最大值是len(li)-1,所以这里要到len(li),也就是在i+1到999之间的值比较
      #外面一层的for-1是因为最后一轮已经没必要了
         if li[j] < li[min_li]:
            min_li = j
      li[i], li[min_li] = li[min_li], li[i]
   print(li)

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
select_sort(li)
插入排序

(就是有一个有序区和无序区,依次把无序区的放到有序区对应位置里,类似打牌的时候那种感觉,一开始有一张牌,放手上,然后抽一张牌,然后比前一张大就放其左边,否则右边,依次循环,这里手上的牌就是有序区,抽牌的地方就是无序区)

import random

def insert_sort(li):
    for i in range(1, len(li)): # 第一个元素默认为有序区,每次遍历开始时,当前位置的左边都为有序区,右边为无序区
        j = i
        while (j - 1) >= 0 and li[j] < li[j - 1]:   # 当前元素当比左边的小时,则交换位置,并继续往前找,直到合适的位置停止
            li[j - 1], li[j] = li[j], li[j - 1]
            j -= 1

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
insert_sort(li)
print(li)

另一种写法

import random

def insert_sort(li):
    li_temp = [li.pop(0)]   #分一个新区用来插入数据并排序
    for each in li:
        for j in range(len(li)-1):
            if each <= li_temp[j]:  #遍历到比新数据小的位置则插入
                li_temp.insert(j, each)
                break
            elif j >= len(li_temp)-1:   #当超出新区长度时则说明遍历完成,直接在尾部插入
                li_temp.append(each)
                break
    print(li_temp)

li = []
for each in range(100):
   li.append(each)
random.shuffle(li)
print(li)
insert_sort(li)
快速排序

(取最左边的数,然后一轮下来要求左边的数都比他小,右边的都比他大,每轮开始时先在头尾都有一个指针,因为取了最左边的数A,所以第一个位置是空的,然后看右边的指针指向的数是否比取出来的那个数A小,小的话就把这数移到那个空位去,否则右边的指针往左移一位,继续比较是否小于A;然后如果右边的数移到了左边那个空位,那么右边那个位置就空了,此时就判断左边的指针是否大于数A,大于就移到那个空位去,否则左边的指针往右移一位,继续比较是否大于A,两边一直循环操作,直到两边指针指到一个地方,就完成一轮遍历)

import random

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)

def partition(li, left, right):
   temp = li[left]     #先取出最左边的数,然后最左边空了
   while left < right:
      while li[right] >= temp and left < right:
      #如果右边的数比取出的数大就指针往左移一位,还要判断left<right是为了防止左移过头了
         right -= 1
      li[left] = li[right]    #比取出的数大,然后放到空位上
      while li[left] <= temp and left < right:
         left += 1
      li[right] = li[left]
   li[left] = temp         #此时right和left指在一起,然后把取出的数放这里
   return left

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
quick_sort(li, 0, len(li) - 1)
print(li)
堆排序

(首先建一个堆,需为完全二叉树,然后要是一个大顶堆,即根节点的值要比子节点的大,比如下面这个,a就要比b、c大,b要比d、e大,那么根节点就是这里面的最大值,于是将其取出,此时顶点就空了,然后把堆的最后一个节点放到堆顶,比如这里是g,然后判断其是否比两个子节点大,如果是则g是最大值,把g取出,否则比较b和c谁大,大的和g换位置,假如b大,那么g和b对调,接着g再和子节点比较,假如比d小,那么d和g再换位置,就这样循环确定位置,然后最大的再取出,一直这样循环取出最大值即完成排序)

                                      a
                                     /  \
                                   b      c
                                  /  \   /  \
                                 d    e f    g

注:
将上面的堆按从上到下顺序存入数组当中,比如上面的就是abcdefg(地址从0开始),那么可以发现每个父节点和左子节点的关系是2n+1,和右子节点关系是2n+2

import random

def heap_sort(li):
    n = len(li)
    # 建堆(大顶堆)
    for i in range(((n >> 1) - 1), -1, -1):
        siftdown(li, i, n - 1)
    # 每次把当前堆的最大值移到末尾,然后堆大小-1,并让堆顶元素下溢
    while n > 0:
        n -= 1
        li[0], li[n] = li[n], li[0]
        siftdown(li, 0, n)

def siftdown(li, low, high):
    while low < high:
        # 左子节点
        lc = low * 2 + 1
        if lc >= high:
            break
        # 找到最大的子节点
        if lc + 1 < high and li[lc] < li[lc + 1]:
            lc += 1
        # 拿子节点和父节点进行比较
        if li[lc] > li[low]:
            li[lc], li[low] = li[low], li[lc]
            low = lc
        else:
            break

a = [i for i in range(100)]
b = a.copy()
random.shuffle(a)
heap_sort(a)
print(a == b)
归并排序

(该排序主要用于整个数组分为两个有序数组时,比如:1,3,4,5,2,6,7,8,那么前四个和后四个都是有序数组,此时就通过将建立两个指针,分别在这两个有序数组开头,然后进行对比,那个小就先把那个加入数组,比如最开始两个指针(A、B)分别指向1和2,然后比较,发现1小,所以1进入数组,指针A往右移动到3,3比2大,然后2进入数组,指针B指到6,…以此类推,最终可能有一边会有剩下的,但都排好序了,所以直接加进去即可),先参考下面代码,了解基本原理:
简单归并示例

import random

def merge(li, low, mid, high):
   i = low     #指向第一个数组开头
   j = mid + 1 #指向第二个数组开头
   temp = []
   while i <= mid and j <= high:   #两个数组都没排完序加入数组
      if li[i] <= li[j]:          #哪个小,哪个加入数组,然后指针往右一位
         temp.append(li[i])
         i += 1
      else:
         temp.append(li[j])
         j += 1
   while i <= mid:     #前面当有一个数组拍完序就到这里了,接下来如果是左边数组没排完,就依次加进数组
      temp.append(li[i])
      i += 1
   while j <= high:    #如果是右边数组没排完...
      temp.append(li[j])
      j += 1
   return temp

li = [1,2,5,7,3,4,8,9]  #前、后四个分别都是排好序的
print(li)
for each in range(len(li)):
   if li[each+1] > li[each]:    #当后一个比前一个大,即递增
      mid = each + 1        #找到第一个递增数组的最后一个数位置
      continue
   else:
      break
li = merge(li, 0, mid, len(li)-1) #中间位置指针就指向第四个,也就是第二个有序数组的前一位
print(li)

另一种写法:

import random

def merge_sort(left, right):
    divide(left, right)

def divide(left, right):
    if right - left < 2: # 只有一个元素的时候不用拆分
        return
    mid = (left + right) // 2
    divide(left, mid)   # 左边拆分
    divide(mid, right) # 右边拆分
    merge(left, mid, right) # 合并

def merge(left, mid, right):
    pl, pr = left, mid  # 合并数组左/右边一半起始位置
    t = li[left: mid+1] # 拷贝数组的左边一半
    ti = 0  # 拷贝数组比较的索引
    while ti < len(t) - 1:
        if pr < right and t[ti] > li[pr]:
            # 如果右边没有走到尽头并且右边的值小,则存入右边的数据
            li[pl] = li[pr]
            pr += 1
        else:
            # 如果右边已经存完了,或者右边的数据大,则存左边的
            li[pl] = t[ti]
            ti += 1
        pl += 1

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
merge_sort(0, len(li))
print(li)
归并算法实例

(上面的是归并基本思想,但实际上一般不会刚好出现由2个有序数组组成的情况,所以真正的归并算法原理应该是这样:将数组不断对半拆分,直至两个时就是最小的数组的,然后再排序,2个之间排好,然后合并成4个,再4个之间排好,一直合并到整个即可,过程当中就都是两边都是有序数组的情况了)

import random

def merge(li, low, mid, high):
   i = low     #指向第一个数组开头
   j = mid + 1 #指向第二个数组开头
   temp = []
   while i <= mid and j <= high:   #两个数组都没排完序加入数组
      if li[i] <= li[j]:          #哪个小,哪个加入数组,然后指针往右一位
         temp.append(li[i])
         i += 1
      else:
         temp.append(li[j])
         j += 1
   while i <= mid:     #前面当有一个数组拍完序就到这里了,接下来如果是左边数组没排完,就依次加进数组
      temp.append(li[i])
      i += 1
   while j <= high:    #如果是右边数组没排完...
      temp.append(li[j])
      j += 1
   li[low:high+1] = temp   #把排好的部分填进对应位置

def merge_sort(li, low, high):
   if low < high:  #拆分到2个时再递归,子递归里low=high,就不执行下面的了
      mid = (low + high) // 2     #对半拆分
      merge_sort(li, low, mid)    #当前数组的左半边递归拆分
      merge_sort(li, mid+1, high) #...右半边递归拆分
      merge(li, low, mid, high)   #对拆分后的单元进行排序

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
merge_sort(li, 0, len(li)-1)
print(li)
希尔排序

(其相当于插入排序的改进版,插入排序每次只能移动一位,而希尔排序则是通过分组进行插入排序从而提高效率,比如最开始设置一个间隔值gap等于数组长度的一半,每趟排序结束后都除2,那么排序时,把每组的第一个数进行排序,比如有数组:3,5,2,4,1,6,9,7,第一趟时gap=4,那么就把每组的第一个数3和1比较排序,结果是3和1对调,然后第二个数5和6比较排序,不变,第一趟结果为:1,5,2,4,3,6,9,7,然后接着gap=2,继续照前面的操作)

import random

def shell_sort(li):
   gap = len(li) // 2      #先分两半进行希尔排序,即左半边和右半边比较
   while gap > 0:
      for i in range(gap, len(li)):
         while i >= gap and li[i-gap] > li[i]:   #每组的同一个位置进行比较排序
            li[i], li[i-gap] = li[i-gap], li[i]
            i -= gap
      gap = gap // 2      #循环对半进行排序比较

li = []
for each in range(1000):
   li.append(each)
random.shuffle(li)
print(li)
shell_sort(li)
print(li)
计数排序

只能够对纯数值类型的数组进行排序,在特定情况效率非常高,原理是创建对应长度为max值+1大小的数组,然后给每一个计数,最后再添加回去,非in-place,是典型的空间换时间算法,代码如下:

def cs(li):
    """计数排序基础版"""
    tmp = [0] * (max(li) + 1)
    for i in li:
        tmp[i] += 1
    res = []
    for i in range(len(tmp)):
        if tmp[i] > 0:
            res.extend([i] * tmp[i])
    return res

对于纯数值情况下,时间复杂度基本取决于数组中的最大值

计数排序优化

上面的排序算法有个问题就是只支持正数,而且如果数组中的数值都特别大,那么也会造成空间浪费和效率的降低,因此我们在创建数组时可以在确定上界的同时也确定下界,代码如下:

def cs(li):
    """计数排序优化版,在创建数组前也确定数组的下界,当数据都特别大时,可以避免空间浪费问题,且可以避免负数无法排序的问题"""
    base = min(li)
    tmp = [0] * (max(li) + 1 - base)
    for i in li:
        tmp[i - base] += 1
    res = []
    for i in range(len(tmp)):
        if tmp[i] > 0:
            res.extend([i + base] * tmp[i])
    return res
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,723评论 0 33
  • 昨天妹妹在群里转发了一篇文章,内容是关于一位妻子在丈夫乘坐的飞机失事后,如何悲痛欲绝、如何日渐憔悴的感人记录。妹妹...
    静默心谷阅读 495评论 3 1
  • 一个人的生活 一个人做饭,一个人上班,无聊时看看手机,望着窗外发呆,阴雨绵绵的天气,不是我喜欢的,喜欢艳阳高照,阳...
    二月的萍阅读 143评论 0 0
  • 2018年4月19日 星期四 天气晴 今天又是一个匆忙的一天,很喜欢忙的感觉,因为一忙就会很专注...
    微笑哥张明涛阅读 62评论 0 0