快排 & 堆排 & 归排

快排:快速排序中最简单的(递归调用)

  • 快排
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import random
import sys
sys.setrecursionlimit(10000000)             #设置系统最大递归深度

def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)    # mid返回的是上一个用来排序那个数的下标
        quick_sort(data, left, mid - 1)
        quick_sort(data, mid + 1,right)

# 每执行一次partition函数都可以实现将某个数左边都比这个数小右边都比这个数大
def partition(data, left, right):
    tmp = data[left]
    while left < right:
        while left < right and data[right] >= tmp:     # 从右向左找小于tmp的数放到左边空位置
            right -= 1
        data[left] = data[right]                       # 将右边小于tmp值得数放到左边空位置
        while left < right and data[left] <= tmp:      # 从左向右找到大于tmp的值放到右边空位置
            left += 1
        data[right] = data[left]                       # 将右边大于tmp值得数放到右边空位置
    data[left] = tmp
    return left

data = list(range(100))
random.shuffle(data)                                 #将有序列表打乱
quick_sort(data, 0, len(data) - 1)
print(data)
  • 不使用递归实现快排
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def quick_sort(arr):
    '''''
    模拟栈操作实现非递归的快速排序
    '''
    if len(arr) < 2:
        return arr
    stack = []
    stack.append(len(arr)-1)
    stack.append(0)
    while stack:
        l = stack.pop()
        r = stack.pop()
        index = partition(arr, l, r)
        if l < index - 1:
            stack.append(index - 1)
            stack.append(l)
        if r > index + 1:
            stack.append(r)
            stack.append(index + 1)


def partition(arr, start, end):
    # 分区操作,返回基准线下标
    pivot = arr[start]
    while start < end:
        while start < end and arr[end] >= pivot:
            end -= 1
        arr[start] = arr[end]
        while start < end and arr[start] <= pivot:
            start += 1
        arr[end] = arr[start]
    # 此时start = end
    arr[start] = pivot
    return start

lst = [1,3,5,7,9,2,4,6,8,10]
quick_sort(lst)
print lst   # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • 快排简版
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def quick(list):
    if len(list) < 2:
        return list

    tmp = list[0]  # 临时变量 可以取随机值
    left = [x for x in list[1:] if x <= tmp]  # 左列表
    right = [x for x in list[1:] if x > tmp]  # 右列表
    return quick(left) + [tmp] + quick(right)

li = [4,3,7,5,8,2]
print quick(li)  # [2, 3, 4, 5, 7, 8]

#### 对[4,3,7,5,8,2]排序
'''
[3, 2] + [4] + [7, 5, 8]                 # tmp = [4]
[2] + [3] + [4] + [7, 5, 8]              # tmp = [3] 此时对[3, 2]这个列表进行排序
[2] + [3] + [4] + [5] + [7] + [8]        # tmp = [7] 此时对[7, 5, 8]这个列表进行排序
'''

  • 快排原理
    在这里插入图片描述
# 从排序前--------> 到P归位 经历过程(前面都比5小后面都比5大)
# 1、    首先从右向左比较,取出列表第一个元素5(第一个位置就空出来)与列表最后一个元素8比较,8>5不换位置
# 2、    用5与-2位置的9比,5<9不换位置
# 3、    5与-3位置的2比较,2<5,将-3位置的5放到1号位置,那么-3号位置空出来了,然后从左往右比较
# 4、    5与2号位置的7比,5<7,将7放到-3号位置,2号位置空出来了,在从右往左比
# 5、    -4号位置的1小于5将1放到空出的2号位置,-4位置空出来了,再从右向左比
# 6、    这样第一次循环就实现了5放到列表中间,前面的都比5大,后面的都比5小
  • 快排与冒泡时间复杂度对比
    在这里插入图片描述
  • 快排最坏时间复杂度为何为O(n2)
  1. 每次划分只能将序列分为一个元素与其他元素两部分,这时的快速排序退化为冒泡排序
  2. 如果用数画出来,得到的将会是一棵单斜树,也就是说所有所有的节点只有左(右)节点的树;平均时间复杂度O(n*logn)

堆排

  • 代码实现
# !/usr/bin/env python
# -*- coding:utf-8 -*-
import random

