当前位置:   article > 正文

数据结构与算法【Python实现】(一)查找和基础排序_数据结构python 查找排序

数据结构python 查找排序

一、汉诺塔问题

  1. def hanoi(n,a,b,c): #从a经过b移动到c
  2. if n>0:
  3. hanoi(n-1,a,c,b) #从a经过b移动到c
  4. print("盘%d moving from %s to %s" %(n,a,c))
  5. hanoi(n-1,b,a,c) #从b经过a移动到c
  6. hanoi(2,'A','B','C')

递推式 h(x)=2h(x-1)+1 约等于2的n次方

二、查找

1、顺序查找

  1. def linear_search(li,val):
  2. for ind,v in enumerate(li):
  3. if v == val:
  4. return ind
  5. else:
  6. return None

2、二分法查找

  1. def binary_search(li,val):
  2. left = 0
  3. right = len(li) - 1
  4. while left <= right: #候选区有值
  5. mid = (left + right) // 2 #整除2
  6. if li[mid] == val:
  7. return mid
  8. elif li[mid] > val: #待查找的值在mid左侧
  9. right = mid - 1
  10. else: #待查找的值在mid右侧
  11. left = mid + 1
  12. else:
  13. return None #没有查找到

时间复杂度:O(logn)

三、排序

初级排序:

1、冒泡排序

原地排序,不需要占用新的内存空间。

列表每两个相邻的数,如果前面比后面大,则交换这两个数;

一趟排序完成后,则无序区减少一个数,有序区增加一个数。

  1. def bubble_sort(li): #升序排列
  2. for i in range(len(li)-1): #第i趟 从0开始
  3. exchange = False
  4. for j in range(len(li)-i-1): #指针,每一趟n-i-1个位置
  5. if li[j] > li[j+1]:
  6. li[j],li[j+1] = li[j+1],li[j]
  7. exchange = True
  8. print(li)
  9. if not exchange:
  10. return

时间复杂度 O(n^2)

2、选择排序

一趟排序记录最小的数,放到第一个位置

再一趟排序记录列表无序区最小的数,放到第二个位置

……直到排序结束

  1. def select_sort(li):
  2. for i in range(len(li)-1): #第i趟
  3. min_loc = i
  4. for j in range(i+1,len(li)): #遍历范围
  5. if li[j] < li[min_loc]: #如果是最小的,保存下标到min_loc
  6. min_loc = j
  7. li[i],li[min_loc] = li[min_loc],li[i] #最小值和无序区第一个数交换位置
  8. print(li)

时间复杂度 O(n^2)

3、插入排序

初始时手里(有序区)只有一张牌

每次从无序区摸一张牌,插入到手里已有牌的正确位置

  1. def insert_sort(li):
  2. for i in range(1,len(li)): #表示摸到的牌的下标
  3. tmp = li[i]
  4. j = i - 1 #j指的是手里的牌的下标 从右往左遍历
  5. while li[j] > tmp and j >= 0: #直到找到比摸到的牌小的牌
  6. li[j+1] = li[j] #向右移动一位
  7. j -= 1
  8. li[j+1] = tmp
  9. print(li)

时间复杂度O(n^2)

进阶排序:

4、快速排序

取第一个元素p(第一个元素),使元素p归位

列表被p分程两部分,左边都比p小,右边都比p大

递归完成排序

  1. def partition(li,left,right):#归位函数 一次快排
  2. tmp = li[left]
  3. while left < right:
  4. while left < right and li[right] >= tmp: #从右面开始找到比tmp小的数 放到left的空位
  5. right -= 1 #往左走一位
  6. li[left] = li[right] #把右边的值放入左边空位
  7. while left < right and li[left] <= tmp:
  8. left += 1
  9. li[right] = li[left] #把左边的值放入右边空位
  10. li[left] = tmp #把原来的值归位
  11. return left #放回mid,也就是left或right的值
  12. def quick_sort(li,left,right):
  13. if left < right: #至少有两个元素
  14. mid = partition(li,left,right)
  15. quick_sort(li,left,mid-1)
  16. quick_sort(li,mid+1,right) #递归调用mid分成的左右两部分

