排序算法归档

算法复杂度速查:https://www.jianshu.com/p/2bae94d4ea9b

快速排序是二叉树的前序遍历,归并排序是二叉树的后序遍历

排序算法的稳定性指的是在排序过程中,能够保证相等元素在排序后的相对位置与排序前一致。

一、稳定排序算法

  1. 冒泡排序:
    • 重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
    • 在这个过程中,相等元素不会交换位置,所以是稳定排序。
  2. 插入排序:
    • 对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
    • 当遇到相等元素时,会插入到相等元素的后面,不会改变相等元素的相对顺序,所以是稳定排序。
  3. 归并排序:
    • 采用分治的思想,将数列分成两部分,分别排序后再进行合并。
    • 在合并过程中,如果两个元素相等,会按照它们在原始数列中的顺序依次放入结果数列中,所以是稳定排序。

二、不稳定排序算法

  1. 选择排序:
    • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
    • 每次选择最小元素时,可能会改变相等元素的相对位置,所以是不稳定排序。
  2. 快速排序:
    • 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。
    • 在分区过程中,可能会改变相等元素的相对位置,所以是不稳定排序。
  3. 希尔排序:
    • 也称递减增量排序算法,是插入排序的一种更高效的改进版本。
    • 由于多次插入排序,不同的步长可能会导致相等元素的相对位置发生变化,所以是不稳定排序。

遍历顺序

1、遍历的过程中,所需的状态必须是已经计算出来的。
2、遍历结束后,存储结果的那个位置必须已经被计算出来。

归并排序

分治法

  • mergesort(): 分,从中间分离array,分别排序(递归),最后调用merge合并
  • merge():治,将2个array合并为一个有序array,返回有序array
    • 若2个array都还有,依次填入
    • 若有一个遍历结束,按序填入
def mergesort(arr):
    if len(arr)<=1:
        return arr
    mid = int(len(arr)/2)
    left = mergesort(arr[0:mid])
    right = mergesort(arr[mid:])
    return merge(left,right)

def merge(left,right):
    l = 0
    r = 0
    res = []
    while( l<len(left) and r < len(right)):
        if left[l]<= right[r]:
            res.append(left[l])
            l +=1
        else:
            res.append(right[r])
            r +=1
    if ( l<len(left) ):
        res.extend(left[l:])
    if ( r<len(right) ):
        res.extend(right[r:])
    return res

快速排序

快速排序的过程是一个构造二叉搜索树的过程。 快排是不稳定排序。
为了避免最坏情况,引入随机 pivot。

更快的做法
在 partition 过程中,把 left 作为 pivot,左右同时交换,最终,把l=r 后,r 和pivot(left)交换,返回 r。

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int: 
        return self.quickselect(nums, 0, len(nums)-1, k-1)
    def partition(self, nums, left, right):
        pivot = nums[left]
        l = left+1
        r = right
        while (l <=r):
            if (nums[l]< pivot and nums[r]> pivot):
                nums[l], nums[r] = nums[r], nums[l]
                l += 1
                r -= 1
            if nums[l]>= pivot:
                l += 1
            if nums[r] <= pivot:
                r -= 1 
        nums[left], nums[r] = nums[r], nums[left]
        return r

    def quickselect(self, nums, left, right, k):
        index = self.partition(nums, left, right)
        if index==k:
            return nums[k]
        elif index<k:
            return self.quickselect(nums, index+1, right, k)
        elif index >k:
            return self.quickselect(nums, left, index-1, k )

做法:此算法较慢,无法通过leetcode
在l和r之间,把较小的数换在前面,然后放置pivot,返回pivot的index。

  1. 把right定位pivot
  2. 从left到i之间,为小于pivot的数
  3. j遍历left~right,如果违背,将j处的小数换到前边i的位置。
def quicksort(arr,left,right):
    if left<right:
        if len(arr)<=1:
            return 
        idx = partition(arr,left, right)
        quicksort(arr,left,idx-1)
        quicksort(arr,idx+1,right)

def partition(arr,left,right):
    pivot = arr[right]
    index = left
    for j in range(left,right):
        if arr[j]<pivot:
            arr[i], arr[j] = arr[j], arr[i]
            index+=1
    arr[index], arr[right] = arr[right], arr[index]
    return i+1


arr = [12,1,7,3,5,6]
quicksort(arr,0,len(arr)-1)
print(arr)

