当前位置:   article > 正文

蓝桥杯:算法很美 笔记 3.查找和排序(Python实现)_蓝桥杯 查找 排序

蓝桥杯 查找 排序

1.分治法介绍以及关键点解析

  • 分治法(divide and conquer, D&C)∶将原问题划分成若干个规模较小而结构与原问题一致的子问题﹔递归地解决这些子问题,然后再合并其结果,就得到原问题的解。

  • 容易确定运行时间,是分治算法的优点之一。

  • 分治模式在每一层递归上都有三个步骤一分解(Divide) :

  • 将原问题分解成一系列子问题;

  • 解决(Conquer):递归地解各子问题。若子问题足够小,则直接有解;

  • 合并(Cpmbine);将子问题的结果合并成原问题的解。

分治法关键点

  • 原问题可以一直分解为形式相同子问题,当子问题规模较小时,可自然求解,如一个元素本身有序

  • 子问题的解通过合并可以得到原问题的解

  • 子问题的分解以及解的合并—定是比较简单的,否则分解和合并所花的时间可能超出暴力解法,得不偿失

2.快速排序

快速排序算法:

  1. 分解︰数组A[p..r]被划分为两子数组A[p..q-1]和A[ q+1,r],使得A[q]为大小居中的数,左侧A[p..q-1]中的每个元素都小于等于它,而右侧A [ q+1,r]中的每个元素都大于等于它。其中计算下标q也是划分过程的一部分。

  1. 解决:通过递归调用快速排序,对子数组A[p ..q-1]和A[ q+1,r]进行排序

  1. 合并:因为子数组都是原址排序的,所以不需要合并,数组A[p..r]已经有序

    • 一遍单向扫描法:

  • 一遍扫描法的思路是,用两个指针将数组划分为三个区间

  • 扫描指针(scan_pos)左边是确认小于等于主元的

  • 扫描指针到某个指针(next_bigger_pos)中间为未知的,因此我们将第二个指针(next_bigger_pos) 称为未知区间末指针,末指针的右边区间为确认大于主元的元素

伪码实现

  1. QuickSort
  2. quickSort(A,p,r)
  3. if(p<r)
  4. q=partition(A,p,r)
  5. quickSort(A,p,q-1)
  6. quickSort(A,q+1,r)
  7. partition(A,p,r):
  8. pivot = A[p]
  9. sp =p+1 //扫描指针
  10. bigger =r //右侧指针
  11. while(sp<=bigger):
  12. if(A[sp]<pivot)/扫描元素小于主元,左指针右移
  13. sp++
  14. else
  15. swap(A,sp,bigger)//扫描元素大于主元,二指针的元素交换,右指针左移
  16. bigger--
  17. swap(A,p,bigger)
  18. return bigger
  1. nums = [5, 6, 4, 5, 3, 1, 8, 9, 7]
  2. def QuickSort(num):
  3. # 若列表长度为1,直接输出不用排序
  4. if len(num) <= 1:
  5. return num
  6. # 取数组的第一个数作为基值
  7. key = num[0]
  8. # 定义空列表用来储存大于/小于/等于基准值的元素
  9. llist, mlist, rlist = [], [], []
  10. # 定义空列表用来储存大于/小于/等于基准值的元素
  11. for i in range(0, len(num)): # 遍历列表,把元素归类到三个列表中
  12. if num[i] < key:
  13. llist.append(num[i])
  14. elif num[i] > key:
  15. rlist.append(num[i])
  16. else:
  17. mlist.append(num[i])
  18. # 对左右两个列表进行快排,拼接三个列表并返回
  19. return QuickSort(llist) + mlist + QuickSort(rlist)
  20. print(QuickSort(nums))

    • 双向扫描法:

