十大排序算法(python实现)【未完待续】

一、排序算法分类

十大排序算法可以分为两大类:
非线性时间排序:通过比较元素来决定元素间的相对次序,其时间复杂度不能突破O(nlogn)
线性时间排序:不通过比较元素来决定元素间的相对次序,可以突破比较排序时间下界,以线性时间运行

其中:稳定是指如果a原本在b前面,而a=b,排序之后a仍然在b的前面,不稳定是a可能会出现在b的后面。

二、非线性时间排序算法

1. 冒泡排序(Bubble Sort)

· 遍历一遍,比较相邻元素大小,若元素顺序错误则交换位置,一遍结束后,一个元素的位置排好;
· 重复遍历列表中未排序元素,直至完成列表排序;(如果在一次遍历中,没有发生元素交换行为,则说明列表所有元素顺序正确,排序完成)

每轮只对相邻两个数比较与交换,每轮会将一个最值交换到数据列首或尾,像冒泡一样,n个数据会操作n-1轮。时间复杂度O(n^2),额外空间开销在交换数据时那一个过渡空间,空间复杂度O(1)。
代码实现:

def bubble_sort(arr):
        def swap(i, j):
            arr[i], arr[j] = arr[j], arr[i]
        
        n = len(arr)
        swapped = True    

        x = -1

        while swapped:
            swapped = False
            x = x + 1
            for i in range(1, n - x):
                if arr[i-1] > arr[i]:
                    swap(i-1, i)
                    swapped = True
                    
        return arr

2. 简单选择排序(Select Sort)

· 将输入列表/数组分为两部分:已经排序的子列表和剩余要排序的子列表;
· 在未排序的子列表中找到最小的元素,并将其按顺序放置在排序的子列表中,重复此过程,直至列表完全排序;

n个数操作n-1轮,每轮选一个最值,时间复杂度O(n^2),额外空间开销在交换数据时那一个过渡空间,空间复杂度O(1)。
代码实现:

def select_sort(arr):
        for i in range(len(arr)):
            minvalue_idx = i
            for j in range(i, len(arr)):
                if arr[j] < arr[minvalue_idx]:
                    minvalue_idx = j
            arr[i], arr[minvalue_idx] = arr[minvalue_idx], arr[i]
        return arr

3. 快速排序(Quick Sort)

· 从数组中挑出一个元素,称为“基准”(pivot);
· 分区:将所有比基准值小的元素摆放在基准值前,比基准值大的元素摆在基准后值,基准值位于数列的中间位置;
· 递归地把小于基准值的子数列和大于基准值的子数列排序;

递归到最底部的判断条件是数列的大小是0或者1,此时该数列显然已经有序。
选取基准值数种具体方法,其选取方法对排序的时间性能有关。常见会选取数组第一个数作为基准。

1)设置两个变量i、j,排序开始的时候:i=0, j=N-1;
2)以第一个数组元素作为基准,赋值给pivot,即key=A[0];
3)从j开始向前搜索,即由后向前搜索(j--),找到第一个小于pivot的A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前向后搜索(i++),找到第一个大于pivot的A[i],将A[i]和A[j]互换;
5)重复3、4步,直到i=j;(3,4步中,没找到符合条件的值,即3中的A[j]不小于pivot,4中的A[i]不大于pivot时,改变i, j的值,j = j-1,i = i+1)

快排是原地排序,不需要辅助数组,但是递归调用需要辅助栈。快速排序最好的情况下是每次都正好将数组对半分,这样递归调用次数才是最少的,这种情况下比较次数为 Cn=2Cn/2+n,复杂度为 O(nlogn)。最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较n^2/2,为了防止数组从最开始就是有序的,在进行快排时需要随机打乱数组。

时间复杂度:快排涉及到递归调用,因此其时间复杂度与递归算法的复杂度相关,T[n] = aT[n/b] + f(n) 。
最优快排:每一次取到的元素都刚好平分整个数组,T[n] = 2T[n/2] + f(n), T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间。递归推算下来时间复杂度O( nlogn )。最差的情况就是每次取到的元素是数组中最大/或者最小的,此时时间复杂度O(n^2)。平均时间复杂度O(nlogn)。
空间复杂度:就地快速排序使用的空间O(1),是常数级,而真正消耗空间的就是递归调用,因为每次递归就要保持一些数据。额外空间开销出在暂存基准值,最优情况下空间复杂度为O(logn),每次都平分数组,最差情况下空间复杂度为O(n),每次取到的元素是最值。
代码实现:

def partition(arr, low, high):
        pivot = arr[low]
        while low < high:
            while low < high and arr[high] >= pivot:
                high = high -1
                
            arr[low] = arr[high]
            
            while low < high and arr[low] <= pivot:
                low = low + 1
                
            arr[high] = arr[low]
        
        arr[low] = pivot
        return low
                   
    def QucikSort(arr, low, high):
        if low >= high:
            return
        pivot_idx = partition(arr, low, high)
        QucikSort(arr, low, pivot_idx-1)
        QucikSort(arr, pivot_idx+1, high)
        return arr

