排序算法总结
排序算法 |
时间复杂度(最差) |
时间复杂度(最好) |
时间复杂度(平均) |
空间复杂度 |
稳定性 |
冒泡排序 |
O(n2) |
O(n) |
O(n2) |
O(1) |
稳定 |
插入排序 |
O(n2) |
O(n) |
O(n2) |
O(1) |
稳定 |
希尔排序 |
O(n2) |
O(n) |
O(n1.3) |
O(1) |
不稳定 |
选择排序 |
O(n2) |
O(n2) |
O(n2) |
O(1) |
不稳定 |
堆排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(1) |
不稳定 |
快速排序 |
O(n2) |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
不稳定 |
归并排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
O(n) |
稳定 |
计数排序 |
O(n+k) |
O(n+k) |
O(n+k) |
O(n+k) |
稳定 |
桶排序 |
O(n2) |
O(n) |
O(n+k) |
O(n+k) |
稳定 |
基数排序 |
O(n∗k) |
O(n∗k) |
O(n∗k) |
O(n+k) |
稳定 |
堆排序算法
- 堆的上浮操作:当在堆尾添加了新元素后,需要对堆尾元素进行上浮操作来维护堆
- 堆的下沉操作:当更新堆首元素后,需要对堆首元素进行下沉操作来维护堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution: def heap_sort(self, heap): if(len(heap) <= 1): return heap for i in range(len(heap) // 2 - 1, -1, -1): self.sift_down(heap, i, len(heap) - 1) for i in range(len(heap) - 1, 0, -1): heap[0], heap[i] = heap[i], heap[0] self.sift_down(heap, 0, i - 1) return heap
def sift_down(self, heap, start, end): tmp = heap[start] i = start j = 2 * i + 1 while(j <= end): if(j + 1 <= end and heap[j] < heap[j + 1]): j += 1 if(tmp < heap[j]): heap[i] = heap[j] else: break i = j j = 2 * i + 1 heap[i] = tmp
|
运行结果
1 2 3 4
| solu = Solution() print(solu.heap_sort([2, 5, 9, 8, 3, 1, 6, 4]))
>>> [1, 2, 3, 4, 5, 6, 8, 9]
|
注:
① 大项堆对应的是升序排列,小项堆对应的是降序排列,当然,将结果逆序输出就能反过来。
② 新元素只能添加在末尾,若添加在堆首,则会将堆中元素打乱,从而需要重新构建堆。
复杂度分析
总体时间复杂度:O(nlogn)
注:构造堆有两种方法。
① 对原始list进行操作,如 基础算法 所示
② 新建一个list,依次插入元素并进行上浮操作,如 算法题案例 所示
算法题案例
最小的K个数
题目来源:剑指Offer
题目链接:牛客网-剑指Offer
题目描述:
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
解题思路
- 取前K个数,构造一个大小为K的大项堆,用来记录最小的K个数
- 依次将剩余数与堆顶元素进行比较
- 若小于堆顶元素,则用其替换堆顶元素,并进行上浮操作调整堆
- 若大于等于堆顶元素,则将其抛弃, 继续读入下一个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class Solution: def GetLeastNumbers_Solution(self, tinput, k): if(k == 0 or k > len(tinput) or len(tinput) == 0): return [] heap = []
for n in tinput: if(len(heap) < k): heap.append(n) self.sift_up(heap, 0, len(heap) - 1) elif(n < heap[0]): heap[0] = n self.sift_down(heap, 0, len(heap) - 1) print(heap, n) return sorted(heap)
def sift_down(self, heap, start, end): tmp = heap[start] i = start j = 2 * i + 1 while(j <= end): if(j + 1 <= end and heap[j] < heap[j + 1]): j += 1 if(tmp < heap[j]): heap[i] = heap[j] else: break i = j j = 2 * i + 1 heap[i] = tmp
def sift_up(self, heap, start, end): tmp = heap[end] i = end j = (i - 1) >> 1 while(j >= start): if(tmp > heap[j]): heap[i] = heap[j] else: break i = j j = (i - 1) >> 1 heap[i] = tmp
|
运行结果
1 2 3 4
| solu = Solution() print(solu.GetLeastNumbers_Solution([4,5,1,6,2,7,3,8], 4))
>>> [1, 2, 3, 4]
|
复杂度分析
- 维护一个大小为k的堆:O(logk)
- 一共有n个数:O(nlogk)
总体时间复杂度:O(nlogk)
数组中的第K个最大元素
题目来源:Leetcode
题目链接:Leetcode-215
题目描述:
在未排序的数组中找到第 K个最大的元素。请注意,你需要找的是数组排序后的第 K 个最大的元素,而不是第 K 个不同的元素。
解题思路
- 取前K个数,构造一个大小为K的小项堆,用来记录最大的K个数
- 依次将剩余数与堆顶元素进行比较
- 若大于堆顶元素,则用其替换堆顶元素,并进行上浮操作调整堆
- 若小于等于堆顶元素,则将其抛弃, 继续读入下一个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| class Solution: def findKthLargest(self, tinput, k): if(k == 0 or k > len(tinput) or len(tinput) == 0): return [] heap = []
for n in tinput: if(len(heap) < k): heap.append(n) self.sift_up(heap, 0, len(heap) - 1) elif(n > heap[0]): heap[0] = n self.sift_down(heap, 0, len(heap) - 1) return heap[0] def sift_down(self, heap, start, end): tmp = heap[start] i = start j = 2 * i + 1 while(j <= end): if(j + 1 <= end and heap[j] > heap[j + 1]): j += 1 if(tmp > heap[j]): heap[i] = heap[j] else: break i = j j = 2 * i + 1 heap[i] = tmp
def sift_up(self, heap, start, end): tmp = heap[end] i = end j = (i - 1) >> 1 while(j >= start): if(tmp < heap[j]): heap[i] = heap[j] else: break i = j j = (i - 1) >> 1 heap[i] = tmp
|
运行结果
1 2 3 4 5 6 7 8
| solu = Solution() print(solu.findKthLargest([3,2,1,5,6,4], 2))
>>> 5
print(solu.findKthLargest([3,2,3,1,2,4,5,5,6], 4))
>>> 4
|
复杂度分析
- 维护一个大小为k的堆:O(logk)
- 一共有n个数:O(nlogk)
总体时间复杂度:O(nlogk)
数据流中的第K大元素
题目来源:Leetcode
题目链接:Leetcode-703
题目描述:
设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。
你的 KthLargest 类需要一个同时接收整数 k 和整数数组nums 的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add,返回当前数据流中第K大的元素。
解题思路
- 初始化一个大小至多为K的堆(可能小于K)
- 依次读入数据流的新元素
- 若堆还没到K,则在堆底插入新元素,并进行上浮操作
- 若堆已到达K,且新元素大于堆顶元素,则用其替换堆顶元素,并进行下沉操作调整堆
- 否则将其抛弃, 继续读入下一个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| class KthLargest: def __init__(self, k, nums): self.k = k self.heap = [] for n in nums: if(len(self.heap) < self.k): self.heap_push(self.heap, n) else: self.heap_update(self.heap, n)
def add(self, val): if(len(self.heap) < self.k): self.heap_push(self.heap, val) else: self.heap_update(self.heap, val) return self.heap[0] def sift_down(self, heap, start, end): tmp = heap[start] i = start j = 2 * i + 1 while(j <= end): if(j + 1 <= end and heap[j] > heap[j + 1]): j += 1 if(tmp > heap[j]): heap[i] = heap[j] else: break i = j j = 2 * i + 1 heap[i] = tmp
def sift_up(self, heap, start, end): tmp = heap[end] i = end j = (i - 1) >> 1 while(j >= start): if(tmp < heap[j]): heap[i] = heap[j] else: break i = j j = (i - 1) >> 1 heap[i] = tmp
def heap_push(self, heap, val): heap.append(val) self.sift_up(heap, 0, len(heap) - 1)
def heap_update(self, heap, val): if(val > heap[0]): heap[0] = val self.sift_down(heap, 0, len(heap) - 1)
|
运行结果
1 2 3 4 5 6 7 8 9 10 11 12
| solu = KthLargest(3, [4,5,8,2]) solu.add(3) solu.add(5) solu.add(10) solu.add(9) solu.add(4)
>>> 4 >>> 5 >>> 5 >>> 8 >>> 8
|
复杂度分析
- 维护一个大小为k的堆:O(logk)
- 一共有n个数:O(nlogk)
总体时间复杂度:O(nlogk)