算法小结(二):列表排序1

一、列表排序概述

列表排序就是将无序列表转变为有序列表,包括升序和降序两种,python内置函数为sort()。
常见排序算法有冒泡排序、选择排序、插入排序、快速排序、堆排序、归并排序、希尔排序、计数排序和基数排序(9种)。

二、冒泡排序(Bubble Sort)

1、基本思路

(1)列表每两个相邻的数,如果前面比后面大,则交换两个数
(2)一趟排序完成之后,则无序区减少一个数,有序区增加一个数
(3)代码关键点:趟数、无序区范围

2、代码实现

def Bubble_Sort(li):
    for i in range(len(li)-1):          # 趟数
        for j in range(len(li)-i-1):    # 前后数比较
            if li[j+1] > li[j]:
                li[j], li[j+1] = li[j+1], li[j]  # 交换位置
                print("排序过程",li)
    return li

print(Bubble_Sort([1,5,6,2,7]))
>>排序过程 [5, 1, 6, 2, 7]
>>排序过程 [5, 6, 1, 2, 7]
>>排序过程 [5, 6, 2, 1, 7]
>>排序过程 [5, 6, 2, 7, 1]
>>排序过程 [6, 5, 2, 7, 1]
>>排序过程 [6, 5, 7, 2, 1]
>>排序过程 [6, 7, 5, 2, 1]
>>排序过程 [7, 6, 5, 2, 1]
>>[7, 6, 5, 2, 1]

算法优化:如果冒泡排序中一趟排序没有发生交换,则说明列表已经有序,可以直接结束算法.

def Bubble_Sort2(li):
    for i in range(len(li)-1):
        exchange = False
        for j in range(len(li)-i-1):
            if li[j+1] > li[j]:
                li[j], li[j+1] = li[j+1], li[j]
                exchange = True
                print("排序过程", li)
        if not exchange:
            return li
    return li

print(Bubble_Sort2([7,5,3,1,2,3]))
>>排序过程 [7, 5, 3, 2, 1, 3]
>>排序过程 [7, 5, 3, 2, 3, 1]
>>排序过程 [7, 5, 3, 3, 2, 1]
>>[7, 5, 3, 3, 2, 1]
  • 时间复杂度:O(n2)
  • 稳定性:对于相等的两个元素不做交换处理,因此具有稳定性

三、选择排序(Select Sort)

1、基本思路

(1)一趟排序记录最小的数,放在第一个位置
(2)再一趟排序记录列表无序区最小的数,放在第二个位置
...
(3)算法关键点:有序区和无序区、无序区最小的数

2、代码实现

def Select_Sort(li):
    for i in range(len(li)-1):
        min_loc = i
        for j in range(i+1, len(li)):
            if li[j] < li[min_loc]:
                min_loc = j
        if min_loc != i:
            li[i], li[min_loc] = li[min_loc], li[i]
            print("当前排序",li)
    return li

print(Select_Sort([5,8,4,1,6,7]))
>>当前排序 [1, 8, 4, 5, 6, 7]
>>当前排序 [1, 4, 8, 5, 6, 7]
>>当前排序 [1, 4, 5, 8, 6, 7]
>>当前排序 [1, 4, 5, 6, 8, 7]
>>当前排序 [1, 4, 5, 6, 7, 8]
>>[1, 4, 5, 6, 7, 8]
  • 时间复杂度:O(n2)
  • 稳定性:由于会进行选择,相同大小的元素如果出现在前面,会发生优先交换,因此该排序方法不稳定
排序前 3(1) 5(1) 3(2) 2(1)
第一次排序 2(1) 5(1) 3(2) 3(1)
第二次排序 2(1) 3(2) 5(1) 3(1)
第三次排序 2(1) 3(2) 3(1) 5(1)

四、插入排序(Insert Sort)

1、基本思路

(1)初始时手里(有序区)只有一张牌
(2)每次(从无序区)取出一张牌,比较之后插入到手中已有牌的正确位置

2、代码实现

