本文根据《算法(第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. gin中获取查询字符串参数

    package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := ...

  2. AOP-底层原理(JDK动态代理实现)

    AOP(JDK动态代理) 1,使用JDK动态代理,使用Proxy类里面的方法创建代理对象 (1)调用 newProxyInstance 方法 方法有三个参数 第一参数,类加载器 第二参数,增强方法所在 ...

  3. iptables匹配条件总结1

    源地址 -s选项除了指定单个IP,还可以一次指定多个,用"逗号"隔开即可 [root@web-1 ~]# iptables -I INPUT -s 172.16.0.116,172 ...

  4. python os模块 文件操作

    Python内置的os模块可以通过调用操作系统提供的接口函数来对文件和目录进行操作 os模块的基本功能: >>> import os >>> os.name 'po ...

  5. 数据库查询语句遇到:Unknown column 'XXXX' in 'where clause'解决方法

    数据库查询语句遇到:Unknown colunm 'XXX' in 'where clause'解决方法 根本原因:可能是sql语句所用到的数据类型错误(int与String)弄错- 我的情况: 在网 ...

  6. poj_3190

    首先把所有的牛排个序,优先按照起始时间 其次建立一个堆,重载小于号(只可以重载小于号),优先按照右端点的时间排序,大的放下面(sort的时候会放后面),堆顶是结束时间最快的 #include < ...

  7. 「JOI 2015 Final」城墙

    「JOI 2015 Final」城墙 复杂度默认\(m=n\) 暴力 对于点\((i,j)\),记录\(ld[i][j]=min(向下延伸的长度,向右延伸的长度)\),\(rd[i][j]=min(向 ...

  8. CF1581

    其实是手速场,但因为 \(\rm D, E\) 数据范围时限太阴间卡住了. D \(\rm Hint:\) 本题常数极小加适当剪枝可以 \(\mathcal{O}(n ^ 5)\) 过 \(100\) ...

  9. HTML元素的隐藏方式

    感谢原文作者:幼儿园中的小小白 原文链接:https://blog.csdn.net/weixin_43846130/article/details/95963426 一.元素的隐藏方式: 1.dis ...

  10. Loadrunner 11 中的Java Vuser

    Java vuser是自定义的java虚拟用户脚本,脚本中可以使用标准的java语言. 1.安装jdk 注意,lr11最高支持jdk1.6 2.配置环境变量 3.在lr中选择java vuser协议 ...