本文根据《算法(第4版)》和《算法图解》整理。文中代码使用python编写。

(一)选择排序

每次遍历整个数组,选出其中最小值。如果数组长度为n,则需要(n-1)+(n-2)+...+2+1次操作,则算法的时间复杂度用大O表示法表示为,但是大O表示法省略诸如1/2这样的常数,因此该方法的大O表示为

选择排序的代码:

>>> def findSmallest(arr):
smallest = arr[0]
smallest_index = 0
for i in range(1, len(arr)):
if arr[i] < smallest:
smallest = arr[i]
smallest_index = i
return smallest_index >>> def selectionSort(arr):
newArr = []
for i in range(len(arr)):
smallest = findSmallest(arr)
newArr.append(arr.pop(smallest))
return newArr

(二)插入排序

用个例子来说明,arr=[5,1,2,4,7,3]。取arr[1]与arr[0]相比,如果arr[1]<arr[0],则交换arr[1]和arr[0],交换后的arr=[1,5,2,4,3,7]。再取arr[2]与arr[1]相比,如果arr[2]<arr[1],则交换arr[2]和arr[1],交换后的arr=[1,2,5,4,3,7],并再将新数组中的arr[1]与arr[0]相比,如果arr[1]<arr[0],则交换。

和选择排序不同的是,插入排序所需的时间取决于输入中元素的初始顺序。对于随机排列的长度为N且主键不重复的数组,平均情况下插入排序需要~N^2/4次比较以及~N^2/4次交换。最坏情况下需要~N^2/2次比较和~N^2/2次交换,最好情况下需要N-1次比较和0次交换。

插入排序的代码:

def insertSort(arr):
for i in range(1,len(arr)):
for j in range(i,0,-1):
if arr[j]<arr[j-1]:
temp=arr[j]
arr[j]=arr[j-1]
arr[j-1]=temp
return arr

通过在内循环中将较大的元素向右移动而不是交换,便可以大幅度提高插入排序的速度。同样以arr=[5,1,2,4,7,3]为例,令对比的基准base=arr[1],如果base<arr[0],则arr[0]向右移动一位,即arr[1]=arr[0],内循环结束,即arr[0]=base,新数组为arr=[1,5,2,4,7,3]。令base=arr[2],如果base<arr[1],则arr[1]向右移动一位,arr[2]=arr[1],继续内循环,即对比base和arr[0],在本例中base>arr[0],结束内循环,并且arr[1]=base。新数组为arr=[1,2,5,4,7,3]。

快速插入排序的代码:

def insertSort2(arr):
for i in range(1,len(arr)):
_base=arr[i]
k=0
for j in range(i,0,-1):
if _base<arr[j-1]:
arr[j]=arr[j-1]
else:
k=j
break
arr[k]=_base return arr

(三)希尔排序

对于大规模乱序数组而言,插入排序很慢,因为它只交换相邻的元素,因此元素只能一点一点地从数组的一段移动到另一端。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。即希尔排序使数组中任意间隔为h的元素都是有序的。例如

[2, 6, 3, 5, 10, 4, 8, 21, 1, 34, 7, 9]

当h=4时,使得2-10-1有序,6-4-34有序,3-8-7有序,5-21-9有序,得到的新数组为

[1, 4, 3, 5, 2, 6, 7, 9, 10, 34, 8, 21]

然后h=1时,就是普通的插入排序,得到的新数组为

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 21, 34]

对于本例,使用希尔排序一共交换13次,而使用插入排序的交换次数为21次

希尔排序的代码:

def ShellSort(arr):
h=1
while h<len(arr)/3:
h=3*h+1 while h>=1:
for i in range(h,len(arr)):
for j in range(i,h-1,-h):
if arr[j]<arr[j-h]:
temp=arr[j]
arr[j]=arr[j-h]
arr[j-h]=temp
else:
break
h=math.floor(h/3)
return arr

(四)归并排序

要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。归并的一种直截了当的做法是将两个不同的有序数组归并到第三个数组中。例如如下数组:

[2,4,9,13,1,5,8,23]

是由两个有序数组sub1=[2,4,9,13]和sub2=[1,5,8,23]组成的,将这两个有序的子数组归并为一个数组,就可以先创建一个长度为8的辅助数组aux,然后sub1[0]和sub2[0]作比较,把小的那个写入到aux[0]中,在本例中,sub2[0]<sub1[0],因此aux[0]=sub2[0],然后再取sub2[1]与sub1[0]对比,如此循环,终止条件为sub1或者sub2取到了最后一个数。

归并方法的代码:

