十大排序算法练习

十大排序算法练习

使用Python实现常用的十大排序算法:
1.插入排序insert sort
2.希尔排序shell sort
3.简单选择排序select sort
4.冒泡排序bubble sort
5.快速排序quick sort
6.堆排序HeapSort
7.归并排序mergesort
8.计数排序countingSort
9.桶排序bucketSort
10.基数排序radixSort

参考实现:

博客中代码对应的仓库—sort_algorithm.ipynb,以及用于排序程序测试的代码—sort.py.

1.插入排序

将后续元素一个一个插入到前面已经排好序的有序序列中。
算法复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def insertSort(arr):
length = len(arr)
for i in range(length):
k = i
for j in range(k,0,-1):
if arr[j]<arr[j-1]: # 控制大小端
arr[j],arr[j-1]=arr[j-1],arr[j]
return arr

def insertSort1(arr):
step = 1
for i in range(step, len(arr)): # step=1时退化为插入排序
while i >= step and arr[i] < arr[i-step]: # 控制大小端
arr[i], arr[i-step] = arr[i-step], arr[i]
i -= step

return arr

arr = [-1, 3, 0, 9, 10, 0]
insertSort(arr)
print('insertSort:',arr)
insertSort [-1, 0, 0, 3, 9, 10]

2.希尔排序shell sort

是简单插入排序的高效改进版,选择增量step序列,根据增量先从宏观上对数组进行排序
算法复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def shellSort(arr):
if len(arr) <= 1:
return arr

step = len(arr) // 2

while step > 0:
for i in range(step, len(arr)): # step=1时退化为插入排序
while i >= step and arr[i] < arr[i-step]: # 控制大小端
arr[i], arr[i-step] = arr[i-step], arr[i]
i -= step
step //= 2

return arr


arr = [-1, 3, 0, 9, 10, 0]
shellSort(arr)
print('shellSort:',arr)
shellSort: [-1, 0, 0, 3, 9, 10]

3.简单选择排序

遍历数组,选择最小或最大值从头开始交换
算法复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def selectSort(arr):
if len(arr) <= 1:
return arr

for i in range(len(arr)-1):
min_index = i
for j in range(i+1, len(arr)):
if arr[j] < arr[min_index]: # 控制大小端
min_index = j
if min_index != i:
arr[min_index], arr[i] = arr[i], arr[min_index]


return arr


arr = [-1, 3, 0, 9, 10, 0]
selectSort(arr)
print('selectSort:',arr)
selectSort: [-1, 0, 0, 3, 9, 10]

4.冒泡排序

算法复杂度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def bubbSort(arr):
if len(arr) <= 1:
return arr
for i in range(len(arr)):
for j in range(i+1, len(arr)):
if arr[i] > arr[j]:
#pass
arr[i], arr[j] = arr[j], arr[i]
else:
#arr[i], arr[j] = arr[j], arr[i]
pass
return arr


arr = [1,2,4,5,7,9,10,12,2,3,2,4]
bubbSort(arr)
print("bubbSort:",arr)
bubbSort: [1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]

5.快速排序quick sort

分治法思想,时间复杂度为:

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
def quickSort(arr, left, right):
# 递归终止条件
if left > right:
return []

pivot = arr[left]
i = left
j = right
while i < j:
while i < j and arr[j] >= pivot:
j -= 1
while i < j and arr[i] <= pivot:
i += 1
if i < j:
arr[i], arr[j] = arr[j], arr[i]
# 此时arr的pivot两侧数分别小于和大于pivot
# 放入中心点
arr[left] = arr[i]
arr[i] = pivot

# 递归调用,对pivot左右两边的list进行排序
quickSort(arr, left, i - 1)
quickSort(arr, i + 1, right)

