前端排序方法

做编程,排序是个必然的需求。前端也不例外,虽然不多,但是你肯定会遇到。

不过说到排序,最容易想到的就是冒泡排序,选择排序,插入排序了。

冒泡排序

依次比较相邻的两个元素,如果后一个小于前一个,则交换,这样从头到尾一次,就将最大的放到了末尾。

从头到尾再来一次,由于每进行一轮,最后的都已经是最大的了,因此后一轮需要比较次数可以比上一次少一个。虽然你还是可以让他从头到尾来比较,但是后面的比较是没有意义的无用功,为了效率,你应该对代码进行优化。

图片演示如下:

代码实现:

function bubbleSort(arr) {

var len = arr.length;

for (var i = 0; i < len - 1; i++) {

for (var j = 0; j < len - 1 - i; j++) {

if (arr[j] > arr[j+1]) { // 相邻元素两两对比

var temp = arr[j+1]; // 元素交换

arr[j+1] = arr[j];

arr[j] = temp;

}

}

}

return arr;

}

选择排序

选择排序我觉得是最简单的了,大一学VB的时候,就只记住了这个排序方法,原理非常简单:每次都找一个最大或者最小的排在开始即可。

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

重复第二步,直到所有元素均排序完毕。

动图演示:

代码演示:

function selectionSort(arr) {

var len = arr.length;

var minIndex, temp;

for (var i = 0; i < len - 1; i++) {

minIndex = i;

for (var j = i + 1; j < len; j++) {

if (arr[j] < arr[minIndex]) {  // 寻找最小的数

minIndex = j;  // 将最小数的索引保存

}

}

temp = arr[i];

arr[i] = arr[minIndex];

arr[minIndex] = temp;

}

return arr;

}

插入排序

插入排序也比较简单。就像打扑克一样,依次将拿到的元素插入到正确的位置即可。

1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

动图演示:

代码示例:

function insertionSort(arr) {

var len = arr.length;

var preIndex, current;

for (var i = 1; i < len; i++) {

preIndex = i - 1;

current = arr[i];

while(preIndex >= 0 && arr[preIndex] > current) {

arr[preIndex+1] = arr[preIndex];

preIndex--;

}

arr[preIndex+1] = current;

}

return arr;

}

简单的代价是低效

上面三种都是非常简单的排序方法,简单的同时呢,效率也会比较低,还是拿这本书里的对比图来说明:

时间复杂度都高达O(n^2),而它们后面的一些排序算法时间复杂度基本都只有O(n log n)。

我的强迫症又犯了,我想要高效率一点的排序方法。

归并排序

简单把这本书的内容过了一遍,当时就理解了这个归并排序,因此这里就谈一下这个归并排序吧。

基本原理是分治法,就是分开并且递归来排序。

步骤如下:

1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;

3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

4.重复步骤 3 直到某一指针达到序列尾;

5.将另一序列剩下的所有元素直接复制到合并序列尾。

动图演示:

代码示例:

function mergeSort(arr) { // 采用自上而下的递归方法

var len = arr.length;

if(len < 2) {

return arr;

}

var middle = Math.floor(len / 2),

left = arr.slice(0, middle),

right = arr.slice(middle);

return merge(mergeSort(left), mergeSort(right));

}


function merge(left, right)

{

var result = [];


while (left.length && right.length) {

if (left[0] <= right[0]) {

result.push(left.shift());

} else {

result.push(right.shift());

}

}


while (left.length)

result.push(left.shift());


while (right.length)

result.push(right.shift());


return result;

}

既然是个爱折腾的人,折腾了总得看看效果吧。

效率测试

由于我学这个来进行排序不是对简单数组,数组内都是对象,要对对象的某个属性进行排序,还要考虑升降序。

因此我的代码实现如下:

/**

* [归并排序]

* @param  {[Array]} arr   [要排序的数组]

* @param  {[String]} prop  [排序字段,用于数组成员是对象时,按照其某个属性进行排序,简单数组直接排序忽略此参数]

* @param  {[String]} order [排序方式 省略或asc为升序 否则降序]

* @return {[Array]}       [排序后数组,新数组,并非在原数组上的修改]

*/