双向扫描的思路是,头尾指针往中间扫3描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,继续扫描,直到左侧无大元素,右侧无小元素

  1. QuickSort2
  2. pivot = A[p]
  3. left = p + 1 // 扫描指针
  4. right = r // 右侧指针
  5. while (left <= right):
  6. { //left不停往右走,直到遇到大于主元的元素
  7. while(left <= right && A[left]<=pivot)left++; //循环退出时,left一定是指向第一个大于主元的位置
  8. while(left <= right && A[right]>pivot)right--; //循环退出时,right一定是指向最后一个小于等于主元的位置
  9. if(left <= right)
  10. swap(A,left,right);
  11. }
  12. // while退出时,两者交错,且right指向的是最后一个小于等于主元的位置,也就是主元应该呆的位置
  13. swap(A, p, right);
  14. return right;
  1. # QSort
  2. nus = [4, 5, 1, 2, 3, 5, 4, 1]
  3. # left,right分别为子数组中第一个元素和最后一个元素在原数组中的位置
  4. def QSort(left, right):
  5. # 边界条件
  6. if left >= right:
  7. return
  8. # 初始化左右指针的初始值
  9. l, r, key = left, right, nus[left]
  10. # 调整元素的位置
  11. while l < r:
  12. while l < r and nus[r] >= key:
  13. r -= 1
  14. nus[l] = nus[r]
  15. while l < r and nus[l] <= key:
  16. l += 1
  17. nus[r] = nus[l]
  18. # 把基准值赋给左右指针共同指向的位置
  19. nus[r] = key
  20. # 对左侧数组排序
  21. QSort(left, l-1)
  22. # 对右侧数组排序
  23. QSort(l+1, right)
  24. QSort(0, len(nus) - 1)
  25. print(nus)

3.工程实践中的其他优化

  • 三点中值法

  • 绝对中值法

  • 待排序列表较短时,用插入排序 (n≤8)

3.归并排序

  • 归并排序(Merge Sort)算法完全依照了分治模式

  • 分解:将n个元素分成各含n/2个元素的子序列;

  • 解决:对两个子序列递归地排序;

  • 合并:合并两个已排序的子序列以得到排序结果

  • 和快排不同的是

  • 归并的分解较为随意

  • 重点是合并、

归并过程分析

  1. '''归并排序'''
  2. def merge_sort(arr, left, right):
  3. if left == right:
  4. return
  5. mid = left + ((right - left) >> 1)
  6. merge_sort(arr, left, mid)
  7. merge_sort(arr, mid + 1, right)
  8. merge(arr, left, mid, right)
  9. def merge(arr, left, mid, right):
  10. help = []
  11. p1 = left
  12. p2 = mid + 1
  13. while p1 <= mid and p2 <= right:
  14. if arr[p1] <= arr[p2]:
  15. help.append(arr[p1])
  16. p1 += 1
  17. else:
  18. help.append(arr[p2])
  19. p2 += 1
  20. while p1 <= mid:
  21. help.append(arr[p1])
  22. p1 += 1
  23. while p2 <= right:
  24. help.append(arr[p2])
  25. p2 += 1
  26. arr[left:right + 1] = help
  27. ls = [7, 3, 2, 6, 0 , 1 , 5 , 4]
  28. merge_sort(ls, 0, len(ls) - 1)
  29. print(ls)

4.解题

题1:调整数组顺序使奇数位于偶数前面

1.新建一个整数数组,利用二分思想,遍历原数组,遇到奇数从首天剑,偶数从尾部添加,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。要求时间复杂度为O(n)。

2.快排思想,左指针位于首元素,右指针位于末元素,找到第一个奇,偶就对调位置,不需要额外空间。

3.遍历,依次寻找奇偶元素

  1. def exchange(nums):
  2. low=0
  3. high=len(nums)-1
  4. while low<high:
  5. if (nums[low]&1)==1:
  6. low+=1
  7. elif (nums[high]&1)==0:
  8. high-=1
  9. else :
  10. nums[low],nums[high]=nums[high],nums[low]
  11. return nums
  12. list1=[1,3,5,7,8,4,7]
  13. print(exchange(list1))
  1. class Solution:
  2. def fun1(self, array):
  3. """奇数在前,偶数在后,相对位置保持不变"""
  4. new_arry = []
  5. for i in range(len(array)):
  6. if array[i] & 1 == 1: # 二进制与操作,奇数(***1(奇数) & 0001 = 0001)
  7. new_arry.append(array[i])
  8. for j in range(len(array)):
  9. if array[j] & 1 == 0:
  10. new_arry.append(array[j])
  11. return new_arry
  12. def fun2(self, array):
  13. # sorted 默认降序排列
  14. return sorted(array, key=lambda x: x % 2, reverse=True)
  15. def fun3(self, array):
  16. """冒泡排序"""
  17. for i in range(len(array) - 1):
  18. flag = False
  19. for j in range(len(array) - 1 - i):
  20. if array[j] % 2 == 0 and array[j + 1] % 2 == 1:
  21. # change position
  22. array[j], array[j + 1] = array[j + 1], array[j]
  23. flag = True
  24. if flag is False:
  25. break
  26. return array
  27. return array

题2:第k个元素

以尽量高的效率求出一个乱序数组中按数值顺序的第K个元素值