4. 简单插入排序(Insert Sort)

· 从第一个元素开始,该元素可被认为已经被排序;
· 取出下一个元素,在已经排序的元素列表中从后向前扫描;
· 如果该已排序元素大于新元素,将新元素向前移动至下一位置;
· 重复上一步骤,直到找到已排序的元素小于或等于新元素的位置;
· 将新元素插入到该位置后;

简单插入排序需要操作n-1轮,每轮将一个未排序元素插入排好序列,开始时默认第一个元素有序,将剩余n-1个数逐个插入。插入操作具体包括:比较确定插入位置,数据移位腾出合适空位。
每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2);
额外空间开销是数据移位时产生的一个过渡空间,空间复杂度为O(1)。

def InsertSort(arr):
    n = len(arr)
    if n<=1: return arr
    for i in range(1,n):
        j = i
        target = arr[i]
        while j>0 and target<arr[j-1]:
            arr[j]=arr[j-1]
            j=j-1
        arr[j] = target
    return arr

5. 堆排序(Heap Sort)

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序。堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。


对堆中的结点按层进行编号,将这种逻辑结构映射到数组中如下:

该数组从逻辑上讲就是一个堆结构,用公式来描述堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

堆排序的基本思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样就会得到n个元素的次小值。如此反复执行,便能得到一个有序序列。
步骤一、构造初始堆:将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)

  1. 假设给定无序序列结构如下:
  1. 此时从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子节点len(arr)/2-1 = 5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
  1. 找到第二个非叶子节点4,其中4,9,8中9最大,4和9交换。

此时,交换导致了子根4,5,6结构混乱,继续调整,4,5,6中6最大,4和6交换位置,将无序序列构造成了一个大顶堆。

步骤二、将堆顶元素与末尾元素进行交换,使末尾元素最大,然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素,如此反复进行交换、重建、交换,直到整个序列有序。

  1. 将堆顶元素9与末尾元素4进行交换
  1. 重新调整结构,使其继续满足堆定义
  1. 再将堆顶元素8与末尾元素5进行交换,得到第二大元素8

后续过程,继续进行调整、交换,如此反复进行,最终使得整个序列有序。

总结:
· 将无序序列构建成一个堆,根据升序、降序需求选择大顶堆或小顶堆;
· 将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端;
· 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序;

def heapify(arr, n, i):
    largest = i
    l = 2 * i + 1
    r = 2 * i +2
            
    if l < n and arr[i] < arr[l]:
        largest = l
            
    if r < n and arr[largest] < arr[r]:
        largest = r
            
    if largest != i:
        arr[i], arr[largest] = arr[largest], arr[i] #exchange
                
        heapify(arr, n, largest)
        
def heapSort(arr):
    n = len(arr)
    
    # Build a maxheap
    for i in range(n, -1, -1):
        heapify(arr, n, i)
            
    # exchange the max to the end
    for i in range(n-1, 0, -1):
        arr[i], arr[0] = arr[0], arr[i]
        heapify(arr, i, 0)

6. 希尔排序(Shell Sort)

7. 归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序方法,该算法是分治法(Divide and Conquer)的一个非常典型的应用。先使每个子序列有序,再将两个有序子序列合并成一个有序序列称为2-路归并。

· 把长度为n的输入序列分成为两个长度为n/2的子序列;
· 对这两个子序列分别采用归并排序;
· 将两个排好序的子序列合并成一个最终的排序序列;

归并排序包括了Sort和Merge两部分。当有n个待排序元素时,需要进行logn轮归并排序,每一轮归并,其比较元素不超过n个,因此归并排序时间复杂度为nlogn;而在排序时需记录的中间元素个数与待排序元素个数相等,因此空间复杂度为O(n)。

def merge_sort(arr, low, high):
    if low >= high: return
    mid = (low + high) // 2
    merge_sort(arr, low, mid)
    merge_sort(arr, mid+1, high)
    merge(arr, low, mid, high)
        
def merge(arr, low, mid, high):
    container = []
    i, j = low, mid + 1
    while i<=mid and j<=high:
        if arr[i] <= arr[j]:
            container.append(arr[i])
            i = i + 1
        else:
            container.append(arr[j])
            j = j + 1
        if i<=mid:
            container.extend(arr[i:mid+1])
        elif j<=high:
            container.extend(arr[j:high+1])
    arr[low:high+1] = container

三、线性时间排序算法

1. 计数排序(Counting Sort)

2. 桶排序(Bucket Sort)

3. 基数排序(Radix Sort)

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