时间复杂度:O(nlogn)   logn以2为底

最坏情况:O(n^2)  选取第一个/最后一个数为枢轴的情况下,当表已经有顺序(正序/倒序),或列表所有元素相同时

避免方式:可以先把列表打乱,或不选取第一个数为枢轴 改为随机选取第一个要归位的数(枢轴)。但是如果输入的数组最大元素(或者最小元素)被选为枢轴,那最坏的情况就又来了。

虽然是原地排序,但递归需要用到系统栈的空间

空间复杂度: 平均情况 O(logn)  最坏情况 O(n)

5、堆排序

二叉树的存储方式:链式存储方式、顺序存储方式

顺序存储方式:

        父节点 i ,左孩子节点 2i+1, 右孩子节点 2i+2

        孩子节点 i ,父节点 (i-1) // 2

堆:是一种特殊的完全二叉树结构

        大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大

        小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小

一次向下调整性质:节点的左右子树都是堆,但自身不是堆,可以通过一次向下调整来将其变成一个堆

堆排序过程:

(1)建立堆(满足任一节点都比其孩子节点大 / 小)

(2)开始按大小出数:得到堆顶元素,为最大元素

(3)去掉堆顶,将堆最后一个元素放到堆顶,此时满足一次向下调整的条件,可通过一次调整重新使堆有序

(4)堆顶元素为第二大元素

(5)重复步骤三,直到堆变空

建立堆:

先看最后一个非叶子节点,一层一层往上看

  1. def sift(li,low,high): #一次向下调整算法
  2. """
  3. :param li: 列表
  4. :param low: 堆的堆顶位置 (根节点)
  5. :param high: 堆的最后一个元素位置
  6. :return:
  7. """
  8. i = low #i最开始指向根节点
  9. j = 2 * i + 1 #j开始是左孩子
  10. tmp = li[low] #把堆顶存起来
  11. while j <= high: #只要j位置有数 就循环
  12. if j+1 <= high and li[j+1] > li[j]: #如果右孩子存在 比左孩子大 把j指向右孩子
  13. j = j + 1 #把j指向右孩子
  14. if li[j] > tmp:
  15. li[i] = li[j] #把大的数移到i的位置
  16. i = j #i往下看一层
  17. j = 2 * i + 1
  18. else: #tmp更大,把tmp放到i的位置
  19. li[i] = tmp #把tmp放到某一层父节点的位置
  20. break
  21. else:
  22. li[i] = tmp #j>high后,把tmp放到叶子节点
  23. def heap_sort(li):
  24. n = len(li)
  25. #建立堆
  26. for i in range((n-2)//2,-1,-1):
  27. #i代表建立堆的时候调整的部分的根的下标,从n-1的根节点开始,倒序
  28. sift(li,i,n-1) #low是调整部分的根节点,将high直接放到最后一个叶子节点
  29. #建堆完成了 开始出数
  30. for i in range(n-1,-1,-1):
  31. #i一直指向堆的最后一个数
  32. li[0],li[i] = li[i],li[0] # 最后一个数与堆顶交换
  33. sift(li,0,i-1) #对整个堆调整 low是0,当前最后一个数是i-1,i-1是新的high
  34. #排序完成
  35. li = [i for i in range(100)]
  36. import random
  37. random.shuffle(li)
  38. print(li)
  39. heap_sort(li)
  40. print(li)

时间复杂度:sift过程复杂度折半,为O(logn);整个堆排序O(nlogn)

实际表现:快速排序好于堆排序

Python堆排序内置模块:heapq

常用函数:

        heapify(x):建堆(小根堆)

        heappush(heap,item):为heap增加元素

        heappop(heap):每次弹出一个最小的数

eg:

  1. import heapq #q->queue 优先队列(小的先出/大的先出)
  2. import random
  3. li = [i for i in range(100)]
  4. random.shuffle(li)
  5. print(li)
  6. heapq.heapify(li) #建堆
  7. print(li)
  8. for i in range(len(li)):
  9. print(heapq.heappop(li),end=',')

topk问题:有n个数,设计算法得到前k大的数(k<n)

解决思路:

        排序后切片 O(nlogn)

        冒泡/插入/选择排序 O(kn)

使用堆排序:复杂度 O(nlogk)  更快

取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数

依次向后遍历原列表,对于列表中的元素,如果小于堆顶则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整

遍历列表所有元素后,倒序弹出堆顶

  1. #topk 根据堆排序改动
  2. def sift(li,low,high): #一次向下调整算法
  3. """
  4. :param li: 列表
  5. :param low: 堆的堆顶位置 (根节点)
  6. :param high: 堆的最后一个元素位置
  7. :return:
  8. """
  9. i = low #i最开始指向根节点
  10. j = 2 * i + 1 #j开始是左孩子
  11. tmp = li[low] #把堆顶存起来
  12. while j <= high: #只要j位置有数 就循环
  13. if j+1 <= high and li[j+1] < li[j]: #如果右孩子存在 比左孩子小 把j指向右孩子
  14. j = j + 1 #把j指向右孩子
  15. if li[j] < tmp:
  16. li[i] = li[j] #把小的数移到i的位置
  17. i = j #i往下看一层
  18. j = 2 * i + 1
  19. else: #tmp更大,把tmp放到i的位置
  20. li[i] = tmp #把tmp放到某一层父节点的位置
  21. break
  22. else:
  23. li[i] = tmp #j>high后,把tmp放到叶子节点
  24. def topk(li,k):
  25. heap = li[0:k]
  26. for i in range((k-2)//2,-1,-1):
  27. sift(li,i,k-1)
  28. #1、建堆
  29. for i in range(k,len(li)-1):
  30. if li[i] > heap[0]:
  31. heap[0] = li[i]
  32. sift(heap,0,k-1)
  33. #2、遍历所有元素
  34. for i in range(k-1,-1,-1):
  35. heap[0], heap[i] = heap[i], heap[0]
  36. sift(heap, 0, i - 1)
  37. #3、出数
  38. return heap
  39. import random
  40. li = list(range(100))
  41. random.shuffle(li)
  42. print(topk(li,10))

6、归并排序

一次归并:假设列表分为两段有序,将其合称为一个有序列表

归并排序——使用一次归并:

(1)分解:将列表越分越小,直至分成一个元素

(2)终止条件:只有一个元素的时候是一定有序的

(3)合并:将两个有序列表归并,列表越来越大

  1. def merge(li,low,mid,high): #列表分为两段
  2. i = low
  3. j = mid + 1 #两端的起始指针
  4. ltmp = [] #一个临时列表
  5. while i <= mid and j <= high: #只要左右两段都有数
  6. if li[i] < li[j]:
  7. ltmp.append(li[i])
  8. i += 1
  9. else:
  10. ltmp.append(li[j])
  11. j += 1
  12. #while执行完 两段中肯定有一部分没数了
  13. while i <= mid:
  14. ltmp.append(li[i])
  15. i += 1
  16. while j <= high:
  17. ltmp.append(li[j])
  18. j += 1
  19. li[low:high+1] = ltmp #切片右不包 把临时列表写会去
  20. def merge_sort(li,low,high):
  21. if low < high: #至少段中有两个元素,递归
  22. mid = (low + high) //2
  23. merge_sort(li,low,mid)
  24. merge_sort(li,mid+1,high)
  25. merge(li,low,mid,high)

 时间复杂度:一次归并O(n),分解+合并整体:O(nlogn)

空间复杂度:O(n)  开了临时列表存储;递归空间复杂度为O(logn),可由开的O(n)的了表抵消

快速排序 / 堆排序 / 归并排序总结:

时间复杂度都是O(n)

一般情况下,就运行时间而言:

        快读排序 < 归并排序 < 堆排序

三种排序算法的缺点:

        快速排序:极端情况下效率低

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

        堆排序:在快的排序算法中相对较慢

 稳定性:当两个元素值相同时,保证他们的相对位置不变

               <在列表中挨个移动顺序的都是稳定的,不挨个换顺序的是不稳定的>

Python使用的稳定排序 基于归并排序

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/55335
推荐阅读
相关标签
  

闽ICP备14008679号