def sift(data, low, high):
    '''  构造堆  堆定义:堆中某节点的值总是不大于或不小于父节点的值
    :param data: 传入的待排序的列表
    :param low:  需要进行排序的那个小堆的根对应的号
    :param high: 需要进行排序那个小堆最大的那个号
    :return:
    '''
    root = low  # root最开始创建堆时是最后一个有孩子的父亲对应根的号
    child = 2 * root + 1  # child子堆左孩子对应的号
    tmp = data[root]  # tmp是子堆中原本根的值(拿出最高领导)
    while child <= high:  # 只要没到子堆的最后(每次向下找一层)  #孩子在堆里
        if child + 1 <= high and data[child] < data[child + 1]:  # 如果有右孩纸,且比左孩子大
            child += 1
        if tmp < data[child]:  # 如果孩子还比子堆原有根的值tmp大,就将孩子放到子堆的根
            data[root] = data[child]  # 孩子成为子堆的根
            root = child  # 孩子成为新父亲(向下再找一层)
            child = 2 * root + 1  # 新孩子  (此时如果child<=high证明还有孩,继续找)
        else:
            break  # 如果能干就跳出循环就会流出一个空位
    data[root] = tmp  # 最高领导放到父亲位置

def heap_sort(data):
    '''调整堆'''
    n = len(data)
    ''' n//2-1 就是最后一个有孩子的父亲那个子堆根的位置 '''
    for i in range(n // 2 - 1, -1, -1):  # 开始位置,结束位置, 步长       这个for循环构建堆
        # for循环输出的是: (n // 2 - 1 ) ~ 0 之间的数
        sift(data, i, n - 1)  # i是子堆的根,n-1是堆中最后一个元素
    # 堆建好了,后下面就是挨个出数
    for i in range(n - 1, -1, -1):  # i指向堆的最后        这个for循环出数然后,调长调整堆
        # for循环输出的是 : n-1 ~ 0之间所有的数,n-1就是这个堆最后那个数的位置
        data[0], data[i] = data[i], data[0]  # 将堆的第一个和最后一个值调换位置(将最大数放到最后)
        sift(data, 0, i - 1)  # 将出数后的部分重新构建堆(调长)


data = list(range(100))
random.shuffle(data)  # 将有序列表打乱
heap_sort(data)
print(data)
  • 堆排序时间:O(nlogn) 公式推倒
    1)推导方法1:

    循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn ;

    2)推导方法2:

    1. 在一个堆中一次调长(调整堆)时间复杂度: log(n)
    2. 排序时一次出栈顶元素需要循环 n次,每次时间复杂度为:log(n)
    3. 所以总时间复杂度:nlog(n)

归并排序(递归调用)

  • 归并原理图
    在这里插入图片描述
  • 归并排序代码(时间复杂度:O(nlogn))
#! /usr/bin/env python
# -*- coding: utf-8 -*-
def merge(li, low, mid, high):
   '''
   :param li:      带排序列表
   :param low:     列表中第一个元素下标,一般是:0
   :param mid:     列表中间位置下标
   :param high:    列表最后位置下标
   :return:
   '''
   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

def mergesort(li, low, high):
   if low < high:
      mid = (low + high) // 2          #获取列表中间的索引下标
      mergesort(li, low, mid)          #先分解
      mergesort(li, mid+1, high)
      merge(li, low, mid, high)        #然后合并

data = [10,4,6,3,8,2,5,7]
mergesort(data, 0 , len(data) -1)
print(data)                            # [2, 4, 6, 8, 10, 12, 14, 16, 18]

快速排序,堆排序, 归并排序 比较

  • 三种排序算法时间复杂度都是( O(nlogn) )
  • 一般情况下,就运行时间而言:
    • 快速排序 < 归并排序 < 堆排序
  • 三种排序算法的缺点
    • 快速排序: 极端情况下排序效率低( O(n2) )
    • 归并排序: 需要额外内存开销(需要新建一个列表放排序的元素)
    • 堆排序: 在快的排序算法中相对较慢,堆排序最稳定
发布了50 篇原创文章 · 获赞 17 · 访问量 4839

猜你喜欢

转载自blog.csdn.net/Hanmin_hm/article/details/105051120