def insert_sort(li):
    for i in range(1, len(li)):  # i表示摸到的牌的下标
        tmp = li[i]
        j = i - 1                # j表示手里的牌的下标
        while j >= 0 and tmp < li[j]: #循环比较前面的数
            li[j+1] = li[j]
            j = j - 1
        li[j+1] = tmp
        print("插入过程",li)
    return li

li = [3,2,4,1,5,7,9,6,8]
print(insert_sort(li))
>>插入过程 [2, 3, 4, 1, 5, 7, 9, 6, 8]
>>插入过程 [2, 3, 4, 1, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 7, 9, 6, 8]
>>插入过程 [1, 2, 3, 4, 5, 6, 7, 9, 8]
>>插入过程 [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>[1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 时间复杂度:O(n2)
  • 稳定性:因为在两个数相等时,只会把较后面的数仍旧插在后面,因此是稳定的。

五、快速排序(Quick Sort)

1、基本思路

(1)取出一个元素p(第一个元素),使元素p归位
(2)列表被p分为两部分,左边都比p小,右边都比p大
(3)递归完成排序

2、代码实现

# 找出p的正确位置
def partition(data, left, right):
    tmp = data[left]
    while left < right:
        while left < right and data[right] >= tmp:
            right -= 1
        data[left] = data[right]
        while left < right and data[left] <= tmp:
            left += 1
        data[right] = data[left]
    data[left] = tmp
    return left

# 快速排序框架
def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        quick_sort(data, left, mid-1)
        quick_sort(data, mid+1, right)
    return data

li = [9,8,7,6,5,4,3,2,1]
print(quick_sort(li, 0, len(li)-1))
>>[1, 2, 3, 4, 5, 6, 7, 8, 9]
# 快速排序
def quickSort(nums, left, right):
    '''
    left = 0
    right = len(nums)-1
    '''
    # 递归的终止条件
    if left < right:
        center = partion(nums, left, right)
        quickSort(nums, left, center-1)
        quickSort(nums, center+1, right)
    return nums

# 排序之后寻找主元位置进行分割
def partion(nums, left, right):
    i = left
    for j in range(left, right):
        if nums[j] < nums[right]:
            nums[i], nums[j] = nums[j], nums[i]
            i += 1
    nums[i], nums[right] = nums[right], nums[i]   # 交换主元
    return i

nums = [9,8,7,6,5,4]
print(quickSort(nums, 0, len(nums)-1))
>>[4, 5, 6, 7, 8, 9]

3、时间复杂度讨论

(1)时间复杂度:O(nlogn)
(2)最坏情况下,从逆序排为正序,O(n2)
(3)稳定性:相等元素会和主元发生位置交换,因此不稳定

六、堆排序(Heap Sort)

1、什么是堆?

堆是一种特殊的完全二叉树结构
(1)大根堆:一颗完全二叉树,满足任一节点都比其孩子节点大
(2)小根堆:一颗完全二叉树,满足任一节点都比其孩子节点小

(3)堆的向下调整性质
假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下调整将其变成一个堆
(4)二叉树的顺序存储方式
父节点与左孩子节点的下标关系:i --> 2i+1
父节点与右孩子节点的下标关系:i --> 2i+2

2、堆排序的过程

(1)建立堆
(2)得到堆顶元素,为最大元素
(3)去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
(4)堆顶元素为第二大元素
(5)重复步骤3,直到堆变空

3、代码实现

def sift(data, low, high):
     """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return: 
    """
    i = low        # i最开始指向根节点
    j = 2 * i + 1 # j最开始是左孩子
    tmp = data[i]   # 把堆顶存起来
    while j <= high:  # 只要j位置有数
        if j < high and data[j] < data[j + 1]:  #如果右孩子存在且较大
            j += 1  # j指向右孩子
        if tmp < data[j]:    # 堆顶小于右孩子
            data[i] = data[j]  # 把右孩子放在堆顶
            i = j      # 往下走一层 
            j = 2 * i + 1    # 左孩子
        else:          # tmp更大,把tmp放在i的位置上
            li[i] = tmp     # 把tmp放到某一级领导位置上
            break
    data[i] = tmp      # 把tmp放到叶子节点上
# 排序
def heap_sort(data):
    n = len(data)
    for i in range(n//2-1, -1, -1):   # i表示建堆时调整的部分根的下标
        sift(data, i, n-1)        # 建堆完成
    for i in range(n-1, -1, -1):     # i 表示当前堆的最后一个元素
        data[0], data[i] = data[i], data[0]
        sift(data, 0, i-1)      # i-1 是新的high

li = [i for i in range(20)]
import random
random.shuffle(li)
print(li)
print(heap_sort(li))
>>[0, 17, 9, 14, 4, 16, 10, 7, 5, 12, 15, 2, 1, 19, 11, 3, 13, 6, 8, 18]
>>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
  • 时间复杂度:O(nlogn)
  • 稳定性:不稳定

4、堆排序的内置模块

li = [i for i in range(20)]
import random
random.shuffle(li)
print(li)

import heapq
heapq.heapify(li)   # 建堆

n = len(li)
for i in range(n):
    print(heapq.heappop(li), end=',')
>>[0, 11, 17, 9, 2, 5, 4, 18, 1, 14, 15, 12, 6, 8, 7, 3, 19, 10, 16, 13]
>>0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,

5、使用堆排序解决topk问题

基本思路:
(1)取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数
(2)依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整
(3)遍历列表所有元素之后,倒序弹出堆顶

# 比较排序
def sift(li, low, high):
    i = low
    j = 2 * i + 1
    tmp = li[low]
    while j <= high:
        if j + 1 <= high and li[j+1] < li[j]:
            j = j + 1
        if li[j] < tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else:
            break
        li[i] = tmp

def topk(li, k):
    heap = li[0:k]
    for i in range(k//2-1, -1, -1):
        sift(heap, i, k-1)
    # 1、建堆
    for i in range(k, len(li)):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap, 0, k-1)
    # 2、遍历
    for i in range(k-1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        sift(heap, 0, i-1)
    # 3、出数
    return heap

import random
li = list(range(100))
random.shuffle(li)
print(topk(li, 10))
>>[14, 19, 21, 29, 30, 47, 65, 75, 84, 99]

七、归并排序(Merge Sort)

假设现在一个列表分两段有序,将其合并为一个有序列表

1、基本思路

(1)分解:将列表越分越小,直至分成一个元素
(2)终止条件:一个元素是有序的
(3)合并:将两个有序列表合并,列表越来越大

排序过程

2、代码实现

(1)对于一个已经分别有序的列表,直接进行归并

# 一次排序
def merge(li, low, mid, high):
    i = low
    j = mid + 1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] <= li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    while i <= mid:          # 右侧列表元素全部比较结束
        ltmp.append(li[i])
        i += 1
    while j <= high:         # 左侧列表元素全部比较结束
        ltmp.append(li[j])
        j += 1
    li[low:high+1] = ltmp    # 重新写入

li = [2,4,5,7,1,3,6,8]
merge(li, 0, 3, 7)
print(li)
>> [1, 2, 3, 4, 5, 6, 7, 8]

(2)对于一个乱序列表,则需要先将列表分解成部分有序

def mergeSort(nums, left, right):
    '''
    left = 0
    right = len(nums)-1
    '''
    # 递归的终止条件是left=right,即分割为一个元素
    if left < right:
        mid = left + (right-left) // 2
        mergeSort(nums, left, mid)
        mergeSort(nums, mid+1, right)
        merge(nums, left, mid, right)
    return nums

def merge(nums, left, mid, right):
    i = left
    j = mid + 1
    ltemp = []
    while i <= mid and j <= right:
        if nums[i] <= nums[j]:
            ltemp.append(nums[i])
            i += 1
        else:
            ltemp.append(nums[j])
            j += 1
    while i <= mid:
        ltemp.append(nums[i])
        i += 1
    while j <= right:
        ltemp.append(nums[j])
        j += 1
    nums[left:right+1] = ltemp

print(mergeSort([5,7,8,3,1,2,4,6], 0, 7))
>> [1, 2, 3, 4, 5, 6, 7, 8]

3、复杂度分析

时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:因为在比较合并的过程中,当两个数相等时,先将前面的数加入数组,因此是稳定的。

八、内容小结

排序算法比较

你可能感兴趣的:(算法小结(二):列表排序1)