arr = [6, -2, 0, 9, 10]
quickSort(arr,0, len(arr)-1)
print(arr)
[-2, 0, 6, 9, 10]
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
# # 更加简洁清晰的实现,这种实现对含有重复出现元素的数组排序将出错
# def quickSort(arr):
# if arr:
# pivot = arr[0] # 设定基准点
# big = [m for m in arr[1:] if m>=pivot]
# little = [m for m in arr if m<pivot]
# return quickSort(little)+[pivot]+quickSort(big)
# else:
# return []


## 改进方法1,将重复出现的元素放入基准值左边或右边,这里放入较大值一边
# def quickSort(arr):
# if arr:
# pivot = arr[0] # 设定基准点
# big = [m for m in arr[1:] if m>=pivot]
# little = [m for m in arr if m<pivot]
# return quickSort(little)+[pivot]+quickSort(big)
# else:
# return []

## 改进方法2,引入重复数组,记录相等的基准值
def quickSort(arr) -> list:
if arr:
pivot = arr[0] # 设定基准点
big = [m for m in arr if m>pivot]
little = [m for m in arr if m<pivot]
equal = [m for m in arr if m==pivot]
return quickSort(little) + equal + quickSort(big) # 控制大小端
else:
return []

arr1 = [1,2,4,5,7,9,10,12,2,3,2,4]
arr = quickSort(arr1)
print(arr)
[1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]

6.堆排序HeapSort

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

基本思路:
  a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
  b.将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;(升序方法)
  c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

算法复杂度为:

堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆的调整:
最后一个非叶子结点为 arr.length/2-1,从最后一个非叶子节点开始,从右至左,从下至上进行调整
使用双向队列进行优化:https://www.jianshu.com/p/d174f1862601

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
class HeapSort:
def heapSort(self, arr):
length = len(arr)
# 从后往前遍历,交换堆顶与最后叶子节点,并依次调整堆,重复操作
for i in range(length-1, 0, -1):
# 获取堆顶元素并调整堆
firstNum = self.adjustHeap(arr, i+1)
# 交换最后一个叶子节点与堆顶元素
arr[i], arr[0] = firstNum, arr[i]

return arr

# 调整堆,每次返回堆顶元素
def adjustHeap(self, arr, length):
# 最后一个非叶节点
i = length // 2 - 1
# 从最后一个非叶节点开始调整,构成最大堆
while i >= 0:
large = arr[i] # 节点最大值
left = 2 * i + 1 # 左侧子节点
right = 2 * i + 2 # 右侧子节点

while left < length:
if right < length and arr[left] < arr[right]:
left = right

if arr[left] > large: # 从下往上调整
arr[i] = arr[left]
i = left ## i的更新非常重要!!!
else: # 当前节点和其子节点已满足大顶堆条件,所以break
break

# 因为对父节点做了调整,所以要对父节点对应的子节点重新调整
left = 2 * left + 1
right = left + 1

arr[i] = large # 将最大节点和原始节点交换
i -= 1 # 向前调整

return arr[0]


s = HeapSort()
nums = [1,2,4,5,7,9,10,12,2,3,2,4]
t = s.heapSort(nums)
print(t)
[1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]

7.归并排序mergesort

该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
分治法:

  • 分: 将问题划分为很多小问题,拆分子序列,递归深度为log2n
  • 治: 将每个小问题的解“修补”在一起

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

需要额外的空间消耗,时间复杂度:

算法步骤:
1) 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2) 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4) 重复步骤 3 直到某一指针达到序列尾;
5) 将另一序列剩下的所有元素直接复制到合并序列尾。

很好的图解算法过程参考:https://www.cnblogs.com/chengxiao/p/6194356.html

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
#########################################
##### mergeSort #####
#########################################
def mergeSort(arr):
if len(arr) <= 1:
return arr # 递归终止条件

mid = len(arr) // 2

# 分
left = mergeSort(arr[:mid])
right = mergeSort(arr[mid:])

# 治
return merge(left, right)

def merge(left, right):
i,j = 0,0
result = [] # 额外的空间消耗

while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1

if i < len(left):
result += left[i:]
if j < len(right):
result += right[j:]