利用快速排序的分区思想,执行完partition之后,左边元素小于q,右边大于q,q的位置为实际排序后的位置,如果它的序列为k,则p为所求元素,大于k,继续在左半部分查找,小于k,在右半部分查找

  1. def selectK(A, p, r, k):# k为要找第k个
  2. q=QSort(A,p,r)
  3. qK=q-p+1 # 在p~r中第几个
  4. if(k==qK):
  5. return A[q]
  6. elif(k<qK): #要找的小在左边找
  7. return selectK(A,p,q-1,k)
  8. else:
  9. return selectK(A,q+1,r,k-qK)
  10. A = [3, 9, 7, 6, 1, 2]
  11. k = selectK(A, 0, len(A) - 1, 6)
  12. print(k)

输入n个整数,找出其中最小的K个数。

  1. class Solution:
  2. def GetLeastNumbers_Solution(self, a, k):
  3. def partition(a, low, high):
  4. pivot = a[low] #枢轴为第一个元素
  5. while low < high:
  6. #从右向左找比枢轴元素小的数,移到左边(枢轴整好空一个位置出来,所以放到左边)
  7. while low < high and a[high] >= pivot:
  8. high -= 1
  9. a[low] = a[high]
  10. #从左向右找比枢轴大的数,移到右边的空位上(上一步右边一个数移到左边,空出一个位置)
  11. while low < high and a[low] <= pivot:
  12. low += 1
  13. a[high] = a[low]
  14. a[low] = pivot #最后low=high,枢轴就在这个位置
  15. return low #返回枢轴位置
  16. def find_k_small(a, low, high):
  17. pos = k-1
  18. if low < high:
  19. index = partition(a, low, high)
  20. if index < pos:
  21. index = find_k_small(a, index+1, high)
  22. if index > pos:
  23. index = find_k_small(a, low, index)
  24. return index
  25. if len(a) < k:
  26. return []
  27. if k-1 < 0:
  28. return []
  29. if k == len(a):
  30. a.sort()
  31. return a
  32. index = find_k_small(a, 0, len(a)-1)
  33. sub = a[:index+1]
  34. sub.sort()
  35. return sub

题3:超过一半的数字

数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。

  1. def over_half_list(list):
  2. # 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字
  3. len_list = len(list)
  4. dict = {}
  5. #转字典,方遍统计字数
  6. for i in list:
  7. dict[i] = list.count(i)
  8. #判断哪个key的value>len_list/2
  9. for k,v in dict.items():
  10. if v > len_list/2:
  11. print('所得到的数字是:%d,他的次数是%d,数组长度是%d'%(k,v,len_list))
  12. list = [2,3,4,56,7,4,4,5,4,4,3,4,4,23,4,2,4,4]
  13. over_half_list(list)-

方法二:

  1. '''
  2. >该题目就是要找到数组中的超过一半的数字
  3. >其实有一种最简单得方法就是将数组进行排序
  4. >数组中间的值一定是超过一半的数字
  5. >还有一种方法就是摩尔选票,什么意思?
  6. >就是一种不断抵消选票的原理
  7. >先假设第一个是最终的结果,
  8. >遍历第二个元素时,如果与第一个相同就将选票+1
  9. >否则将第一个人的选票抵消
  10. >最终有票数的就是最多的人
  11. '''
  12. class Solution:
  13. def majorityElement(self, nums):
  14. res = 0
  15. tickets = 0
  16. for num in nums:
  17. if tickets == 0:
  18. res = num
  19. if num == res:
  20. tickets += 1
  21. else:
  22. tickets -= 1
  23. return res
  24. nums=[1,2,3,4,5,7,7,7,7,7,7]
  25. a=Solution()
  26. print(a.majorityElement(nums))

题3扩展:寻找发帖“水王”

Tango是微软亚洲研究院的-一个试验项目。研究院的员工和实习生们都很喜欢在Tango上面交流灌水。传说,Tango 有一大“水王”,他不但喜欢发贴,还会回复其他ID发的每个帖子。**坊间风闻该“水王”发帖数目超过了帖子总数的一半。**如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?

与题三解法相同

水王增强

