• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
阳光Angel
博客园    首页    新随笔    联系   管理    订阅  订阅
快速排序
阮一峰的网络日志 » 首页 » 档案
  • 上一篇:MP3是如何颠覆音乐行
  • 下一篇:云储存时代的到来   

分类:

 
  • JavaScript

快速排序(Quicksort)的Javascript实现

作者: 阮一峰

日本程序员norahiko,写了一个排序算法的动画演示,非常有趣。

这个周末,我就用它当做教材,好好学习了一下各种排序算法。

排序算法(Sorting algorithm)是计算机科学最古老、最基本的课题之一。要想成为合格的程序员,就必须理解和掌握各种排序算法。

目前,最常见的排序算法大概有七八种,其中"快速排序"(Quicksort)使用得最广泛,速度也较快。它是图灵奖得主C. A. R. Hoare(1934--)于1960时提出来的。

"快速排序"的思想很简单,整个排序过程只需要三步:

  (1)在数据集之中,选择一个元素作为"基准"(pivot)。

  (2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。

  (3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

举例来说,现在有一个数据集{85, 24, 63, 45, 17, 31, 96, 50},怎么对其排序呢?

第一步,选择中间的元素45作为"基准"。(基准值可以任意选择,但是选择中间的值比较容易理解。)

第二步,按照顺序,将每个元素与"基准"进行比较,形成两个子集,一个"小于45",另一个"大于等于45"。

第三步,对两个子集不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

下面参照网上的资料(这里和这里),用Javascript语言实现上面的算法。

首先,定义一个quickSort函数,它的参数是一个数组。

var quickSort = function(arr) {

};

然后,检查数组的元素个数,如果小于等于1,就返回。

var quickSort = function(arr) {

  if (arr.length <= 1) { return arr; }

};

接着,选择"基准"(pivot),并将其与原数组分离,再定义两个空数组,用来存放一左一右的两个子集。

var quickSort = function(arr) {

  if (arr.length <= 1) { return arr; }

  var pivotIndex = Math.floor(arr.length / 2) ;

  var pivot = arr.splice(pivotIndex, 1)[0];

  var left = [];

  var right = [];

};

然后,开始遍历数组,小于"基准"的元素放入左边的子集,大于基准的元素放入右边的子集。

var quickSort = function(arr) {

  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]);

    }

  }

};

最后,使用递归不断重复这个过程,就可以得到排序后的数组。

var quickSort = function(arr) {

  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]);

    }

  }

  return quickSort(left).concat([pivot], quickSort(right));

};

使用的时候,直接调用quickSort()就行了。

(完)

文档信息

  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 发表日期: 2011年4月 4日
  • 更多内容:  档案 »  JavaScript
  • 付费支持: 购买文集
  • 社交媒体: twitter, weibo
  • Feed订阅: 

相关文章

  • 2014.04.30: 《ECMAScript 6入门》上线了
    过去的一个月,我写了一本书《ECMAScript 6入门》,今天上线了。
  • 2014.03.28: undefined与null的区别
    大多数计算机语言,有且仅有一个表示"无"的值,比如,C语言的NULL,Java语言的null,Python语言的None,Ruby语言的nil。
  • 2013.10.21: 什么是 Event Loop?
    Event Loop 是一个很重要的概念,指的是计算机系统的一种运行机制。
  • 2013.09.02: JavaScript与有限状态机
    有限状态机(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物。

广告(购买广告位)

留言(38条)

Fleeting Years 说:

好东西!谢谢!

2011年4月 4日 21:51 | 档案 | 引用

BlueDream 说:

通俗易懂

2011年4月 4日 22:16 | 档案 | 引用

wordgold 说:

取数组中间值的时候为什么不直接取而要Splice,如果是为了后续循环好像没必要

2011年4月 4日 22:19 | 档案 | 引用

limon 说:

why I love python:

q_sort= lambda l: l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x<=l[0]])

2011年4月 4日 22:20 | 档案 | 引用

chenge 说:

相当好,感谢分享!

2011年4月 5日 07:45 | 档案 | 引用

reader 说:

In Haskell

quicksort :: (Ord a) => [a] -> [a] 
quicksort [] = [] 
quicksort (x:xs) = 
  let smallerSorted = quicksort [a | a <- xs, a <= x]
    biggerSorted = quicksort [a | a <- xs, a > x] 
  in smallerSorted ++ [x] ++ biggerSorted

见 Learn You a Haskell for Great Good! 一书

2011年4月 5日 08:31 | 档案 | 引用

AnLuoRidge 说:

引用reader的发言:

In Haskell

见 Learn You a Haskell for Great Good! 一书

函数式编程的确够简洁

2011年4月 5日 15:58 | 档案 | 引用

tumutanzi 说:

这个太专业了吧,看不懂~~不过阮兄还是辛苦了。

2011年4月 5日 20:27 | 档案 | 引用