return result

arr = [-1, 3, 0, 9, 10, 0, 2, 0, 7, -2, 40, 32]
s = mergeSort(arr)
print('mergeSort:',s)
mergeSort: [-2, -1, 0, 0, 0, 2, 3, 7, 9, 10, 32, 40]

8.计数排序countingSort

计数排序要求输入的数据必须是有确定范围的整数。
放输入数据是n个0到k之间的整数时,时间复杂度为:

算法的步骤如下:

1)找出待排序的数组中最大和最小的元素
2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

第3) 4)步的目的是使得到的排序是稳定排序,即相等的元素相对位置不变。

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
def countingSort(arr):

if len(arr) <= 1:
return arr

max_arr = max(arr)
min_arr = min(arr)

count_len = max_arr - min_arr + 1
count_arr = [0] * count_len

result = [0] * len(arr)

for i in arr:
count_arr[i - min_arr ] += 1

for j in range(1,count_len):
count_arr[j] += count_arr[j-1]

# 倒排写入结果,得到稳定排序结果
for k in arr[::-1]:
result[count_arr[k-min_arr] - 1] = k
count_arr[k-min_arr] -= 1

return result

arr = [-1, 3, 0, 9, 10, 0, 2, 0, 7, -2, 40, 32]
s = countingSort(arr)
print('countingSort:',s)
countingSort: [-2, -1, 0, 0, 0, 2, 3, 7, 9, 10, 32, 40]

9.桶排序bucketSort

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
1) 在额外空间充足的情况下,尽量增大桶的数量
2) 使用的映射函数能够将输入的 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
31
32
33
34
35
# 和计数排序排序很像,下面是一个pythonic实现
score=[5,3,5,2,8]
a = [score.count(i) for i in range(11)]
print([ i for i in range(10, 0, -1) for x in range(a[i])])


# 针对均匀分布在[0,1]之间的数的桶排序
class bucketSort(object):
def insertSort(self,a):
n=len(a)
if n<=1:
pass
for i in range(1,n):
key=a[i]
j=i-1
while key<a[j] and j>=0:
a[j+1]=a[j]
j-=1
a[j+1]=key
def sort(self,a):
n=len(a)
s=[[] for i in range(n)]
for i in a:
s[int(i*n)].append(i)
for i in s:
self.insertSort(i)
return [i for j in s for i in j]
def __call__(self,a):
return self.sort(a)



arr = [0.2,0.3,0.5,0.12,0.46]
a = bucketSort()
print(a.sort(arr))
[8, 5, 5, 3, 2]
[0.12, 0.2, 0.3, 0.46, 0.5]

10.基数排序radixSort

典型整数排序算法,基数排序有两种方法:

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 基数排序:根据键值的每位数字来分配桶;
  • 计数排序:每个桶只存储单一键值;
  • 桶排序:每个桶存储一定范围的数值;

基数排序重要的两个过程:取每个数的每一位,然后按顺序放回。重复上述过程,直到所有数的各个位数均被取过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def radixSort(s):
"""基数排序"""
i = 0 # 记录当前正在排哪一位,最低位为1
max_num = max(s) # 最大值
j = len(str(max_num)) # 记录最大值的位数
while i < j:
bucket_list =[[] for _ in range(10)] #初始化桶数组
for x in s:
bucket_list[int(x / (10**i)) % 10].append(x) # 找到位置放入桶数组
# print(bucket_list)
s.clear()
for x in bucket_list: # 放回原序列
for y in x:
s.append(y)
i += 1
return s

a = [334,5,67,345,7,345345,99,4,23,78,45,1,3453,23424]
res = radixSort(a)
print(res)
[[], [1], [], [23, 3453], [334, 4, 23424], [5, 345, 345345, 45], [], [67, 7], [78], [99]]
[[1, 4, 5, 7], [], [23, 23424], [334], [345, 345345, 45], [3453], [67], [78], [], [99]]
[[1, 4, 5, 7, 23, 45, 67, 78, 99], [], [], [334, 345, 345345], [23424, 3453], [], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345], [], [], [23424, 3453], [], [345345], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453], [], [23424], [], [345345], [], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453, 23424], [], [], [345345], [], [], [], [], [], []]
[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453, 23424, 345345]