出现次数恰好为个数的一半,求出这个数

  1. '''
  2. 水王占总数的一半,说明总数必为偶数;
  3. 不失一般性,假设隔一个数就是水王的id,两两不同最后一定会消减为0
  4. 水王可能是最后一个元素,每次扫描的时候,多一个动作,和最后一个元素做比较,单独计数,计数恰好等于一半
  5. 如果不是,计数不足一半,那么去掉最后一个元素,水王就是留下的那个candidate
  6. '''
  7. def solution(arr):
  8. candidate = arr[0]
  9. nTimes = 0
  10. countOfLast = 0 #统计最后这个元素出现的次数
  11. N = len(arr)
  12. i=0
  13. while(i<N):
  14. #增加和最后一个元素比较的步骤
  15. if (arr[i] == arr[N - 1]):
  16. countOfLast += 1
  17. if (nTimes == 0):
  18. candidate = arr[i]
  19. nTimes = 1
  20. continue
  21. if (arr[i] == candidate):
  22. nTimes+=1
  23. else:
  24. nTimes-=1
  25. i+=1
  26. # 最后一个元素出现次数是n/2
  27. if (countOfLast == N / 2):
  28. return arr[N - 1]
  29. else:
  30. return candidate
  31. arr=[1,5,1,4,1,2,1,3]
  32. print(solution(arr))

题4:最小可用ID

在非负数组(乱序)中找到最小的可分配的id (从1开始编号),数据量1000000

  1. # O(N²) 暴力解法:从1开始依次探测每个自然数是否在数组中
  2. def find1(arr):
  3. i = 1
  4. while (i<=len(arr)): # [1,2,3] len=3
  5. if (i not in arr):
  6. return i
  7. i+=1
  8. return i
  9. # NlogN
  10. def find2(arr):
  11. arr.sort() # NlogN 先排序
  12. i = 0;
  13. while (i < len(arr)): # [1,2,3]
  14. if (i + 1 != arr[i]): # 不在位的最小的自然数
  15. return i + 1;
  16. i+=1
  17. return i + 1;
  18. ''''**
  19. * 改进1:
  20. * 用辅助数组
  21. * 遍历数组,如果对应值有,将辅助数组的值赋值为1,大于数组长度的值可以不管
  22. * 查找辅助数组,返回第一个0值
  23. '''
  24. def find3(arr):
  25. n = len(arr)
  26. helper =[0 for i in range(n+1)]
  27. i=0
  28. while(i<n):
  29. if (arr[i] < n + 1):
  30. helper[arr[i]] = 1;
  31. i+=1
  32. i=1
  33. while(i<n):
  34. if (helper[i] == 0):
  35. return i;
  36. i+=1
  37. return n + 1;
  38. ''''**
  39. 改进2,分区,递归
  40. 假设一个长度为100的数组
  41. [1,2,3,6,7,5,4,...,50,...,99,100] 利用快速排序思想,一次快排后,该元素在正确的位置上,左边的小,右边的大
  42. 分为以下三种情况;
  43. 1.中间值恰好为50时,表明数组左半区间数字元素紧凑,没有要找的缺少元素
  44. 2.中间值为大于50的值,表明数组左半区间有漏掉的元素
  45. '''
  46. # def find4(arr,l, r):
  47. # if (l > r):
  48. # return l + 1
  49. # midIndex = l + ((r - l) >> 1) #中间下标
  50. # q = Qsort(arr, l, r, midIndex - l + 1) #实际在中间位置的值
  51. # t = midIndex + 1 #期望值
  52. # if (q == t): #左侧紧密
  53. # return find4(arr, midIndex + 1, r)
  54. # else: #左侧稀疏
  55. # return find4(arr, l, midIndex - 1)
  56. arr=[1,2,3]
  57. print(find1(arr))
  58. print(find2(arr))
  59. print(find3(arr))

题5:合并有序数组

给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B。编写一个方法,将B合并入A并排序

一种简易方法:用arr2直接覆盖arr1中的0元素,然后进行排序

  1. class Solution:
  2. def merge(self, nums1, m, nums2, n):
  3. p1 = m - 1 # 两个数组指针都指向尾部
  4. p2 = n - 1
  5. p = m + n - 1 # 计算合并后总长度,尾指针指向位置,总共有3个指针
  6. while p1 >= 0 and p2 >= 0:
  7. if nums1[p1] < nums2[p2]:
  8. nums1[p] = nums2[p2]
  9. p2 -= 1
  10. else:
  11. nums1[p] = nums1[p1]
  12. p1 -= 1
  13. p -= 1
  14. nums1[:p2+1] = nums2[:p2+1]
  15. # P1为空,直接将P2剩下的粘过去
  16. # P2为空,不进行操作,P1元素不动
  17. a=Solution()
  18. arr1=[1,2,2,3,7,0,0,0]
  19. arr2=[3,4,5]
  20. a.merge(arr1,5,arr2,3)
  21. print(arr1)

题6:逆序对个数

