排序,我想大家一定经历过或者正在经历着。或许你不懂算法,对排序算法一无所知,但是你一定用过一些第三方库的api来一键排序,那么,在你享受便捷的同时,你是否想过它的底层是如何实现的?这样的算法实现方式是不是最好的?还有没有其它的可能性来实现更快速的排序?那么,希望这一篇文章过后。对于排序算法,你不会再觉得陌生和迷惑。

  这篇文章会介绍一些简单常用的排序算法,比如我们耳熟能详的冒泡排序,以及选择排序、插入排序、归并排序等等等等。当然,你一旦学会了这些算法在js中的实现方式,其实你也就弄懂了这种算法。就算以后要用其它语言来实现这些算法,也不过就是一些语言特性上的差别罢了。

  我们会专门写一个数组类,并在其中加入各种排序算法。那么,我们先开始搭一个简单的架子。

function ArrayList() {
var array = []; this.insert = function (item) {
array.push(item);
}; this.toString = function() {
return array.join();
};
}

  我们构建一个数组类,并且只有一个insert和toString方法,以便我们输入数组元素和打印数组元素。

  下面我们为这个数组类添加各种排序方法。我们先从最简单的开始。

1、冒泡排序

  冒泡排序十分简单,就是比较数组中任何两个相邻的元素,如果第一个比第二个大,那么就交换两个元素的位置。这样,较大值得元素就会一点一点移动到正确的位置,就像气泡升至表面一样。我们先来看一下代码。

//交换数组中两个相邻元素的方法,传入的是相关的数组,以及相邻两个元素的下标。
var swap = function (array,index1,index2) {
// 这里,是最“普通”的方式,通过一个中间量来存储index1元素,因为要把index1的值设置为index2,然后再把index2的值设置为刚才存储index1的aux变量。
// 希望我上面说的足够清楚。
var aux = array[index1];
array[index1] = array[index2];
array[index2] = aux;
// 但是我们可以有更为简便的方式来做到替换两个数组元素位置的方式。
// 一个是下面的ES6新增的方法。数组的解构赋值。不多说,大家可以自行去查看。
//[array[index1],array[index2]] = [array[index2],array[index1]];
// 另外一个方法是利用数组的splice方法,删除从index1开始的两个元素,并且在删除的位置插入index2,和index1以达到替换元素的目的。
  // 如果你对数组方法还不是很清楚,请看这里用js来实现那些数据结构02(数组篇02-数组方法)
  //array.splice(index1,2,array[index2],array[index1]);
};
  // 这是最简单,当然也是最慢的排序方法,我们需要两层循环来判断值得大小,以此来一步一步确定每一个值得位置。
  // 这里我要刨根问底一下,为什么外层循环(i)循环的是数组元素的长度,而内层(j)是比数组长度少1的循环次数?
  // 因为这是可以保证每两个元素都进行过比较的最小的循环次数,不信你把两次循环的次数增加(length +100000);来试一下,发现结果仍旧是我们想要的。(当然,这更耗费时间。)
  // 但是你把次数减得更少就不行了,排序的结果就不对了(其实这里可以合理的减少内层循环的次数,后面说)。你还可以这么理解,外层循环控制我们有多少个数需要比较,内层循环去具体的操作两个数的比较。
this.bubbleSort = function () {
var length = array.length;
for(var i = 0; i < length; i++) { //i=0,1,2,3,4...length-1;
//console.log(i,"i");
for(var j = 0; j < length-1;j++) {//j = 0,1,2,3,4...length - 1 - 1;
//console.log(j,"j");
if(array[j] > array[j+1]){
swap(array,j,j+1)
}
}
}
}; //我们来写一个简单的方法生成一个未排序的arraylist。//这个方法是在构造函数外的。你不用也可以。只是为了方便生成一个数组罢了。
function creatArrayList (size) {
var array = new ArrayList();
for(var i = size; i > 0; i--) {
array.insert(i);
} return array;
} var arraylist = creatArrayList(5);
console.log(arraylist.toString());//5,4,3,2,1
arraylist.bubbleSort();
console.log(arraylist.toString());//1,2,3,4,5

  上面的解释我相信已经够详细了,我们下面接着看看是否可以改进一下这垃圾的耗时间的效率低的冒泡排序,让我们在简单实现的基础上提高一点点性能?

   //咱们看看modifiedBubbleSort和bubbleSort的区别,唯一不同的地方就在于内层循环的时候在for循环的第二个条件中多减了一个i。这么做的用意是什么呢?