leetcode

最大乘积子串

leetcode

leetcode-cn

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
# 暴力解法---超时警告:(
class Solution:
def multi(self, arr):
result = 1
if arr:
for x in arr:
result *= x
return result

def maxProduct(self, nums: list) -> int:
maxP = -10000
arr_sub = None
for length in range(1, len(nums)+1):
for start in range(len(nums) - length +1):
arr_s = nums[start:start+length]
if len(arr_s) > 0:
#print(arr_s)
result = self.multi(arr_s)
#print(result)
if result > maxP:
maxP = result
arr_sub = arr_s
else:
continue

return maxP

solution = Solution()

a = [2,3,-2,4]
mp = solution.maxProduct(a)

print(mp)

a = [-2,0,-1]
mp = solution.maxProduct(a)
print(mp)
6
0

动态规划法:

1
2
3
4
max = max(curr*last-max,curr*last-min,curr)   
min = min(curr*last-max,curr*last-min,curr)

dp(i) = max(dp(i-1)[0]*arr[i],dp(i-1)[1]*arr[i],arr[i]))

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
# 动态规划法
import sys
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
m = [-sys.maxsize-1]
def dp(i):

if i == 0:
m[0] = max(m[0],nums[i])
return nums[i],nums[i]
else:


prev = dp(i-1)
mi = min(nums[i],nums[i]*prev[0],nums[i]*prev[1])

ma = max(nums[i],nums[i]*prev[0],nums[i]*prev[1])
m[0] = max(m[0],ma)

return mi,ma

dp(len(nums)-1)[1]

return m[0]

# O(1) space
# 动态规划法二
def maxProduct1(self, nums):
if not nums:
return
locMin = locMax = gloMax = nums[0]
for i in range(1, len(nums)):
tmp = locMin
locMin = min(locMin*nums[i], nums[i], locMax*nums[i])
locMax = max(tmp*nums[i], nums[i], locMax*nums[i])
gloMax = max(gloMax, locMax)
return gloMax

solution = Solution()

a = [2,3,-2,4,5]
mp = solution.maxProduct(a)

print(mp)

a = [-2,0,-1]
mp = solution.maxProduct(a)
print(mp)

print(solution.maxProduct1(a))
20
0
0
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
# 运行时间优于99%
class Solution:
def maxProduct(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
elif len(nums) == 0:
return 0
def calProduct(nums):
if len(nums) == 0:
return 0
res = 1
for i in range(len(nums)):
res *= nums[i]
return res

num_list = []
if nums.count(0) != 0: # split the List by 0
while(nums.count(0) != 0):
ind = nums.index(0) # index of first 0
if ind == 0:
nums.pop(0) # pop the first element
elif ind == len(nums) - 1:
nums.pop()
else:
num_list.append(nums[:ind])
nums = nums[ind+1:]
num_list.append(nums)
res = 0
for nums_e in num_list:
res = max(res, self.maxProduct(nums_e))
return res
# Now there's no 0 in nums
neg_ind = []
for i in range(len(nums)):
if nums[i] < 0:
neg_ind.append(i)
l_neg = len(neg_ind)
if l_neg % 2 == 0: # even
return calProduct(nums)
else: # odd
if l_neg == 1:
return max(calProduct(nums[:neg_ind[0]]), calProduct(nums[neg_ind[0]+1:]))
else:
return max(calProduct(nums[:neg_ind[0]]), calProduct(nums[neg_ind[0]+1:]), calProduct(nums[:neg_ind[l_neg-1]]), calProduct(nums[neg_ind[l_neg-1]+1:]))
-------------The EndThanks for reading!-------------
0%