一个数列,如果左边的数大,右边的数小,则称这两个数位一个逆序对。求出一个数列中有多少个逆序对。例如 5 2 就是一个逆序对

解题思路为二分归并排序,如果选左边第一个,说明左边第一个比右边第一个小,所以左边第一个比右半部都小,类似,如果选右边第一个,说明右边第一个比左边第一个小,所以右边第一个比左半部都小,此时产生逆序对,个数为左边剩余全部的个数 ,即mid - left + 1 [1,2,3,4,5] 个数为5-1+1=5

方法一:暴力

  1. # -*- coding:utf-8 -*-
  2. class Solution:
  3. def InversePairs(self, data):
  4. # write code here
  5. # 思路一:暴力解,两个循环
  6. # 运行超时,暴力解不行哦
  7. length = len(data)
  8. if(length == 0):
  9. return 0
  10. else:
  11. num = 0
  12. for i in range(length-1):
  13. point = data[i]
  14. for j in range(i,length):
  15. if(data[i]>data[j]):
  16. num += 1
  17. return num
  18. a=Solution()
  19. data=[5,3,2,1]
  20. print(a.InversePairs(data))

方法二:二分归并

  1. # -*- coding:utf-8 -*-
  2. class Solution:
  3. def InversePairs(self, data):
  4. # write code here
  5. num, new_list = self.mergeSort(data)
  6. return num%1000000007
  7. def mergeSort(self, data):
  8. # 逆序对个数
  9. InversePairsNum = 0
  10. # 归并过程
  11. def merge(left,right):
  12. # 合并时发现的逆序对个数
  13. InversePairsNum = 0
  14. result = [] # 保存归并后的结果
  15. i = j = 0
  16. while(i<len(left) and j<len(right)):
  17. if left[i] <= right[j]:
  18. result.append(left[i])
  19. i += 1
  20. else:
  21. result.append(right[j])
  22. j += 1
  23. # 当右边的元素被插入时,证明这个元素比左边的剩下的所有元素都小
  24. # 可以组成len(left)-i个逆序对
  25. InversePairsNum = InversePairsNum + (len(left)-i)
  26. result = result + left[i:] + right[j:] # 剩余的元素直接添加到末尾,大概率是空的
  27. return InversePairsNum, result
  28. #
  29. if len(data) <= 1:
  30. return 0, data
  31. else:
  32. mid = len(data)//2 # //是向下取整
  33. num_left, left = self.mergeSort(data[:mid])
  34. num_right, right = self.mergeSort(data[mid:])
  35. num_merge, new_list = merge(left, right)
  36. InversePairsNum = num_left + num_right + num_merge
  37. return InversePairsNum, new_list
  38. a=Solution()
  39. data=[5,3,2,1]
  40. print(a.InversePairs(data))

5.树、二叉树介绍

1.用数组表示二叉树:

根结点为0

左儿子 2i+1

右儿子 2i+2

父节点(i-1)/2

2.三种遍历方式

1.先序遍历(根左右)

若二叉树为空,则空操作;否则:

(1)访问根结点

(2)先序遍历左子树

(3)先序遍历右子树

2.中序遍历(左根右)

若二叉树为空,则空操作;否则:

(1)中序遍历左子树

(2)访问根结点

(3)中序遍历右子树

3.后序遍历(左右根)

若二叉树为空,则空操作;否则:

(1)后序遍历左子树

(2)后序遍历右子树

(3)访问根结点

先序:ABDEHCFIG

中序:DBHEAFICG