// 我们一点一点来捋一下这点点代码。假设我们的数组是【5,4,3,2,1】;当i = 0的时候(第一次外循环),我们拿5去依次和4,3,2,1来比较,最后数组渐变成了【4,3,2,1,5】;
// 那么此时,5就是最大的,当i=1的时候(第二次外循环)。此时我们的内循环就不再需要去拿当前的j去与5(也就是数组中确定了的最大的)比较。
// 外层每循环一次,内层中的每个元素就相互交换了一下位置(如果符合条件的话),最终每一次内层循环完毕,都会确定一个当前轮数最大的值。
// 那么既然我们已经知道最大的值是什么,就无需在后面的循环轮数中再去和已经确定了位置的值去做比较了。这样就可以提高一点我们的执行效率。
// 这里再多句嘴,当i = 0时,j = 0,j < length - 1 - 0;当i = 1时,j = 0,j < length - 1 - 1;(要理解这句话)
this.modifiedBubbleSort = function () {
var length = array.length;
for(var i = 0; i < length; i++) {
for(var j = 0; j < length - 1 - i;j++) {
if(array[j] > array[j+1]) {
swap(array,j,j+1);
}
}
}
};

  改进后的冒泡排序我们也学会了,但是也就只能这样了。没办法再进一步的进行优化和效率的提升。冒泡排序,是最基础的,最不推荐的排序方式。因为它的时间复杂度是O(n2),大O表示法,我们会在后面的内容中详细的讲解什么是大O表示法。这里可以暂时的理解为,两层循环形成了i*j的计算结果,也就是length*length的循环总次数。也就是n2.。(事实上就是这么回事)。如果是三层循环,那很有可能就是O(n3)的复杂度了。

2、选择排序

  选择排序的思想是,找到数据结构中最小值并将其放在第一位,接着找到第二小的值,放到第二位,以此类推。(当然你也可以找最大的值放在最后一位)。我们还是来看代码。

//其实选择排序也并不复杂,我们来一起看一看。
this.selectionSort = function () {
//首先,我们声明一个存储数组长度的变量,以及一个存储当前最小值的变量,哦不对,存储当前最小值的对应下标的变量。
var length = array.length,indexMin;
// 在外层循环,我们循环次数是整个数组的长度。
for(var i = 0; i < length - 1; i++) {
// 这里,最开始的循环我们也不知道谁是最小的,所以我们就把第一个值得下标作为最小值得下标。
indexMin = i;
// 内层循环,我们会依次比较当前最小值(也就是indexMin对应的值)和数组中的其它值。
// 这里,为什么是j=i呢?
// 我们每一次外层循环,都会确定一个最小值并把最小值放置在相应的位置(从下标0开始每次外循环都会往后加1)。
// 那么我们就不需要再去比较开始循环过比较过的下标了,所以我们每次外层循环过后,内层循环都从i的位置开始就可以了。
// 有点类似于modifiedBubbleSort的j<length - 1 - i;
for(var j = i; j < length; j++) {
// 这样,我们就可以判断出最小值是什么,如果indexMin所对应的值比j所对应的值还要大,说明最小值对应的下标应该为j。
if(array[indexMin] > array[j]) {
indexMin = j;
}
}
// 在外层循环一次结束后,如果i(最开始我们确定的最小值)不等于indexMin。这说明i并不是最小值。我们就交换两个值的位置。
// 如果相等,说明当前的indexMin就是最小值,无需交换位置。
console.log(i,indexMin)
if(i !== indexMin) {
swap(array,i,indexMin)
}
}
};

  不过选择排序的复杂度也是O(n2),效率并不是很好。那么我们继续往下看。

3、插入排序

  插入排序,怎么说呢....就是假设数组中的第一个元素是已经排序过的了(不假设不行,或者说它就是排过序的了,因为就一个元素嘛),那么我们和第二个元素比较,第二个元素是应该在第一个元素之前,还是在原位置不动呢?也就是说,第一个元素和第二个元素比较大小来确定这两个元素的位置。那么这样,单纯就数组元素的前两项来说,他们是排好序的了。那么我们再以第三个元素跟前两个元素进行比较,来确定第三个元素应该插入在前两项元素的什么位置。

  简单来说,我们可以认为在排序数组中有一个已排序的子数组,我们依次用后面的元素与子数组中的元素进行比较,以确定后面的元素应该插入到子数组的什么位置。最后,我们就会得到一个完全排序的数组了。

  我们继续来看代码。