var mergeSort = (function() {

// 合并

var _merge = function(left, right, prop) {

var result = [];


// 对数组内成员的某个属性排序

if (prop) {

while (left.length && right.length) {

if (left[0][prop] <= right[0][prop]) {

result.push(left.shift());

} else {

result.push(right.shift());

}

}

} else {

// 数组成员直接排序

while (left.length && right.length) {

if (left[0] <= right[0]) {

result.push(left.shift());

} else {

result.push(right.shift());

}

}

}


while (left.length)

result.push(left.shift());


while (right.length)

result.push(right.shift());


return result;

};


var _mergeSort = function(arr, prop) { // 采用自上而下的递归方法

var len = arr.length;

if (len < 2) {

return arr;

}

var middle = Math.floor(len / 2),

left = arr.slice(0, middle),

right = arr.slice(middle);

return _merge(_mergeSort(left, prop), _mergeSort(right, prop), prop);

};


return function(arr, prop, order) {

var result = _mergeSort(arr, prop);

if (!order || order.toLowerCase() === 'asc') {

// 升序

return result;

} else {

// 降序

var _ = [];

result.forEach(function(item) {

_.unshift(item);

});

return _;

}

};

})();

需要对哪个属性进行排序是不确定,可以随意指定,因此写成了参数。有由于不想让这些东西在每次循环都进行判断,因此代码有点冗余。

关于降序的问题,也没有加入参数中,而是简单的升序后再逆序输出。原因是不想让每次循环递归里都去判断条件,所以简单处理了。

下面就是见证效率的时候了,一段数据模拟:

var getData = function() {

return Mock.mock({

"list|1000": [{

name: '@cname',

age: '@integer(0,500)'

}]

}).list;

};

上面使用Mock进行了模拟数据,关于Mock : http://mockjs.com/

实际测试来啦:

// 效率测试

var arr = getData();


console.time('归并排序');

mergeSort(arr, 'age');

console.timeEnd('归并排序');


console.time('冒泡排序');

for (var i = 0, l = arr.length; i < l - 1; ++i) {

var temp;

for (var j = 0; j < l - i - 1; ++j) {

if (arr[j].age > arr[j + 1].age) {

temp = arr[j + 1];

arr[j + 1] = arr[j];

arr[j] = temp;

}

}

}

console.timeEnd('冒泡排序');

进行了五次,效果如下:

// 归并排序: 6.592ms

// 冒泡排序: 25.959ms


// 归并排序: 1.334ms

// 冒泡排序: 20.078ms


// 归并排序: 1.085ms

// 冒泡排序: 16.420ms


// 归并排序: 1.200ms

// 冒泡排序: 16.574ms


// 归并排序: 2.593ms

// 冒泡排序: 12.653ms

最低4倍,最高近16倍的效率之差还是比较满意的。

虽然1000条数据让前端排序的可能性不大,但是几十上百条的情况还是有的。另外由于node,JavaScript也能运行的服务端了,这个效率的提升也还是有用武之地的。

一点疑问

归并排序里面使用了递归,在《数据结构与算法 JavaScript 描述》中,作者给出了自下而上的迭代方法。但是对于递归法,作者却认为:

However, it is not possible to do so in JavaScript, as the recursion goes too deep for the language to handle. 

然而,在 JavaScript 中这种方式不太可行,因为这个算法的递归深度对它来讲太深了。

gitbook上这本书的作者对此有疑问,我也有疑问。

归并中虽然用了递归,但是他是放在return后的呀。关于在renturn后的递归是有尾递归优化的呀。

关于尾递归优化是指:本来外层函数内部再调用一个函数的话,由于外层函数需要等待内层函数返回后才能返回结果,进入内层函数后,外层函数的信息,内存中是必须记住的,也就是调用堆栈。而内部函数放在return关键字后,就表示外层函数到此也就结束了,进入内层函数后,没有必要再记住外层函数内的所有信息。

上面是我的理解的描述,不知道算不算准确。chrome下已经可以开启尾递归优化的功能了,我觉得这个递归是不该影响他在JavaScript下的使用的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 某次二面时,面试官问起Js排序问题,吾绞尽脑汁回答了几种,深感算法有很大的问题,所以总计一下! 排序算法说明 (1...
    流浪的先知阅读 1,187评论 0 4
  • 参考:十大经典排序算法 0、排序算法说明 0.1排序的定义 对一序列对象根据某个关键字进行排序。 0.2 术语说明...
    谁在烽烟彼岸阅读 1,006评论 0 12
  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an 输...
    code武阅读 650评论 0 0
  • 又到秋冬上新季,怎么可以少了包包? 之前因为自己打算手工制作一款皮包,没在这方面少做功课。最近,小玹兔手工制作的皮...
    小玹兔阅读 1,352评论 1 20
  • 胖胖说:这里是我的梦境,我要对班里欺负过我的人大开杀戒,与你无关,你先出去一下。我点头表示了解。 这是我第三次跑到...
    妄想先生阅读 297评论 0 2