Richard Lee 说:

大于"基准"的元素放入左边的子集,小于基准的元素放入右边的子集。

反了,应该是:

小于"基准"的元素放入左边的子集,大于基准的元素放入右边的子集。

2011年4月 6日 01:58 | 档案 | 引用

wells.tang 说:

引用wordgold的发言:

取数组中间值的时候为什么不直接取而要Splice,如果是为了后续循环好像没必要

有道理。

2011年4月 6日 11:11 | 档案 | 引用

阮一峰 说:

@Richard Lee:谢谢提醒,确实写错了,已更正。

@wordgold:想了一下,你说得对,没必要splice,只要在遍历数组的时候,只要跳过“基准”元素就行了,这样代码也更易读。谢谢。

2011年4月 6日 11:20 | 档案 | 引用

wiki 说:

赞,快速排序是递归的经典应用

2011年4月 6日 14:25 | 档案 | 引用

Ted 说:

In Erlang:

  qsort([]) ->> [];
  qsort([Pivot|T])->
    qsort([X || X <- T, X < Pivot])
    ++[Pivot]++
    qsort([X || X <- T, X > Pivot]).

2011年4月 6日 17:28 | 档案 | 引用

gg 说:

可以写得列简洁些的,此类题其实初中生都能看懂.

2011年4月 7日 16:50 | 档案 | 引用

adriangong 说:

@limon:能具体说明吗?

2011年4月 8日 23:33 | 档案 | 引用

iotioa 说:

每排一次都要新建两个一左一右的数组,那一个非常大的数组,递归非常多次,那要消耗多少内存呢?

2011年4月11日 17:41 | 档案 | 引用

findfunaax 说:

引用iotioa的发言:

每排一次都要新建两个一左一右的数组,那一个非常大的数组,递归非常多次,那要消耗多少内存呢?

是啊,快速排序算法的优点之一就是可以原地排序,博主这样浪费掉了这个特性。

那个排序演示很有趣~

2011年4月12日 21:36 | 档案 | 引用

伦布大陆 说:

这个不错啊,以前的那本数据结构都是伪代码~~~

2011年4月23日 16:41 | 档案 | 引用

xieranmaya 说:

那啥,Array对象不是有sort()方法吗

2011年5月18日 12:00 | 档案 | 引用

big apple 说:

基本原理是实现了,但有一点不尽如人意的地方。
排序后得到的数组不是原来数组了。
如:
var a=[2,1,5,7,4,10,5];
alert(a==quickSort(a));//此处得到结果是false
一般来说,执行排序方法后,原来数组顺序就改变了,但还是原来的数组。
楼主的做法相当于返回了另一个数组。

2011年5月18日 14:35 | 档案 | 引用

big apple 说:

js数组自带排序方法就是如此
alert(a==a.sort());//返回true

2011年5月18日 14:37 | 档案 | 引用

evan 说:

觉的你这个网站的技术文章太好了,加为收藏,定时拜读。

2011年5月21日 11:22 | 档案 | 引用

Tyler Long 说:

我解释下Limon提到的python的代码:

q_sort= lambda l: l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x<=l[0]])

首先lambda是用来动态声明一个函数的. 这个函数接受一个参数l, l很明显是python的list类型. 
这个函数本身赋值给了等号左边的q_sort.

接下来分析右边的函数:
l if len(l) else 后面的部分是如果l的长度大于1就会执行

接下来分析else后面的部分:
采用l的第一个元素l[0]为"基准".
有两个+号,把三段list连接为一个list:
第一段list是小于l[0]的元素快速排序的结果
第二段list就只包含l[0]一个元素
第三段list是大于l[0]的元素快速排序的结果.
三段加起来就是最终的结果, 并且是排好序的. 其中第一段和第三段的生成过程中, 递归调用了q_sort本身.

但是, Simon的这个代码是无法得出正确的结果的! 也就是说是错的! 以下是示例运行结果:
>>> q_sort= lambda l: l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x<=l[0]])
>>> print q_sort([2,3,5,6,3,1,8,5])
[1, 2, 1]

接下来我会留言分析下为什么Simon给出的python解决方案是错的.

2011年5月24日 13:16 | 档案 | 引用

Tyler Long 说:

Simon的代码之所不行,是因为它打错了一个字符. 最后的"="
一开始我以为是什么隐蔽的错误呢, 没想到是这么明显的.

2011年5月24日 13:21 | 档案 | 引用

魏蕤 说:

引用阮一峰的发言:

@wordgold:想了一下,你说得对,没必要splice,只要在遍历数组的时候,只要跳过“基准”元素就行了,这样代码也更易读。谢谢。

调用javascript的splice方法很好啊。

此处var pivot = arr.splice(pivotIndex, 1)[0]有两个作用:

(1)将“基准”从arr数组删除

(2)将arr数组的“基准”赋值给pivot

