N种排序算法
本文根据《算法(第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种排序算法的更多相关文章
- 几种排序算法的学习,利用Python和C实现
之前学过的都忘了,也没好好做过总结,现在总结一下. 时间复杂度和空间复杂度的概念: 1.空间复杂度:是程序运行所以需要的额外消耗存储空间,一般的递归算法就要有o(n)的空间复杂度了,简单说就是递归集算 ...
- 秒杀9种排序算法(JavaScript版)
一:你必须知道的 1> JS原型 2> 排序中的有序区和无序区 3> 二叉树的基本知识 如果你不知道上面三个东西,还是去复习一下吧,否则,看下面的东西有点吃力. 二:封装丑陋的原型方 ...
- PHP的几种排序算法的比较
这里列出了几种PHP的排序算法的时间比较的结果,,希望对大家有所帮助 /* * php 四种排序算法的时间与内置的sort排序比较 * 3000个元素,四种算法的排序所用的时间比较 * 冒泡排序 85 ...
- 学习Java绝对要懂的,Java编程中最常用的几种排序算法!
今天给大家分享一下Java中几种常见的排序算法的Java代码 推荐一下我的Java学习羊君前616,中959,最后444.把数字串联起来! ,群里有免费的学习视频和项目给大家练手.大神有空时也 ...
- C#常用8种排序算法实现以及原理简介
public static class SortExtention { #region 冒泡排序 /* * 已知一组无序数据a[1].a[2].--a[n],需将其按升序排列.首先比较a[1]与a[2 ...
- 排序—时间复杂度为O(n2)的三种排序算法
1 如何评价.分析一个排序算法? 很多语言.数据库都已经封装了关于排序算法的实现代码.所以我们学习排序算法目的更多的不是为了去实现这些代码,而是灵活的应用这些算法和解决更为复杂的问题,所以更重要的是学 ...
- java算法03 - 常用的8种排序算法
Java常用的八种排序算法: 插入排序 - 直接插入排序 每次将待排序的记录按照关键字的大小,插入到前面已经排好序的记录的适当位置.直到全部记录插入完成. 代码实现 /** * 直接插入排序 O(n^ ...
- 用 C 语言描述几种排序算法
排序算法是最基本且重要的一类算法,本文基于 VS2017,使用 C 语言来实现一些基本的排序算法. 一.选择排序 选择排序,先找到数组中最小的元素,然后将这个元素与数组的第一个元素位置互换(如果第一个 ...
- 【C++】四种排序算法的时间比较
四种排序算法的时间比较 [注]clock函数对输入(用户输入)元素N排序的计时 #include<iostream> #include<time.h> using namesp ...
- 几种排序算法及Java实现排序的几种方式
几种排序算法 下面的例子介绍了4种排序方法: 冒泡排序, 选择排序, 插入排序, 快速排序 package date201709.date20170915; public class SortUtil ...
随机推荐
- es6中的导入与导出
参考:https://www.cnblogs.com/sherrycat/p/11152994.html
- Fastjson反序列化漏洞分析 1.2.22-1.2.24
Fastjson反序列化漏洞分析 1.2.22-1.2.24 Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两 ...
- 使用 ES Module 的正确姿势
前面我们在深入理解 ES Module 中详细介绍过 ES Module 的工作原理.目前,ES Module 已经在逐步得到各大浏览器厂商以及 NodeJS 的原生支持.像 vite 等新一代的构建 ...
- 利用JavaWeb实现课程信息添加
整体架构 :HTML+JAVABEAN+SERVLET 一.首先先简单介绍一下所需要的组件 原文地址 https://www.cnblogs.com/zll20153246/p/ ...
- Android系统编程入门系列之硬件交互——通信硬件Bluetooth
通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持.因此针对这个弊端,可以考虑 ...
- python 模块Module
一.模块 1.定义: 模块是一个python文件,以.py结尾,包含了python对象定义和python语句. 2.作用: 模块内可以定义函数.类和变量: 模块可以提高代码的可维护性和重复使用: 让代 ...
- vector概述
vector是一个能够支持任何类型的容器,本身为一个可以动态增长的数组. 1.vector基本数据结构 STL中所有的容器都包括三部分: 迭代器,遍历容器的元素,控制容器空间的边界和元素移动. 构造函 ...
- qiankun 2.x 运行时沙箱 源码分析
简介 从源码层面详细讲解了 qiankun 框架中的 JS 沙箱 和 样式沙箱的实现原理. 序言 沙箱 这个词想必大家应该不陌生,即使陌生,读完这篇文章也就不那么陌生了 沙箱 (Sandboxie) ...
- C# 实例解释面向对象编程中的单一功能原则
在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原 ...
- CF1270G Subset with Zero Sum
首先一定要从每个数的范围 \(i - n \le a_i \le i - 1\) 入手,最开始是这样一个想法,不难发现对于每个 \(i\) 都能选 \(n\) 个数,并且能选的右端点在 \(i - 1 ...