排序算法说明
排序算法是比较常用的基本算法,分类很多,最常见的就是冒泡排序,选择排序,插入排序,以及快速排序,一直都想好好整理整理排序算法,这两天稍微花了点时间,通过看前辈们的帖子,以及十几张草纸的努力,终于总结出了这个帖子.
(1)排序的定义:对一序列对象根据某个关键字进行排序(升序或降序)
(2)对排序算法优劣术语的说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大小。
(3)排序算法图片总结(图片来源于网络)
n 数据规模
k "桶"的个数
in-place 占用常数内存,不占用额外内存
Out-place 占用额外内存
(4)排序分类
1.冒泡排序(Bubble Sort)
冒泡排序可能是大多数人接触的第一个排序算法,对于学过C语言的人来说应该都是有一定了解的.
(1)算法描述
冒泡排序是一种简单的排序算法.重复的走访要排序的数组,一次比较两个数,如果它们顺序错误就把它们两两交换过来,每一轮都会确定一个最大数直到没有需要交换,排序完成.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function BubbleSort(arr) {
console.log("---------------------------------");
var temp="";
console.time("执行时间");
for(var i=0;i<arr.length;i++)
{
for(var j=0;j<arr.length-1;j++)
{
if(arr[j]>arr[j+1])
//如果两个数的顺序不满足升序排序的规则
{
//交换两个相邻的数
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
//输出每一轮的排序结果
var index=i+1;
console.log("冒泡第"+index+"轮结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("执行时间");
return arr;
}
console.log("冒泡排序的结果:"+BubbleSort(arr));
输出结果
改进冒泡排序:每一轮都会确定一个最大值,所以确定后的最大值就无需改动了,那么就可以设置一个标示性变量pos用于记录每轮排序中最后一次进行交换的位置,下一趟扫描只要扫描到pos的位置即可.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function BubbleSort2(arr) {
console.log("---------------------------------");
var temp="";
var i=arr.length-1;
console.time("执行时间");
while (i>0)
{
var pos=0;
for(var j=0;j<arr.length-1;j++)
{
if(arr[j]>arr[j+1])//如果两个数的顺序不满足升序排序的规则
{
pos=j;
//交换两个相邻的数
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
//输出每一轮的排序结果
console.log("冒泡结果:"+arr);
console.log("---------------------------------");
i=pos;
}
console.timeEnd("执行时间");
return arr;
}
console.log("冒泡排序的结果:"+BubbleSort2(arr));
输出结果
再次改进冒泡排序
传统冒泡排序中每一轮只能找到一个最大值或最小值,可以考虑通过双向冒泡一次找到两个最终值(最大值和最小值),从而减少一半的排序次数
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function BubbleSort3(arr) {
var low = 0;
var high= arr.length-1; //设置变量的初始值
var tmp,j;
console.time("改进后冒泡排序耗时");
while (low < high)
{
for (j= low; j< high; ++j) //正向冒泡,找到最大者
{
if (arr[j]> arr[j+1])
{
tmp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
}
--high; //修改high值, 前移一位
for (j=high; j>low; --j) //反向冒泡,找到最小者
{
if (arr[j]<arr[j-1])
{
tmp = arr[j];
arr[j]=arr[j-1];
arr[j-1]=tmp;
}
}
++low;//修改low值,后移一位
console.log("冒泡结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("改进后冒泡排序耗时");
return arr;
}
console.log("冒泡排序的结果:"+BubbleSort3(arr));
输出结果
(2)算法分析
- 最佳情况:T(n)=O(n)
当输入的数据已经是正序的时候(已经是正序,还需要排序吗?)
- 最差情况
当输入的数据是反序的时候(直接反序就是正序了,何必这么麻烦?)
- 平均情况
T(n)=O(n2)
2.选择排序(Selection Sort)
对于选择排序来说,数据规模越小越好,因为无论什么数据进去时间复杂度都是O(n2),它唯一的好处就是不占用额外的内存空间.选择排序也算是一般比较常用的排序算法.
(1)算法简介
选择排序的基本原理:首先在未排序序列中找到最小(大)元素,存放在排序序列的位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾,以此类推,知道所有元素均排序完毕.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function ChooseSort(arr) {
console.log("---------------------------------");
var temp="";
var minINdex; //定义存放最小值下标的变量
console.time("执行时间");
for(var i=0;i<arr.length;i++)
{
minINdex=i;//临时定义最小值下标为i
for(var j=i+1;j<arr.length;j++)
{
if(arr[minINdex]>arr[j])//如果存在某个数小于当前minINdex对应的数
{
minINdex=j;//记录下标到minINdex
}
}
if(minINdex!=i)//如果minINdex改变则交换数值
{
temp=arr[i];
arr[i]=arr[minINdex];
arr[minINdex]=temp;
}
//输出每一轮的排序结果
var index=i+1;
console.log("选择第"+index+"轮结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("执行时间");
return arr;
}
console.log("选择排序的结果:"+ChooseSort(arr));
输出结果
(2)算法分析
- 最佳情况: T(n)=O(n2)
- 最差情况: T(n)=O(n2)
- 平均情况: T(n)=O(n2)
3.入排序(Insertion Sort)
(1)算法简介
插入排序的算法是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到合适位置并插入,在从后到前的扫描过程中,需要反复把已排序的排序元素向后挪位,为最新元素提供插入空间.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function InsertSort(arr) {
console.log("---------------------------------");
var length=arr.length; //获得数组的长度
var preIndex,current; //定义临时变量,包括向前索引值和当前数
console.time("执行时间");
for(var i=1;i<length;i++)
{
preIndex=i-1;//前一个值的索引值
current=arr[i];//保存当前值用于插入赋值
while(preIndex>-1&&arr[preIndex]>current) //如果preIndex合法且存在比当前值大的数
{
arr[preIndex+1]=arr[preIndex];//将有序数组部分大于当前值的数的位置向后移动
preIndex--;//向前索引值变小
}
// 更新当前值
arr[preIndex+1]=current;//插入无序数组中取出的值,即当前值
console.log("插入第"+i+"轮结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("执行时间");
return arr;
}
console.log("插入排序的结果:"+InsertSort(arr));
输出结果
改进插入排序
使用二分查找的方式查找插入位置
js实现代码
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function InsertSort2(arr) {
console.log("---------------------------------");
console.time("执行时间");
for(var i=0;i<arr.length;i++)
{
var key=arr[i];
var left=0;
var right=arr.length-1;
while (left<=right)
{
var middle=parseInt((left+right)/2);
if(key<=arr[middle])
{
right=middle-1;
}
else
{
left=middle+1;
}
}
for (var j=i-1;j>=left;j--)
{
arr[j+1]=arr[j];
}
arr[left]=key;
var index=i+1;
console.log("改进插入第"+index+"轮结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("执行时间");
return arr;
}
console.log("插入排序的结果:"+InsertSort2(arr));
输出结果
(2)算法分析
- 最佳情况: 输入数组按升序排列. T(n)=O(n)
- 最差情况:输入数组按降序排列. T(n)=O(n2)
- 平均情况: T(n)=O(n2)
4.希尔排序(Shell Sort)
简单插入排序的改进版,它与插入排序的不同之处在于,它会优先距离比较远的元素,希尔排序又称缩小增量排序.
(1)算法简介
希尔排序的核心在于间隔序列的设定,既可以提前设好间隔序列,也可以动态定义间隔序列.
Js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function ShellSort(arr) {
var length=arr.length;//定义数组长度
var temp;//定义临时变量
var gap=1;//定义间隔
console.time("执行时间");
while (gap<length/3)//动态定义间隔序列
{
gap=gap*3+1;
}
for(gap;gap>0;gap=Math.floor(gap/3))
{
console.log("当前间隔值:"+gap);
for(var i=gap;i<length;i++)
{
temp=arr[i];//临时记录当前下标对应的数组的值
for(var j=i-gap;j>=0 && arr[j]>temp;j-=gap)//如果存在和temp下标相隔gap或gap的倍数的值,并且这个值大于gap
{
arr[j+gap]=arr[j];//把符合条件的值向后移动gap个位置
}
arr[j+gap]=temp;//把temp放入空出的位置上
console.log("希尔结果:"+arr);
console.log("---------------------------------");
}
}
console.timeEnd("执行时间");
return arr;
}
console.log("希尔排序的结果:"+ShellSort(arr));
输出结果
(2)算法分析
- 最佳情况: T(n)=O(log2 n)
- 最差情况: T(n)=O(log2 n)
- 平均情况:T(n)=O(nlog n)
5.归并排序(Merge Sort)
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的时间复杂度,代价是需要额外的内存空间.
(1)算法简介
归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法的一个典型的应用.归并排序是一种稳定的排序算法,将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列间有序.如果将两个有序列表合并成一个有序列表,称为2-路归并.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function mergeSort(arr) {
var length=arr.length;//获得要排序的数组的长度
if(length<2)//如果数组的长度小于2,就直接返回原来的数组,无需排序
{
return arr;
}
var middle=Math.floor(length/2);//获得分割子序列的中间数
console.log("当前分割值:"+middle);
var left=arr.slice(0,middle);//分割得到左序列
var right=arr.slice(middle);//分割得到右序列
return merge(mergeSort(left),mergeSort(right));//迭代
}
function merge(left,right) //迭代方法
{
var result=[];//定义存放临时排序变量
while(left.length>0&&right.length>0)//左序列和右序列的长度都大于零
{
if(left[0]<=right[0])//如果左序列的第一个值小于右序列的第一个值
{
result.push(left.shift());//把左序列的第一个值插入到result的后面
}
else {
result.push(right.shift());//把右序列的第一个值插入到result的后面
}
}
/*while(left.length)//如果左序列还有没有处理的值,就把这些值依次插入result的后面
{
result.push(left.shift());
}
while (right.length)//如果右序列还有没有处理的值,就把这些值依次插入到result
{
result.push(right.shift());
}
console.log("归并结果:"+result);
console.log("---------------------------------");
return result;*/
//注释部分和下面的三句等价
console.log("归并结果:"+result);
console.log("---------------------------------");
return result.concat(left).concat(right);
}
console.log("归并排序的结果:"+mergeSort(arr));
输出结果
(2)算法分析
- 最佳情况: T(n)=O(n)
- 最差情况: T(n)=O(nlog n)
平均情况: T(n)=O(nlog n)
6.快速排序(Quick Sort)
快排存在的意义就是快,效率高,是处理大数据最快的排序算法之一
(1)算法简介
快排的基本思想:通过一轮排序将待排记录分隔称独立的两部分,其中一部分记录的关键字均比另外一部分的关键字小,就可以分别对这两部分记录继续进行排序,以达到这个序列有序.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function QuickSort(arr)
{
var i = 0;//定义向后查找下标变量,用于寻找小于基准数的值
var j = arr.length - 1;//定义向前查找下标变量,用于寻找大于基准数的值
console.time("执行时间");
var Sort = function(i, j)//让基准值归位
{
if (i == j) // 结束条件
{
return;
}
var key = arr[i];//记录基准值
var stepi = i; // 记录开始位置
var stepj = j; // 记录结束位置
while (j > i)
{
// j <<-------------- 向前查找,寻找小于基准数的值,必须j先移动
if (arr[j]>=key)
{
j--;
}
else//找到大于基准数的值的情况
{
arr[i] = arr[j]//把找到的大于基准数的值放在当前i对应的位置
//i++ ------------>>向后查找,寻找大于基准数的值,在j找到大于基准数的时候再移动
while (j > ++i)
{
if (arr[i] > key)//如果找到大于基准数的值
{
arr[j] = arr[i];//把大于基准数的值放在当前j对应的位置
break;
}
}
}
//代码执行到这,就代表如果在循环结束前即找到了大于基准数的值,也找到了小于基准数的值,就已经成功交换了两个值的位置
}
// 如果第一个取出的 key 是最小的数
if (stepi == i)
{
Sort(++i, stepj);//从最小值的后面开始继续进行快速排序
return;
}
// 最后一个空位留给 key
//如果i=j,就把基准数归位到i和j对应的同一个位置
arr[i] = key;
//左序列递归调用快速排序方法
Sort(stepi, i);
//右序列递归调用快速排序方法
Sort(j, stepj);
console.log("快排结果:"+arr);
console.log("---------------------------------");
}
//调用排序方法
Sort(i, j);
console.timeEnd("执行时间");
return arr;
}
console.log("快速排序的结果:"+QuickSort(arr));
输出结果
快排的第二种实现方法
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function QuickSort2(arr) {
//如果数组长度小于1,直接返回数组本身
if(arr.length<=1)
{
return arr;
}
var pivotindex=Math.floor(arr.length/2);//定义基准下标
var pivot=arr.splice(pivotindex,1)[0];//获得基准值
var left=[];//左边序列
var right=[];//右边序列
for(var i=0;i<arr.length;i++)
{
if(arr[i]<pivot)//如果数组中存在大于基准值的数
{
left.push(arr[i]);//把这个数存入左序列的后面
}
else//数组中存在大于等于基准值的数
{
right.push(arr[i]);//把这个数存入右序列的后面
}
}
console.log("快排结果:"+arr);
console.log("---------------------------------");
//拼接左序列和基准值以及右边序列
return QuickSort2(left).concat([pivot],QuickSort2(right));
}
console.log("快速排序的结果:"+QuickSort2(arr));
(2)算法分析
- 最佳情况: T(n)=O(nlogn)
- 最差情况: T(n)=O(n2)
- 平均情况: T(n)=O(nlogn)
7.堆排序(Heap Sort)
(1)算法简介
堆排序是指利用堆这种数据结构所设计的一种排序算法,堆积是一种近似于二叉树的结构,并同时满足堆积的性质:即子节点的建值或索引总是小于(或大于)它的父节点.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function heapSort(arr) {
console.time("执行耗时:");
//建堆
var temp;//定义临时变量用于交换
var heapSize=arr.length;//获得要操作的数组的长度
//计算获得初始堆
for(var i=Math.floor(heapSize/2);i>=0;i--)
{
// console.log(i);
//调用检查是否符合堆的特点
heapify(arr,i,heapSize);
}
// console.log("初始堆:"+arr);
//堆排序
for (var j=heapSize-1;j>=1;j--)
{
//首先交换首元素和arr[j]的值
temp=arr[0];
arr[0]=arr[j];
arr[j]=temp;
//调用heapify方法调整当前的序列为符合堆特点的序列
heapify(arr,0,--heapSize);
console.log("堆排结果:"+arr);
console.log("---------------------------------");
}
console.timeEnd("执行耗时:");
return arr;
}
//检查是否符合堆的特点
function heapify(arr,x,len) {
var l = 2 * x + 1;//获得当前下标对应的数的左孩子
var r = 2 * x + 2;//获得当前下标对应的数的右孩子
var largest = x;//父节点和左孩子以及右孩子中最大值对应的下标
var temp;//定义临时变量用于交换最大值
if(l<len && arr[l]>arr[largest])//如果存在左孩子并且左孩子大于当前最大值
{
largest=l;//记录当前最大值的下标
}
if(r<len && arr[r]>arr[largest])//如果存在右孩子并且右孩子大于当前最大值
{
largest=r;//记录当前最大值的下标
}
if(largest!=x)//如果最大值的下标发生了变化
{
//交换最大值到父节点
temp=arr[x];
arr[x]=arr[largest];
arr[largest]=temp;
//递归调用heapify方法
heapify(arr,largest,len);
}
}
console.log("堆排序的结果:"+heapSort(arr));
输出结果
(2)算法分析
- 最佳情况: T(n)=O(nlogn)
- 最差情况: T(n)=O(nlogn)
- 平均情况: T(n)=O(nlogn)
8.计数排序(Counting Sort)
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中,作为一种线性的时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数.
(1)算法简介
计数排序是一种稳定的排序算法,使用一个额外的数组 C,其中第i个元素是待排序数组A中值等于i的元素的个数,然后根据数组C来把A中的元素放到正确的位置,它只能对证书进行排序.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function countingSort(arr) {
var len=arr.length;
B=[];//定义临时数组用于存储排序后的数组
C=[];//定义辅助数组,用于确定排序后的数组中每个值的位置
min=max=arr[0];//初始化最大值和最小值为数组的首项
console.time("执行时间:")
//构造C数组
for(var i=0;i<len;i++)
{
min=min<=arr[i]?min:arr[i];//求最小值
max=max>=arr[i]?max:arr[i];//求最大值
C[arr[i]]=C[arr[i]]?C[arr[i]]+1:1;//为C数组添加值,用于记录等于i的数的个数,辅助排序
//数组下标原数组中的数的值,下标对应的C中的值是等于i的数的个数
}
//调整C数组的值为累加形式,如果原数组不存在相应的值,该项就保持上一个状态值,如果存在,就加上它对应的C中的值
for(var j=min;j<max;j++)
{
C[j+1]=(C[j+1]||0)+(C[j]||0);
}
//通过C有目的的把原数组的数一次插入到合适的位置
for(var k=len-1;k>=0;k--)
{
B[C[arr[k]]-1]=arr[k];
C[arr[k]]--;//C动态更新数据
console.log("计数排序结果:"+B);
console.log("---------------------------------");
}
console.timeEnd("执行时间:");
return B;
}
console.log("计数排序的结果:"+countingSort(arr));
输出结果
(2)算法分析
计数排序不是比较排序,当输入的元素是n个0到k之间的整数,它的运行时间是O(n+k),数组C的大小取决于数组中数据的范围,数组范围越大,需要的时间和内存就越大.
- 最佳情况: T(n)=O(n+k)
- 最差情况: T(n)=O(n+k)
平均情况: T(n)=O(n+k)
9.桶排序(Bucket Sort)
桶排序是计数排序的升级版,利用函数的映射关系,高效与否的关键在于这个映射函数的确定.
(1)算法简介
桶排序的原理:假设输入的数据服从均匀分布,将数据分到有限数量的桶里,每个桶分别排序(有可能再使用其他的排序算法或是以递归方式继续使用桶排序进行排序)
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function bucketSort(arr,num) {
if(arr.length<=-1)
{
return arr;
}
var len=arr.length;
var buckets=[];
var result=[];
var min,max;
min=max=arr[0];
var regex ='/^[1-9]+[0-9]*$/';
var space;
var n=0;
num=num||((num>1&®ex.test(num))?num:10);
console.time("执行耗时:");
for(var i=0;i<len;i++)
{
min=min<=arr[i]?min:arr[i];
max=max>=arr[i]?max:arr[i];
}
space=(max-min+1)/num;
for(var j=0;j<len;j++)
{
var index=Math.floor((arr[j]-min)/space);
if(buckets[index])//非空桶,插入排序
{
var k=buckets[index].length-1;
while(k>=0&&buckets[index][k]>arr[j])
{
buckets[index][k+1]=buckets[index][k];
k--;
}
buckets[index][k+1]=arr[j];
}
else//空桶,初始化
{
buckets[index]=[];
buckets[index].push(arr[j]);
}
}
while(n<num)
{
result=result.concat(buckets[n]);
n++;
console.log("桶排序结果:"+result);
console.log("---------------------------------");
}
console.timeEnd("执行耗时:");
return result;
}
console.log("桶排序的结果:"+bucketSort(arr,2));
输出结果
(2)算法分析
桶排序最好情况下使用线性时间O(n),桶排序时间复杂度取决于每个桶之间数据进行排序的时间复杂度,因为它的时间复杂度都为O(n),桶划分的越小,桶之间的数据越小,排序所用的时间也会越少,但相应的空间消耗就会越大.
- 最佳情况: T(n)=O(n+k)
- 最差情况: T(n)=O(n+k)
平均情况: T(n)=O(n2)
10.基数排序(Radix Sort)
基数排序也是非比较类排序算法,对每一位进行排序,从最低位进行排序,复杂度为O(kn),n为数组长度,k为数组中的数的最大的位数.
(1)算法简介
基数排序是按照低位优先排序,然后收集,再按照高位排序,然后收集,以此类推,直到最高位.
js代码实现
var arr=[6,8,1,3,9,2,90,7,18];
console.log("原始数组的显示:"+arr);
function RadixSort(arr,maxDigit) {
var mod = 10;
var dev = 1;
var counter = [];
console.time('基数排序耗时');
for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for(var j = 0; j < arr.length; j++) {
var bucket = parseInt((arr[j] % mod) / dev);
if(counter[bucket]== null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
var pos = 0;
for(var j = 0; j < counter.length; j++) {
var value = null;
if(counter[j]!=null) {
while ((value = counter[j].shift()) != null)
{
arr[pos++] = value;
console.log("基数排序结果:"+arr);
console.log("---------------------------------");
}
}
}
}
console.timeEnd('基数排序耗时');
return arr;
}
console.log("基数排序的结果:"+RadixSort(arr,2));
输出结果
后记
js总结常用的排序算法大概就总结这么多吧,也看了很多前辈们的总结,发现前辈们很厉害,要把这写完全搞懂还是得花很多心思的,可能有些地方总结的不全面或者不专业,欢迎大家在评论里提出问题,可以共同学习.