quickselect算法

class Solution:
    def smallestK(self, arr: List[int], k: int) -> List[int]:
        if len(arr)==0 or k==0:
            return list()
        return self.quicksort(arr, 0, len(arr)-1, k)

    def partition(self, arr, left, right, k):
        pivot_value = arr[right]
        index = left
        for j in range(left, right):
            if arr[j] < pivot_value:
                arr[index], arr[j] = arr[j], arr[index]
                index +=1
        arr[index], arr[right] = arr[right], arr[index]
        return  index

    def quickselect(self,arr, left,right, k):
        if left==right:
            return arr[:k]
        index = self.partition(arr, left, right, k)
        if index ==k:
            return arr[:k]
        elif index<k:
            return self.quicksort(arr, index+1, right, k)
        elif index>k:
            return self.quicksort(arr, left, index-1, k)

堆排序

步骤(以升序排序为例):

  • 构造一个大顶堆
  • 再将大顶堆的的头尾元素互换(此步骤是为了模拟逐个出队的过程)
def max_heapify(heap,heapSize,root):  # 调整列表中的元素并保证以root为根的堆是一个大根堆
    '''
    给定某个节点的下标root,这个节点的父节点、左子节点、右子节点的下标都可以被计算出来。
    父节点:(root-1)//2
    左子节点:2*root + 1
    右子节点:2*root + 2  即:左子节点 + 1
    '''
    left = 2*root + 1
    right = left + 1
    larger = root
    if left < heapSize and heap[larger] < heap[left]:
        larger = left
    if right < heapSize and heap[larger] < heap[right]:
        larger = right
    if larger != root:  # 如果做了堆调整则larger的值等于左节点或者右节点的值,这个时候做堆调整操作
        heap[larger], heap[root] = heap[root], heap[larger]
        # 递归的对子树做调整
        max_heapify(heap, heapSize, larger)


def build_max_heap(heap):  # 构造一个堆,将堆中所有数据重新排序
    heapSize = len(heap)
    for i in range((heapSize -2)//2,-1,-1):  # 自底向上建堆
        max_heapify(heap, heapSize, i)

import random

def heap_sort(heap):  # 将根节点取出与最后一位做对调,对前面len-1个节点继续进行堆调整过程。
    build_max_heap(heap)
    # 调整后列表的第一个元素就是这个列表中最大的元素,将其与最后一个元素交换,然后将剩余的列表再递归的调整为最大堆
    for i in range(len(heap)-1, -1, -1):
        heap[0], heap[i] = heap[i], heap[0]
        max_heapify(heap, i, 0)

# 测试
if __name__ == '__main__':
    a = [30, 50, 57, 77, 62, 78, 94, 80, 84]
    print(a)
    heap_sort(a)
    print(a)
    b = [random.randint(1,1000) for i in range(1000)]
    print(b)
    heap_sort(b)
    print(b)

python中 heapq 的使用

注意默认为小顶堆,如果需要大顶堆,需要用负值。

import heapq

# 创建一个列表
data = [5, 7, 9, 1, 3]

# 将列表转换为堆
heapq.heapify(data)
print("Heapified data:", data)  # 输出: [1, 3, 9, 7, 5]

# 向堆中添加元素
heapq.heappush(data, 4)
print("After heappush(4):", data)  # 输出: [1, 3, 4, 7, 5, 9]

# 从堆中删除并返回最小元素
smallest = heapq.heappop(data)
print("Popped smallest element:", smallest)  # 输出: 1
print("After heappop():", data)  # 输出: [3, 5, 4, 7, 9]

# 获取堆中的最小元素,但不删除
smallest_peek = data[0]
print("Peek smallest element:", smallest_peek)  # 输出: 3

# 从堆中删除并返回最小元素,推入新的元素
smallest_replaced = heapq.heapreplace(data, 2)
print("Replaced smallest element:", smallest_replaced)  # 输出: 3
print("After heapreplace(2):", data)  # 输出: [2, 5, 4, 7, 9]

# 获取前 n 个最小的元素
three_smallest = heapq.nsmallest(3, data)
print("Three smallest elements:", three_smallest)  # 输出: [2, 4, 5]

# 获取前 n 个最大的元素
three_largest = heapq.nlargest(3, data)
print("Three largest elements:", three_largest)  # 输出: [9, 7, 5]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353