算法是程序的灵魂之快希归

Posted 骑着哈哥去旅行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法是程序的灵魂之快希归相关的知识,希望对你有一定的参考价值。

一,希尔排序(shell sort)

希尔排序其实是由插入排序演变过来的,是插入排序的一种更高效的改进版本。希尔排序是不稳定排序算法

希尔排序是基于插入排序的以下两点性质而提出改进方法的

1,插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
2,但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

算法原理

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

例如有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样:

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

然后我们对每列进行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ].这时10已经移至正确位置了,然后再以3为步长进行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序之后变为:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后以1步长进行排序(此时就是简单的插入排序了)。

基于python的程序代码(折半)

import random

shell_list = [random.randint(i, i * 7 + 8) for i in range(11)]
random.shuffle(shell_list)
print('未排序>>>:', shell_list)
n = len(shell_list)
gap = n // 2
while gap >= 1:
    for i in range(gap, n):
        while i > 0 and shell_list[i] < shell_list[i-gap]:
            shell_list[i], shell_list[i-gap] = shell_list[i-gap], shell_list[i]
            i -= gap
    gap //= 2

print('已排序>>>:', shell_list)

基于python的程序代码(Knuth序列)

Knuth 间隔序列 3*h+1,即1, 4,13, 40, 121…

# knuth序列
import random

shell_list = [random.randint(i, i * 7 + 8) for i in range(11)]
random.shuffle(shell_list)
print('未排序>>>:', shell_list)
n = len(shell_list)
gap = 0
while gap <= n // 3:
    gap = gap * 3 + 1
while gap >= 1:
    for i in range(gap, n):
        while i > 0 and shell_list[i] < shell_list[i-gap]:
            shell_list[i], shell_list[i-gap] = shell_list[i-gap], shell_list[i]
            i -= gap
    gap //= 3

print('已排序>>>:', shell_list)

复杂度:

希尔排序的平均时间复杂度约为O(n^1.3)
最坏时间复杂度为O(n^2)
最好时间复杂度为O(n)
空间复杂度为O(1)

二,归并排序(merge sort)

归并排序,是建立在归并操作上的一种有效的排序算法。1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

采用分治法:

分割:递归地把当前序列平均分割成两半。
整合:在保持元素顺序的同时将上一步得到的子序列整合到一起(归并)。

归并操作:

归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。


基于python的程序代码

import random


def merge_sort(merge_list):
    # 求列表长度
    n = len(merge_list)
    if n == 1:  # 当长度为1时,说明不能再分割
        return merge_list
    middle_num = n // 2  # 找到中间元素
    left_list = merge_sort(merge_list[:middle_num])  # 左边通过归并排序后形成新的有序的新列表
    right_list = merge_sort(merge_list[middle_num:])  # 右边通过归并排序后形成新的有序的新列表
    # 将两个有序的子序列合并为一个新的有序序列
    left_pointer, right_pointer = 0, 0  # 定义左右两个起始指针
    new_list = []
    while left_pointer < len(left_list) and right_pointer < len(right_list):  # 只要左右两个指针有一个到达尾部,循环便结束
        if left_list[left_pointer] < right_list[right_pointer]:  # 左边比右边小时,将左边数小的元素追加到新的空列表中
            new_list.append(left_list[left_pointer])
            left_pointer += 1  # 左边指针位置加一
        else:
            new_list.append(right_list[right_pointer])  # 右边比左边小时或左右相等时,将右边的元素追加到新的列表中
            right_pointer += 1  # 右边的指针位置加一
    # 当左右任意一边指针到达尾部时,就将另一边剩余的所有元素直接追加到新列表中。
    new_list += left_list[left_pointer:]
    new_list += right_list[right_pointer:]
    return new_list


if __name__ == '__main__':
    merge_list = [random.randint(i, i * 7 + 8) for i in range(21)]
    random.shuffle(merge_list)
    print('未排序>>>:', merge_list)
    merge_list = merge_sort(merge_list)
    print('已排序>>>:', merge_list)

复杂度:

归并排序的平均时间复杂度,最坏时间复杂度和最好时间复杂度都为O(n*log n),空间复杂度是O(n)。(注:此处的对数函数是n倍以2为底n的对数)

归并排序是稳定的

三,快速排序

快速排序,又称分区交换排序(partition-exchange sort),简称快排,最早由东尼·霍尔提出。在平均状况下,排序n个项目要O(nlog n)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序O(nlog n)通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。(注:此处的对数函数是n倍以2为底n的对数)

算法原理

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。

步骤为:

挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成。
递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。


基于python的程序代码

import random


def pivot_sort(quick_list, left, right):
    pivot_number = quick_list[left]  # 选择第一个元素为轴元素
    while left < right:  # 假如left=right,说明要排序的列表只有一个元素;left<right说明要排序的列表长度大于等于2;
        while left < right and quick_list[right] >= pivot_number:  # 在保证列表长度大于2的情况下,如果右边的元素大于轴元素
            right -= 1  # 就让右边的指针往左边移一步
        else:  # 假如右边的元素小于轴元素(也可省略,但下面的语句要向前缩进四个空格)
            quick_list[left] = quick_list[right]  # 就把右边的元素移到左边的空位置上
        while left < right and quick_list[left] <= pivot_number:  # 在保证列表长度为2的情况下,假如左边的元素小于轴元素,
            left += 1  # 就让左边的指针像右移动一步
        else:  # 假如左边的元素大于轴元素(也可省略,但下面的语句要向前缩进四个空格)
            quick_list[right] = quick_list[left]  # 就把左边的元素移到右边的空位上
    quick_list[left] = pivot_number  # 让轴元素归位
    return left  # 这里返回left或者right都可以,即轴元素归位的索引


def quick_sort(quick_list, left, right):
    if left < right:
        middle_num = pivot_sort(quick_list, left, right)
        quick_sort(quick_list, left, middle_num - 1)
        quick_sort(quick_list, middle_num + 1, right)


if __name__ == '__main__':
    quick_list = [i for i in range(100)]
    random.shuffle(quick_list)
    print('\\033[31;1m未排序>>>:\\033[0m', quick_list)
    quick_sort(quick_list, 0, len(quick_list) - 1)
    print('\\033[32;1m排序后>>>:\\033[0m', quick_list)

复杂度:

快速排序的平均时间复杂度和最好时间复杂度都为O(n*log n),最坏时间复杂度O(n^2),空间复杂度是O(log n)。(注:此处的对数函数是以2为底的)

快速排序是不稳定的

以上是关于算法是程序的灵魂之快希归的主要内容,如果未能解决你的问题,请参考以下文章

排序之快排

数据-第3课-程序的灵魂-算法

程序的灵魂---算法

第2章 算法----程序的灵魂

第三课:程序的灵魂-算法

第三课初识程序的灵魂------------------------狄泰软件学院