十大排序算法练习(Python)

本博客同时发布于个人主页:www.doctorsrn.cn

十大排序算法练习

使用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.插入排序

将后续元素一个一个插入到前面已经排好序的有序序列中。
算法复杂度 O ( n 2 ) O(n^2) O(n2)

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序列,根据增量先从宏观上对数组进行排序
算法复杂度 O ( n 1.3 − 2 ) O(n^{1.3-2}) O(n1.32)

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.简单选择排序

遍历数组,选择最小或最大值从头开始交换
算法复杂度 O ( n 2 ) O(n^2) O(n2)

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.冒泡排序

算法复杂度 O ( n 2 ) O(n^2) O(n2)

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

分治法思想,时间复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

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]
# # 更加简洁清晰的实现,这种实现对含有重复出现元素的数组排序将出错
# 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.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

算法复杂度为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

堆的定义就是:

大顶堆: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

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 种方法);
  • 自下而上的迭代;

需要额外的空间消耗,时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

算法步骤:
1) 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

2) 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

4) 重复步骤 3 直到某一指针达到序列尾;

5) 将另一序列剩下的所有元素直接复制到合并序列尾。

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

#########################################
#####      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之间的整数时,时间复杂度为: O ( n + k ) O(n+k) O(n+k)

算法的步骤如下:

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

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

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 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。

最快的情况:输入的数据可以均匀的分配到每一个桶中。
最慢的情况:输入的数据被分配到了同一个桶中。

# 和计数排序排序很像,下面是一个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

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

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

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

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

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

# 暴力解法---超时警告:(
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

动态规划法:

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]))
# 动态规划法
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
# 运行时间优于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:]))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值