如果按照@wordgold直接取,则后面还需要写一段将“基准”元素从arr数组删除的代码,这样一来代码反而更多。阮兄的代码很精简,拜读。

2011年5月24日 15:33 | 档案 | 引用

Tyler Long 说:

用C#代码实现:
Func<IEnumerable<IComparable>, IEnumerable<IComparable>> quick_sort = null; 
quick_sort = new Func<IEnumerable<IComparable>, IEnumerable<IComparable>>(l => l.Count() <= 1 ? l : quick_sort(l.Skip(1).Where(t => t.CompareTo(l.ElementAt(0)) < 0)).Concat(new IComparable[] { l.ElementAt(0) }).Concat(quick_sort(l.Skip(1).Where(t => t.CompareTo(l.ElementAt(0)) >= 0))));

2011年5月24日 16:00 | 档案 | 引用

Alex 说:

博主在编程方面的日志稍微少了点。可否写一些关于日常项目中的设计思想的日志,如果你愿意分享的话。

2011年5月25日 12:14 | 档案 | 引用

wordgold 说:

引用魏蕤的发言:

调用javascript的splice方法很好啊。

此处var pivot = arr.splice(pivotIndex, 1)[0]有两个作用:

(1)将“基准”从arr数组删除

(2)将arr数组的“基准”赋值给pivot

如果按照@wordgold直接取,则后面还需要写一段将“基准”元素从arr数组删除的代码,这样一来代码反而更多。阮兄的代码很精简,拜读。

不需要删除啊,仔细想想,比较的时候只比较大于小于没有等于就相当于删除了的

2011年7月12日 09:41 | 档案 | 引用

percy 说:

这个对理解quicksort的思想有好处,但实际应用中,quicksort一般会采取"in place"的做法,不应该引入额外的数组,若是这样,quicksort与mergesort都差不多了

2011年9月26日 10:33 | 档案 | 引用

percy 说:

这篇贴子的点击率还挺高的,希望博主更改成就地排序,不然可能会误导初学者啊

2011年9月26日 10:35 | 档案 | 引用

toming 说:

haskell的可以更简单:
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y < x] ++ [x] ++ qsort [y | y <- xs, y >= x]

这里有所有语言的qsort实现大全:http://rosettacode.org/wiki/Sorting_algorithms/Quicksort
几个函数语言haskell、erlang、ocaml、clojure的实现是最简洁的。

2011年9月28日 21:31 | 档案 | 引用

任迪 说:

嘿嘿 谢谢分享 这是我下一步要学习的

2012年1月23日 19:13 | 档案 | 引用

秦强强 说:

添个js原地排序的版本:

function quick (start ,end){
var mid = start , temp = arr[start];
for(var i = start+1 ; i if(arr[i] arr[mid]=arr[i];// >> mid+1 i-1
var j = i ; 
mid++;
while(j!=mid){arr[j]=arr[j-1];j--}
} 
}
arr[mid]= temp ;
return mid;
}

function sort (start , end ){
if(end>start){
var mid = quick(start ,end);console.log(mid)
sort(start,mid-1);sort(mid+1,end);
}
}

sort(0,arr.length)
console.log(arr);

2012年5月14日 18:34 | 档案 | 引用

redfish 说:

引用Tyler Long的发言:

Simon的代码之所不行,是因为它打错了一个字符. 最后的"="
一开始我以为是什么隐蔽的错误呢, 没想到是这么明显的.


你也够粗心的,不是最后的“=”,是if x<=l[0]]) 这里面的 < 要改成 > 就对了

2012年8月14日 14:53 | 档案 | 引用

nebula 说:

引用wordgold的发言:

取数组中间值的时候为什么不直接取而要Splice,如果是为了后续循环好像没必要

splice会对原数组做操作,这里获取pivot的同时,还从原数组中删除了pivot。

2013年11月21日 16:55 | 档案 | 引用

Nebula 说:

引用findfunaax的发言:

是啊,快速排序算法的优点之一就是可以原地排序,博主这样浪费掉了这个特性。

那个排序演示很有趣~

快速排序的递归部分并不是原地排序的,所以原地排序并不是快速排序的优点。

2013年11月21日 17:04 | 档案 | 引用

巴晓鹏 说:

why i love ruby?

def quick_sort(a) 
  (x=a.pop) ? quick_sort(a.select{|i| i x}) : [] 
end

2014年2月14日 17:19 | 档案 | 引用

Troland 说:

挺好的,就是如果在对比的时候不加上那个不用splice在循环的时候判断是否和pivoet相等会少递归循环。。而且会比较好读。。

2014年5月24日 22:59 | 档案 | 引用

我要发表看法

 «-必填

 «-必填,不公开

 «-我信任你,不会填写广告链接

 «- 点击按钮

联系方式 | ruanyifeng.com 2003 - 2014 Site Meter

posted on 2014-06-18 12:00  xiaoleilei  阅读(547)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3