后序:DHEBIFGCA

  1. class TreeNode:
  2. def __init__(self, data) -> None:
  3. self.data = data # 数据
  4. self.left = None # 左子节点
  5. self.right = None # 右子节点
  6. def fixed_tree():
  7. """
  8. 返回固定二叉树结构
  9. :return:
  10. """
  11. a = TreeNode('A')
  12. b = TreeNode('B')
  13. c = TreeNode('C')
  14. d = TreeNode('D')
  15. e = TreeNode('E')
  16. f = TreeNode('F')
  17. g = TreeNode('G')
  18. h = TreeNode('H')
  19. i = TreeNode('I')
  20. a.left = b
  21. a.right = c
  22. b.left = d
  23. c.left = e
  24. c.right = f
  25. d.left = g
  26. d.right = h
  27. e.right = i
  28. return a
  29. def pre_order_traverse(_binary_tree):
  30. """
  31. 前序遍历
  32. :param _binary_tree: 二叉树
  33. :type _binary_tree: TreeNode
  34. """
  35. if _binary_tree is None:
  36. return
  37. print(_binary_tree.data, end=',')
  38. pre_order_traverse(_binary_tree.left)
  39. pre_order_traverse(_binary_tree.right)
  40. def in_order_traverse(_binary_tree):
  41. """
  42. 中序遍历
  43. :param _binary_tree: 二叉树
  44. :type _binary_tree: TreeNode
  45. """
  46. if _binary_tree is None:
  47. return
  48. in_order_traverse(_binary_tree.left)
  49. print(_binary_tree.data, end=',')
  50. in_order_traverse(_binary_tree.right)
  51. def post_order_traverse(_binary_tree):
  52. """
  53. 后序遍历
  54. :param _binary_tree: 二叉树
  55. :type _binary_tree: TreeNode
  56. """
  57. if _binary_tree is None:
  58. return
  59. post_order_traverse(_binary_tree.left)
  60. post_order_traverse(_binary_tree.right)
  61. print(_binary_tree.data, end=',')
  62. def layer_order_traverse(_layer_nodes):
  63. """
  64. 按层遍历
  65. :param _layer_nodes: 当前层节点集合
  66. :type _layer_nodes: list
  67. """
  68. if _layer_nodes is None or len(_layer_nodes) == 0:
  69. return
  70. _childs = [] # 子集
  71. for _node in _layer_nodes: # 遍历传入的当前层所有节点
  72. print(_node.data, end=',')
  73. if _node.left:
  74. _childs.append(_node.left)
  75. if _node.right:
  76. _childs.append(_node.right)
  77. layer_order_traverse(_childs)
  78. if __name__ == '__main__':
  79. binary_tree = fixed_tree()
  80. print('前序遍历:', end='')
  81. pre_order_traverse(binary_tree)
  82. print()
  83. print('中序遍历:', end='')
  84. in_order_traverse(binary_tree)
  85. print()
  86. print('后序遍历:', end='')
  87. post_order_traverse(binary_tree)
  88. print()
  89. print('按层遍历:', end='')
  90. layer_order_traverse([binary_tree])
  91. print('\b' * 1, end='')

3.堆的概念

  • 二叉堆是完全二叉树或者是近似完全二叉树。

  • 二叉堆满足二个特性:
    1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
    2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

  • 任意节点的值都大于其子节点的值—大顶堆

  • 任意节点的值都小于其子节点的值—小顶堆

6.堆排序

排序思想

1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端

2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1

3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

注意:升序用大根堆,降序就用小根堆(默认为升序)

通过该步骤得到从大到小的排序顺序

7.计数排序

用辅助数组对数组中出现的数字计数,元素转下标,下标转元素

思路:开辟新的空间,空间大小为max(source)扫描source,将value作为辅助空间的下标,用辅助空间的改位置元素记录value的个数如:9 7 5 3 1 ,helper=arr(10)一次扫描,value为9,helper[9]++,value为7,将helper[7]++……如此这般之后,我们遍历helper,如果该位(index)的值为0,说明index不曾在source中出现如果该位(index)的值为1,说明index在source中出现了1次,为2自然是出现了2次,遍历helper就能将source修复为升序排列

时间复杂度: 扫描一次source,扫描一次helper,复杂度为N+k

空间复杂度:辅助空间k,k=maxOf(source)

计数有缺陷,数据较为密集或范围较小时,适用。

  1. static void CountSort(int[] a) {
  2. int max = a[0];
  3. for (int e : a) {
  4. if (e > max) {
  5. max = e;
  6. }
  7. }
  8. int[] helper = new int[max + 1];
  9. for (int e : a) {
  10. helper[e]++;
  11. }
  12. int current = 0;//数据回填的位置
  13. for (int i = 1; i < helper.length; i++) {
  14. while (helper[i] > 0) {
  15. a[current++] = i;
  16. helper[i]--;
  17. }
  18. }
  19. }

8、桶排序

  • 一句话:通过"分配”和"收集”过程来实现排序

  • 思想是:设计k个桶( bucket) ( 编号0~k-1 ) ,然后将n个输入数分布到各个桶中去,对各个桶中的数进行排序,然后按照次序把各个桶中的元素列出来即可。

  • 计数是不是有点桶的味道?

  • 由于实现需要链表,我们再讲到链表的时候再回来写这个代码

类似计数排序(一个数一个桶),桶排序(多个数一个桶),桶内要有序,再依次遍历桶

9、基数排序

思路:是一种特殊的桶排序

(1)假设有欲排数据序列如下所示:

77 22 93 43 55 14 28 65 39 81

