三种基础的排序算法

在计算机科学所使用的排序算法通常被分类为:

  • 计算的 时间复杂度(最差、平均、和最好性能),依据列表(list)的大小(n)。一般而言,好的性能是O(n log n),且坏的性能是O(n^2)。对于一个排序理想的性能是O(n)。仅使用一个抽象关键比较运算的排序算法总平均上总是至少需要O(n log n)
  • 存储器使用量(以及其他电脑资源的使用)
    稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
  • 依据排序的方法:插入、交换、选择、合并等等。

依据排序的方法分类的三种排序算法:

冒泡排序

冒泡排序对一个需要进行排序的数组进行以下操作:

  1. 比较第一项和第二项;
  2. 如果第一项应该排在第二项之后, 那么两者交换顺序;
  3. 比较第二项和第三项;
  4. 如果第二项应该排在第三项之后, 那么两者交换顺序;
  5. 以此类推直到完成排序;

冒泡排序.gif

实例说明:

将数组[3, 2, 4, 5, 1]以从小到大的顺序进行排序:

  1. 3应该在2之后, 因此交换, 得到[2, 3, 4, 5, 1];
  2. 3, 4顺序不变, 4, 5也不变, 交换5, 1得到[2, 3, 4, 1, 5];
  3. 第一次遍历结束, 数组中最后一项处于正确位置不会再有变化, 因此下一次遍历可以排除最后一项;
  4. 开始第二次遍历, 最后结果为[2, 3, 1, 4, 5], 排除后两项进行下一次遍历;
  5. 第三次遍历结果为[2, 1, 3, 4, 5];
  6. 最后得到[1, 2, 3, 4, 5], 排序结束;

代码实现:

function swap(items, firstIndex, secondIndex){
      var temp = items[firstIndex];
      items[firstIndex] = items[secondIndex];
      items[secondIndex] = temp;
};
function bubbleSort(items){
      var len = items.length, i, j, stop;
      for (i = 0; i < len; i++){
        for (j = 0, stop = len-i; j < stop; j++){
          if (items[j] > items[j+1]){
            swap(items, j, j+1);
          }
        }
      }
      return items;
}

外层的循环决定需要进行多少次遍历, 内层的循环负责数组内各项的比较, 还通过外层循环的次数和数组长度决定何时停止比较.

冒泡排序极其低效, 因为处理数据的步骤太多, 对于数组中的每n项, 都需要n^2次操作来实现该算法(实际比n^2略小, 但可以忽略, 具体原因见⤵️), 即时间复杂度为O(n^2).