this.insertionSort = function () {
//j和temp分别用来存储当前的下标和当前下标所对应的值。
var length = array.length,j,temp;
// 为什么i要从1开始呢?因为index为0的元素我们视为已经排序过的了。 for(var i = 1; i < length; i++) {
// 我们用j来存储当前要比较的值的下标也就是i,因为0上的元素已经排序过了(我们默认这样做的)。
j = i;
// 同样,我们要比较当前索引的值得大小来确定是否需要换位,所以我们还要有一个临时存储当前下标所对应的值的变量。
temp = array[i];
// 如果j>0说明是数组中的元素,
// 并且,如果当前j(i)的前一个元素(j-1)比当前的变量大,那么就把j(i)的值设置为j-1的值,也就是把j(i)的位置往后挪了一个。
// 直到array[j - 1] > temp为false为止。为什么不说j>0这个条件呢?因为这是保证数组正确对比的一个防护层,当然,它是很重要的。
// 这里有一个十分必要且需要注意的point,就是我们的变量j的值的问题。
// 我们在循环直到条件不成立跳出循环的时候,此时的j就是需要把临时存储array[i]的值(也就是temp)插入的地方。
// 因为,我们在while循环中,每次循环都会用j--来使下标的位置一点点往前移动,直到条件不成立后,我们得到了一个应该插入temp的位置的j。
while (j > 0 && array[j - 1] > temp) {
array[j] = array[j - 1];
j--;
}
array[j] = temp;
}
};

  上面的代码就是插入排序了,其中要注意的点就在while循环这一块,需要花费一点心思去理解一下。我在简单的啰嗦两句,其实在代码中,我们声明了j和temp变量用来存储当前的下标和其所对应的值。那么temp的作用是我们可以在找到该插入的位置的时候,可以知道应该插入的值是什么,而j的存在的意义是确定这个位置是哪里。所以,我们在while循环中会拿递减的j所对应的值的前一个去和temp比较,如果条件成立,那么我就往后挪,直到挪不动为止(while循环的条件不匹配),我们就找到了应该插入temp位置的j。这时候在该位置上插入temp就可以了。

  

  那么,简单的排序方法就介绍到这里了,下一章我们来看看复杂一点的,但是效率更高的排序算法。

  其实我在写这篇文章的时候,一直在纠结要不要去画画图,让大家可以更容易的去理解这些代码和这些排序算法的实现方式,但是,我在网上搜了一下。一大堆!!所以,我就觉得算了吧。不过文中我已经附上了相关的链接地址,其中有对该算法的概念的更为详细的解释。

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!