def merge(arr,low,mid,high):
aux=[0]*len(arr)
i=low
j=mid+1 for k in range(low,high+1):
if i>mid:
aux[k]=arr[j]
j=j+1
elif j>high:
aux[k]=arr[i]
i=i+1
elif arr[i]<arr[j]:
aux[k]=arr[i]
i=i+1
else:
aux[k]=arr[j]
j=j+1
return aux

在构造两个有序的子数组时,可以分为自顶向下和自底向上两种方法。

自顶向下:递归得到树结构需要归并的子数组,以长度为16的数组为例,如下图所示。

先将arr[0-1]处理为有序数组,再将arr[2-3]处理为有序子数组,然后归并arr[0-1]和arr[2-3]这两个有序子数组,得到有序的arr[0-3]子数组。同样地,将arr[4-5]和arr[6-7]处理为有序子数组,然后归并得到arr[4-7]这个有序子数组。再将arr[0-3]和arr[4-7]归并为有序的arr[0-7]子数组。以同样的方式得到有序的arr[8-15]子数组,最后将arr[0-7]和arr[8-15]归并为有序的新数组。

自顶向下的归并排序代码:

def merge(low,mid,high):
i=low
j=mid+1 for k in range(low,high+1):
aux[k]=arr[k] for k in range(low,high+1):
if i>mid:
arr[k]=aux[j]
j=j+1
elif j>high:
arr[k]=aux[i]
i=i+1
elif aux[i]<aux[j]:
arr[k]=aux[i]
i=i+1
else:
arr[k]=aux[j]
j=j+1 def recurisonSort(low,high):
if low<high:
mid=math.floor(low+(high-low)/2)
recurisonSort(low,mid)
recurisonSort(mid+1,high)
merge(low,mid,high) def mergeSort(_arr):
global aux
aux=[0]*len(_arr) global arr
arr=[]
for i in _arr:
arr.append(i) recurisonSort(0,len(arr)-1)
return arr

对于长度为N的任意数组,自顶向下的归并排序需要1/2NlgN-NlgN次比较,最多需要访问数组6NlgN次。

自底向上:先归并微型数组,然后在成对归并得到的子数组。即首先进行的是两两归并,然后是四四归并,进而八八归并,一直下去。这种实现方法比标准递归方法所需要的代码量更少。

自底向上归并排序代码:

def mergeSort2(_arr):
length=len(_arr)
global aux
aux=[0]*length global arr
arr=[]
for j in _arr:
arr.append(j) i=1
while(i<length):
for k in range(0,length-i,2*i):
high=min(k+2*i-1,length-1)
if k<high:
merge(k,k+i-1,high)
i=i*2 print(arr)

(五)快速排序

分而治之(divide and conquer, D&C)的思想:1.找出简单的基线条件;2.确定如何缩小问题的规模,使其符合基线条件。

那么将D&C思想应用于排序任务中,其思路应如下:

基线条件就是只有一个元素的数组,这样的数组顺序就是自己。在数组中任取一个元素作为基准值,那么该数组将会被划分为三部分

  小于基准值的子数组 + 基准值 + 大于基准值的子数组

这样就会不断地缩小数组的规模,直到只剩一个元素为止。

快速排序的代码:

>>> def quicksort(arr):
if len(arr) < 2:
return arr
else:
pivot = arr[0]
less = [i for i in arr[1:] if i <= pivot]
greater = [i for i in arr[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater) >>> arr = [3,5,1,9,7]
>>> quicksort(arr)
[1, 3, 5, 7, 9]
>>>

其实,排序的方法已经包含在各种语言中了,比如Python和C#都是使用Sort方法,就可以对一个数组进行从小到大的排序了。不过了解算法的本质应该也不是什么坏事吧。

(未完待续)

N种排序算法的更多相关文章

  1. 几种排序算法的学习,利用Python和C实现

    之前学过的都忘了,也没好好做过总结,现在总结一下. 时间复杂度和空间复杂度的概念: 1.空间复杂度:是程序运行所以需要的额外消耗存储空间,一般的递归算法就要有o(n)的空间复杂度了,简单说就是递归集算 ...

  2. 秒杀9种排序算法(JavaScript版)

    一:你必须知道的 1> JS原型 2> 排序中的有序区和无序区 3> 二叉树的基本知识 如果你不知道上面三个东西,还是去复习一下吧,否则,看下面的东西有点吃力. 二:封装丑陋的原型方 ...

  3. PHP的几种排序算法的比较

    这里列出了几种PHP的排序算法的时间比较的结果,,希望对大家有所帮助 /* * php 四种排序算法的时间与内置的sort排序比较 * 3000个元素,四种算法的排序所用的时间比较 * 冒泡排序 85 ...

  4. 学习Java绝对要懂的,Java编程中最常用的几种排序算法!

    今天给大家分享一下Java中几种常见的排序算法的Java代码 推荐一下我的Java学习羊君前616,中959,最后444.把数字串联起来!     ,群里有免费的学习视频和项目给大家练手.大神有空时也 ...

  5. C#常用8种排序算法实现以及原理简介

    public static class SortExtention { #region 冒泡排序 /* * 已知一组无序数据a[1].a[2].--a[n],需将其按升序排列.首先比较a[1]与a[2 ...

  6. 排序—时间复杂度为O(n2)的三种排序算法

    1 如何评价.分析一个排序算法? 很多语言.数据库都已经封装了关于排序算法的实现代码.所以我们学习排序算法目的更多的不是为了去实现这些代码,而是灵活的应用这些算法和解决更为复杂的问题,所以更重要的是学 ...

  7. java算法03 - 常用的8种排序算法

    Java常用的八种排序算法: 插入排序 - 直接插入排序 每次将待排序的记录按照关键字的大小,插入到前面已经排好序的记录的适当位置.直到全部记录插入完成. 代码实现 /** * 直接插入排序 O(n^ ...

  8. 用 C 语言描述几种排序算法

    排序算法是最基本且重要的一类算法,本文基于 VS2017,使用 C 语言来实现一些基本的排序算法. 一.选择排序 选择排序,先找到数组中最小的元素,然后将这个元素与数组的第一个元素位置互换(如果第一个 ...

  9. 【C++】四种排序算法的时间比较

    四种排序算法的时间比较 [注]clock函数对输入(用户输入)元素N排序的计时 #include<iostream> #include<time.h> using namesp ...

  10. 几种排序算法及Java实现排序的几种方式

    几种排序算法 下面的例子介绍了4种排序方法: 冒泡排序, 选择排序, 插入排序, 快速排序 package date201709.date20170915; public class SortUtil ...

随机推荐

  1. Maven生命周期,插件,单元测试junit

    maven生命周期,maven命令,maven插件 maven生命周期:就是maven构建项目的过程,清理,编译,测试,报告,打包,安装,部署 maven命令:maven独立使用,通过命令,完成mav ...

  2. Device or resource busy

    格式化磁盘显示忙碌,如何解决呢? [root@jp33e503-11-8 ~]# mkfs.xfs /dev/sdc mkfs.xfs: cannot open /dev/sdc: Device or ...

  3. Java高并发下多线程编程

    1.创建线程 Java中创建线程主要有三种方式: 继承Thread类创建线程类: 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此也把run方法称为 ...

  4. ESP32:蓝牙BLE控制M3508电机

    ESP32:蓝牙BLE控制M3508电机 先给各位朋友拜个年,祝大家新春快乐,事事顺利,身体健康啊! 还是熟悉的3508,内容概述: ESP32主控 蓝牙BLE通信 使用实时系统(FreeRTOS) ...

  5. Luogu P1438无聊的数列

    洛谷 P1438无聊的数列 题目链接 点这里! 题目描述 维护一个数列\(a_i\),支持两种操作: 给出一个长度等于 \(r-l+1\)的等差数列,首项为\(k\) 公差为\(d\) 并将它对应加到 ...

  6. MySQL不建议delete删除数据

    InnoDB存储架构 从这张图可以看到,InnoDB存储结构主要包括两部分:逻辑存储结构和物理存储结构. 逻辑上是由表空间tablespace -> 段segment或者inode -> ...

  7. jQuery EasyUI 的editor组件使用

    问题:最近在优化一个项目时,前端用到了 easyui这个插件来实现表格,搞了很久,才实现出一部分功能,但是还是有很多地方不熟悉,故记录一下,以后再研究 第一个实例------------------- ...

  8. spring学习四:Spring中的后置处理器BeanPostProcessor

    BeanPostProcessor接口作用: 如果我们想在Spring容器中完成bean实例化.配置以及其他初始化方法前后要添加一些自己逻辑处理.我们需要定义一个或多个BeanPostProcesso ...

  9. Ajax接收服务器返回的信息response

    Ajax可以向服务器发起请求,有去的方式,那么久必然可疑返回. 服务器返回的信息也可以通过Ajax接收. Ajax共有5种状态: 1.创建对象,没有调用open方法 2.对象发起请求http,已经调用 ...

  10. js图片预览代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...