首先根据个位数的数值,在遍历数据时将它们各自分配到编号0至9的桶(个位数值与桶号一―对应)中。

81 22 73 93 43 14 55 65 28 39

(2)接着,再进行一次分配,这次根据十位数值来分配(原理同上),分配结果(逻辑想象)如下图

所示:

14 22 28 39 43 55 65 73 81 93

排序算法的总结:

#基础排序

a.冒泡

谁大谁上,每一轮都把最大的顶到天花板

效率太低O(n²)——掌握swap

b.选择排序,效率较低,但经常用它内部的循环方式来找最大值和最小值——怎么一次性求出数组的最大值和最小值

O(n²)

c.插排,虽然平均效率低,但是在序列基本有序时,它很快,所以也有其适用范围

Arrays这个工具类在1.7里面做了较大改动

d.希尔(缩小增量排序),是插排的改良,对空间思维训练有帮助

#分治法

1.子问题拆分

2.递归求解子问题

3.合并子问题的解

e.快排是软件工业中最常见的常规排序法,其双向指针扫描分区算法是核心,

往往用于解决类似问题,特别地partition算法用来划分不同性质的元素,

partition->selectK,也用于著名的top问题

O(NlgN),但是如果主元不是中位数的话,特别地如果每次主元都在数组区间的一侧,复杂度将退化为N²

工业优化:三点取中法,绝对中值法,小数据量用插入排序

快排重视子问题拆分

f.归并排序,空间换时间——逆序对数

归并重视子问题的解的合并

g.堆排序,用到了二叉堆数据结构,是继续掌握树结构的起手式

=插排+二分查找

上面三个都是NlgN的复杂度,其中快排表现最好,是原址的不用开辟辅助空间;堆排也是原址的,但是常数因子较大,不具备优势。

上面7种都是基于比较的排序,可证明它们在元素随机顺序情况下最好是NlgN的,用决策树证明



下面三个是非比较排序,在特定情况下会比基于比较的排序要快:

1.计数排序,可以说是最快的:O(N+k),k=maxOf(sourceArr),

用它来解决问题时必须注意如果序列中的值分布非常广(最大值很大,元素分布很稀疏),空间将会浪费很多

所以计数排序的适用范围是:序列的关键字比较集中,已知边界,且边界较小

2.桶排序:先分桶,再用其他排序方法对桶内元素排序,按桶的编号依次检出。(分配-收集)

用它解决问题必须注意序列的值是否均匀地分布在桶中。

如果不均匀,那么个别桶中的元素会远多于其他桶,桶内排序用比较排序,极端情况下,全部元素在一个桶内

还是会退化成NlgN

其时间复杂度是:时间复杂度: O(N+C),其中C=N*(logN-logM),约等于N*lgN

N是元素个数,M是桶的个数。

3.基数排序,kN级别(k是最大数的位数)是整数数值型排序里面又快又稳的,无论元素分布如何,

只开辟固定的辅助空间(10个桶)

对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下,

桶内多个数据必须进行基于比较操作的排序。

因此,在实际应用中,对十进制整数来说,基数排序更好用。

期望水准:

1、准确描述算法过程

2、写出伪代码

3、能分析时间复杂度

4、能灵活应用(知道优缺点和应用场景)

在查找算法中,基于比较的查找算法最好的时间复杂度也是O(logN)。

比如折半查找、平衡二叉树、红黑树等。

但是Hash表却有O©线性级别的查找效率(不冲突情况下查找效率达到O(1))。

大家好好体会一下:Hash表的思想和桶排序是不是有异曲同工之妙呢?

排序算法可视化网站 Data Structure Visualization (usfca.edu)

题7 :排序数组中找和的因子

  • 给定已排序数组arr和k ,不重复打印arr中所有相加和为k的不降序二元组

  • 如输入arr={-8,-4,-3,0,2,4,5,8,9,10},k=10

  • 输出(0,10)(2,8)

1.左右两个指针,首尾相加,如果小于10,左指针右移动,大于10,右指针左移动。

2.遍历,二分法查找加起来等于10的元素。

  • 扩展:三元组呢?

题8:需要排序的子数组

给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

示例 1:

输入: [2, 6, 4, 8, 10, 9, 15]

输出: 5

解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

说明 :

输入的数组长度范围在 [1, 10,000]。

输入的数组可能包含重复元素 ,所以升序的意思是<=。

##

//扩展右端点:更新历史最高,只要右侧出现比历史最高低的,就应该将有边界扩展到此处

//找左端点:更新历史最低,只要左侧出现比历史最低高的,就应该将左边界扩展到此处