对于含有n个元素的数组, 需要进行(n-1)+(n-2)+...+1次操作, 而(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 - n/2, 如果n趋于无限大, 那么n/2的大小对于整个算式的结果影响可以忽略, 因此最终的时间复杂度用O(n^2)表示

选择排序

选择排序对一个需要进行排序的数组进行以下操作:

1.假定数组中的第一项为最小值(min);

  1. 比较第一项和第二项的值;
  2. 若第二项比第一项小, 则假定第二项为最小值;
  3. 以此类推直到排序完成.

选择排序.gif

实例说明:

将数组["b", "a", "d", "c", "e"]以字母a-z的顺序进行排序:

  1. 假定数组中第一项"b"(index0)为min;
  2. 比较第二项"a"与第一项"b", 因"a"应在"b"之前的顺序, 故"a"(index1)为min;
  3. 然后将min与后面几项比较, 由于"a"就是最小值, 因此min确定在index1的位置;
  4. 第一次遍历结束后, 将假定的min(index0), 与真实的min(index1)进行比较, 真实的min应该在index0的位置, 因此将两者交换, 第一次遍历交换之后的结果为["a", "b", "d", "c", "e"];
  5. 然后开始第二次遍历, 遍历从第二项(index1的位置)开始, 这次假定第二项为最小值, 将第二项与之后几项逐个比较, 因为"b"就在应该存在的位置, 所以不需要进行交换, 这次遍历之后的结果为["a", "b", "d", "c", "e"];
    6.之后开始第三次遍历, "c"应为这次遍历的最小值, 交换index2("d"),index3("c")位置, 最后结果为["a", "b", "c", "d", "e"];
  6. 最后一次遍历, 所有元素在应有位置, 不需要进行交换.

代码实现:

function swap(items, firstIndex, secondIndex){
  var temp = items[firstIndex];
  items[firstIndex] = items[secondIndex];
  items[secondIndex] = temp;
};

function selectionSort(){
  let items = [...document.querySelectorAll('.num-queue span')].map(num => +num.textContent);
  let len = items.length, min;

  for (i = 0; i < len; i++){
    min = i;
    for(j = i + 1; j < len; j++){
      if(items[j] < items[min]){
        min = j;
      }
    }
    if(i != min){
      swap(items, i, min);
    }
  }
  return items;
};

外层循环决定每次遍历的初始位置, 从数组的第一项开始直到最后一项. 内层循环决定哪一项元素被比较.

选择排序的时间复杂度为O(n^2).

插入排序

与上述两种排序算法不同, 插入排序是稳定排序算法(stable sort algorithm), 稳定排序算法指不改变列表中相同元素的位置, 冒泡排序和选择排序不是稳定排序算法, 因为排序过程中有可能会改变相同元素位置. 对简单的值(数字或字符串)排序时, 相同元素位置改变与否影响不是很大. 而当列表中的元素是对象, 根据对象的某个属性对列表进行排序时, 使用稳定排序算法就很有必要了.

一旦算法包含交换(swap)这个步骤, 就不可能是稳定的排序算法. 列表内元素不断交换, 无法保证先前的元素排列为止一直保持原样. 而插入排序的实现过程不包含交换, 而是提取某个元素将其插入数组中正确位置.

插入排序的实现是将一个数组分为两个部分, 一部分排序完成, 一部分未进行排序. 初始状态下整个数组属于未排序部分, 排序完成部分为空. 然后进行排序, 数组内的第一项被加入排序完成部分, 由于只有一项, 自然属于排序完成状态. 然后对未完成排序的余下部分的元素进行如下操作:

  1. 如果这一项的值应该在排序完成部分最后一项元素之后, 保留这一项在原有位置开始下一步;
  2. 如果这一项的值应该排在排序完成部分最后一项元素之前, 将这一项从未完成部分暂时移开, 将已完成部分的最后一项元素移后一个位置;
  3. 被暂时移开的元素与已完成部分倒数第二项元素进行比较;
  4. 如果被移除元素的值在最后一项与倒数第二项的值之间, 那么将其插入两者之间的位置, 否则继续与前面的元素比较, 将暂移出的元素放置已完成部分合适位置. 以此类推直到所有元素都被移至排序完成部分.

插入排序.gif

实例说明:

现在需要将数组var items = [5, 2, 6, 1, 3, 9];进行插入排序:

  1. 5属于已完成部分, 余下元素为未完成部分. 接下来提取出2, 因为5比2大, 于是5被移至靠右一个位置, 覆盖2, 占用2原本存在的位置. 这样本来存放5的位置(已完成部分的首个位置)就被空出, 而2在比5小, 因此将2置于这个位置, 此时结果为[2, 5, 6, 1, 3, 9];
  2. 接下来提取出6, 因为6比5大, 所以不操作提取出1, 1与已完成部分各个元素(2, 5, 6)进行比较, 应该在2之前, 因此2, 5, 6各向右移一位, 1置于已完成部分首位, 此时结果为[1, 2, 5, 6, 3, 9];
  3. 对余下未完成元素进行类似操作, 最后得出结果[1, 2, 3, 5, 6, 9];

代码实现:

function insertionSort(items) {
  let len = items.length, value, i, j;
  for (i = 0; i < len; i++) {
    value = items[i];
    for (j = i-1; j > -1 && items[j] > value; j--) {
      items[j+1] = items[j];
    }
    items[j+1] = value;
  }
  return items;
};

外层循环的遍历顺序是从数组的第一位到最后一位, 内层循环的遍历则是从后往前, 内层循环同时负责元素的移位.

插入排序的时间复杂度为O(n^2)

以上三种排序算法都十分低效, 因此实际应用中不要使用这三种算法, 遇到需要排序的问题, 应该首先使用JavaScript内置的方法Array.prototype.sort();

参考:

2015-CS50-week-算法
排序算法-JavaScript描述
三种基础的排序算法

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

推荐阅读更多精彩内容

  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! 1.冒泡排序(Bub...
    王饱饱阅读 1,790评论 0 7
  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an输出...
    BULL_DEBUG阅读 769评论 0 3
  • 我的生物钟彻底紊乱了,早上没精神,晚上精神好的很。所有需要用脑子的事情,都必须到中午11点以后才能做。而且常常要到...
    百合手工阅读 327评论 5 0
  • 老是画不好眼睛和手~还要好好学习
    铅笔只演绎黑白阅读 213评论 0 0
  • 今天出发去陕西的最北边啦-榆林。一路北上! 今天喉咙痛的比昨天厉害了,不知道她是不是也变得严重了,希望不是不是。 ...
    阿立立哥阅读 157评论 0 0