js算法初窥01(排序算法01-冒泡、选择、插入)的更多相关文章

  1. js算法初窥05(算法模式02-动态规划与贪心算法)

    在前面的文章中(js算法初窥02(排序算法02-归并.快速以及堆排)我们学习了如何用分治法来实现归并排序,那么动态规划跟分治法有点类似,但是分治法是把问题分解成互相独立的子问题,最后组合它们的结果,而 ...

  2. js算法初窥07(算法复杂度)

    算法复杂度是我们来衡量一个算法执行效率的一个度量标准,算法复杂度通常主要有时间复杂度和空间复杂度两种.时间复杂度就是指算法代码在运行最终得到我们想要的结果时所消耗的时间,而空间复杂度则是指算法中用来存 ...

  3. js算法初窥04(算法模式01-递归)

    终于来到了有点意思的地方--递归,在我最开始学习js的时候,基础课程的内容就包括递归,但是当时并不知道递归的真正意义和用处.我只是知道,哦...递归是自身调用自身,递归要记得有一个停止调用的条件.那时 ...

  4. js算法初窥06(算法模式03-函数式编程)

    在解释什么是函数式编程之前,我们先要说下什么是命令式编程,它们都属于编程范式的一种.命令式编程其实就是一块一块的代码,其中包括了我们要执行的逻辑或者判断或者一些运算.也就是按部就班的一步一步完成我们所 ...

  5. 链表插入和删除,判断链表是否为空,求链表长度算法的,链表排序算法演示——C语言描述

    关于数据结构等的学习,以及学习算法的感想感悟,听了郝斌老师的数据结构课程,其中他也提到了学习数据结构的或者算法的一些个人见解,我觉的很好,对我的帮助也是很大,算法本就是令人头疼的问题,因为自己并没有学 ...

  6. python 数据结构与算法之排序(冒泡,选择,插入)

    目录 数据结构与算法之排序(冒泡,选择,插入) 为什么学习数据结构与算法: 数据结构与算法: 算法: 数据结构 冒泡排序法 选择排序法 插入排序法 数据结构与算法之排序(冒泡,选择,插入) 为什么学习 ...

  7. 数据结构和算法(Golang实现)(25)排序算法-快速排序

    快速排序 快速排序是一种分治策略的排序算法,是由英国计算机科学家Tony Hoare发明的, 该算法被发布在1961年的Communications of the ACM 国际计算机学会月刊. 注:A ...

  8. 数据结构和算法(Golang实现)(18)排序算法-前言

    排序算法 人类的发展中,我们学会了计数,比如知道小明今天打猎的兔子的数量是多少.另外一方面,我们也需要判断,今天哪个人打猎打得多,我们需要比较. 所以,排序这个很自然的需求就出来了.比如小明打了5只兔 ...

  9. 数据结构和算法(Golang实现)(19)排序算法-冒泡排序

    冒泡排序 冒泡排序是大多数人学的第一种排序算法,在面试中,也是问的最多的一种,有时候还要求手写排序代码,因为比较简单. 冒泡排序属于交换类的排序算法. 一.算法介绍 现在有一堆乱序的数,比如:5 9 ...

  10. 数据结构和算法(Golang实现)(20)排序算法-选择排序

    选择排序 选择排序,一般我们指的是简单选择排序,也可以叫直接选择排序,它不像冒泡排序一样相邻地交换元素,而是通过选择最小的元素,每轮迭代只需交换一次.虽然交换次数比冒泡少很多,但效率和冒泡排序一样的糟 ...

随机推荐

  1. Warning: $HADOOP_HOME is deprecated解决方法

    启动Hadoop时报了一个警告信息,我的系统是CentOS,安装的Hadoop版本是hadoop1.2.1,具体警告信息如下: [root@m1 hadoop-1.2.1]# bin/start-al ...

  2. Leetcode_128_Longest Consecutive Sequence

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43854597 Given an unsorted arra ...

  3. Linux下xargs命令详解

    http://www.cnblogs.com/perfy/archive/2012/07/24/2606101.html xargs是给命令传递参数的一个过滤器,也是组合多个命令的一个工具.它把一个数 ...

  4. VCC、 VDD、VEE、VSS 电压理解

    VCC. VDD.VEE.VSS 版本一: 简单说来,可以这样理解: 一.解释 VCC:C=circuit 表示电路的意思, 即接入电路的电压: VDD:D=device 表示器件的意思, 即器件内部 ...

  5. LeetCode之“链表”:Reverse Linked List && Reverse Linked List II

    1. Reverse Linked List 题目链接 题目要求: Reverse a singly linked list. Hint: A linked list can be reversed ...

  6. Struts2技术内幕 读书笔记一 框架的本质

    本读书笔记系列,主要针对陆舟所著<<Struts2技术内幕 深入解析Strtus2架构设计与实现原理>>一书.笔记中所用的图片若无特殊说明,就都取自书中,特此声明. 什么是框架 ...

  7. Linux - test测试标志的意思总结

    测试的标志 代表意义 1. 关於某个档名的『文件类型』判断,如 test -e filename 表示存在否 -e 该『档名』是否存在?(常用) -f 该『档名』是否存在且为文件(file)?(常用) ...

  8. C语言可变参实现参数累加返回

    C语言可变参的作用真的是非常大,自从发表了可变参如何实现printf,fprintf,sprintf的文章以来,便有不少博友私信问我实现的机制,我也解释了相关的知识点.今天,我们借着这个机会,再来举一 ...

  9. SharePoint 调查添加图片支持

    前言:今天,碰到一个有趣的问题,就是SharePoint调查里面,添加对于图片的支持,众所周知,SharePoint的调查就支持那么几种字段类型的问题,当然,我们可以开发实现,不过,这个不是我们今天介 ...

  10. linux下利用ruby做系统备份与还原

    啥都不说了,都在代码里 :) #!/usr/bin/ruby BAK_PATH = "/media/backup.tar.xz" def to_backup exclude_fil ...