题9 :前k个数

  • 求海量数据(正整数)按逆序排列的前k个数(topK),因为数据量太大,不能全部存储在内存中,只能一个一个地从磁盘或者网络上读取数据,请设计一个高效的算法来解决这个问题。 第一行用户输入K,代表要求得topK 随后的N(不限制)行,每一行是一个整数代表用户输入的数据,直到用户输入-1代表输入终止,请输出topK,空格分割。

  • **思路:**先开辟一个K大小的数组arr,然后读取K个数据存储到数组arr,读到K+1的时候,如果arr[K+1]小于arr中最小的值,那么就丢掉不管,如果arr[K+1]大于arr中最小的值,那么就把arr[K+1]和数组中最小的值进行交换,然后再读取K+2个数。这样就能解决这个问题。但是这个算法复杂度为K+(N-K)*K,K可以忽略不计,所以时间复杂度为O(KN)。那这个代码很容易就写出来。假如题目要求用到NlgK的时间复杂度,那么这里就需要使用堆这种数据结构来解决问题,而且还是小顶堆。具体思想还是和数组一样.

题10 :所有员工年龄排序

  • 公司现在要对几万员工的年龄进行排序,因为公司员1工的人数非常多,所以要求排序算法的效率要非常高,你能写出这样的程序吗

  • 输入:输入可能包含多个测试样例,对于每个测试案例,

  • 输入的第一行为-一个整数n(1<= n<=1000000) :代表公司内员工的人数。

  • 输入的第二行包括n个整数:代表公司内每个员工的年龄。其中,员工年龄age的取值范围为(1<=age<=99)。

  • 输出:对应每个测试案例,

  • 请输出排序后的n个员工的年龄,每个年龄后面有-一个空格。

采用计数排序,计数排序适用于数据范围小且已知

题11 :数组能排成的最小数(特殊排序)

思路: 这题本质上,竟然是一个排序题,只是在排序时,需要重新制定排序规则
例如 "3" 和 "30" 303 < 330
排序规则可得:
如果x + y > y + x, 那么 x 的权值就大于y
反之亦然

思想冒泡排序,两两组合比较

例子:{3 32 321}

3 32 比较 332 323 ——》 32 3 321

3 321 比较 3321 3213 ——》32 321 3

32 321 比较 32321 32132——》321 32 3

  1. """
  2. 剑指offer题1,关于数组排成最小数
  3. 其实这是一个排序问题,相比大家都知道答案,根据AB和BA比较,谁小以谁的顺序为升序,
  4. 即[A,B,C,D]进行组合,若已知ABCD升序排列,即AB<BA,AC<CA,AD<DA,BC<CB,BD<DB,CD<DC,
  5. 则ABCD为最小数
  6. 证明:
  7. 假设ABCD不是最小的,设DCBA最小,那么一定有DCBA<CDBA ,可以得到DC<CD,与已知矛盾,证毕。
  8. 那接下来使用某种排序算法,将判断大小的方式修正即可。
  9. 此处使用mergesort,只需要将if l1[0]<l2[0]:这个判断进行修改。
  10. ps:这里需要注意,用到join函数,所以l1[0],l2[0]得是str类型,
  11. k1=str(l1[0]);k2=str(l2[0])
  12. if int(''.join([k1,k2]))<int(''.join([k2,k1])):
  13. l0.append(l1[0])
  14. else:
  15. l0.append(l2[0])
  16. 完整代码如下
  17. """
  18. def mergesort(lis):#此处输入的list内为int,str均可
  19. if len(lis)==1:
  20. return lis
  21. else:
  22. mid=int(1/2*len(lis))
  23. lis1=lis[:mid]
  24. lis2=lis[mid:]
  25. return merge(mergesort(lis1),mergesort(lis2))
  26. def merge(l1,l2):
  27. l0=[]
  28. while(len(l1) or len(l2)):
  29. if len(l1)==0:
  30. l0=l0+l2
  31. return l0
  32. elif len(l2)==0:
  33. l0=l0+l1
  34. return l0
  35. else:
  36. k1=str(l1[0])
  37. k2=str(l2[0])
  38. if int(''.join([k1,k2]))<int(''.join([k2,k1])):
  39. l0.append(l1.pop(0))
  40. else:
  41. l0.append(l2.pop(0))

题12 :数组的包含

输入两个字符串str1和str2 ,请判断str1中的所有字符是否都存在与str2中

1.遍历查找是否存在,有一个不存在就return -1

2.排序后二分查找

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

闽ICP备14008679号