2020-11-25 排序算法三(计数排序和桶排序)

计数排序

计数排序非常容易理解,相信等我介绍完概念,大家都可以写出来。

通过数组下标来记录数列中各数的,通过下标对应的值来记录相同数出现的频率。打个比方,一个长度为10的数组,下标为0-9,如果我们要排序的数列为0-9之间的随机数,如list = [0, 0, 8, 3, 4, 2, 7, 7, 7, 5],要将其从小到大排列:

  • 声明一个下标计数数组为sortArray = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],初始值都为0;
  • 遍历list,每个元素大小对应下标的值在计数数组中加1,如遍历完前三个数后,sortArray变成了[2, 0, 0, 0, 0, 0, 0, 0, 1, 0]
  • 结束后再遍历sortArray打印出有序的数列。

是不是很简单呢,代码如下:
需要注意,一开始我们是不确定数列的最大值的,所以我们要先找到数列中的最大值max,并将sortArray的长度设为max + 1,保证最大值可以找到其对应的下标

function countSort(list: number[]): number[] {
  let max = 0;
  for (let i = 0; i < list.length; i++) {
    if (list[i] > max) {
      max = list[i];
    }
  }
  const sortArray: number[] = new Array(max+1);
  list.forEach(item => {
    if (sortArray[item] === undefined) {
      sortArray[item] = 0;
    }
    sortArray[item]++;
  });
  const res = [];
  sortArray.forEach((sort, index) => {
    if (sort > 0) {
      for (let i = 0; i < sort; i++) {
        res.push(index);
      }
    }
  });
  return res;
}

这样是可以实现了排序,但是还可以进行优化,如果我们要排列的数组为[99, 98, 97, 93, 95]呢?最大值为99,所以我们建了一个长度为100的数组,而实际只利用了下标为93-99这一段空间。所以我们可以建立一个长度为max - min + 1的数组,来进行计数。最后统计的时候每个元素都加上最小值min即可。

function countSort(list: number[]): number[] {
  let max = 0;
  let min = 0;
  for (let i = 0; i < list.length; i++) {
    if (list[i] > max) {
      max = list[i];
    }
    if (list[i] < min) {
      min = list[i];
    }
  }
  const sortArray: number[] = new Array(max - min + 1);
  list.forEach(item => {
    const index = item - min;
    if (sortArray[index] === undefined) {
      sortArray[index] = 0;
    }
    sortArray[index]++;
  });
  const res = [];
  sortArray.forEach((sort, index) => {
    if (sort > 0) {
      for (let i = 0; i < sort; i++) {
        res.push(index + min);
      }
    }
  });
  return res;
}

上述方法只是记录了某个数出现的频率,并没有真正将输入的数列排列成有序的,造成了我们容易分不清相同大小的值谁在前谁在后。

改进

  • 将统计数组sortArray进行处理,让其变成为代表输入数列为该下标的元素的排名
    [5, 5, 9, 0, 4]进行统计并处理的过程如下:从第二个元素开始,每个元素的值为前面所有值的和,因为我们是从小到大排序,所以在sortArray前面出现的元素,在输出结果中排名肯定是比后面的高

    image.png

  • 从后往前(为了保证排序稳定性)遍历输入数组,利用其值value找到sortArray中对应下标的元素item(处理完该元素后,该item的值应该减一,因为下次再出现相同的值,由于排序稳定性,应该是排名靠前一位的),item就代表着value元素在最终的输出结果中的位置排名。

function countSort(list: number[]): number[] {
  let max = 0;
  let min = 0;
  for (let i = 0; i < list.length; i++) {
    if (list[i] > max) {
      max = list[i];
    }
    if (list[i] < min) {
      min = list[i];
    }
  }
  const sortArray: number[] = new Array(max - min + 1);
  list.forEach(item => {
    const index = item - min;
    if (sortArray[index] === undefined) {
      sortArray[index] = 0;
    }
    sortArray[index]++;
  });
  for (let i = 1; i < sortArray.length; i++) {
    // 排名递增
    if (sortArray[i] === undefined) {
      sortArray[i] = 0;
    }
    sortArray[i] += sortArray[i - 1];
  }
  const res = new Array(list.length);
  list.forEach(value => {
    const realIndex = value - min;
    res[sortArray[realIndex] - 1] = value;
    sortArray[realIndex]--;
  });
  return res;
}
总结
  • 计数排序只适合在一定范围内的整数排序;
  • 极值差为m,输入规模为n,其时间复杂度为O(n+m)(遍历了三次输入数组,3n省略了系数),空间复杂度为O(m)(sortArray长度为m)

桶排序

基本思想就是:

  • 创建桶,方法有很多,一般来说对n个数进行排序就创建n个桶,可以把桶当作一个链表,一堆桶当作一个数组;
  • 确定每个桶的存值区间,方法也很多,一般来说是输入数列的极值差(max-min)除以个数n为一个区间值,每个桶之间间隔一个区间值;
  • 遍历输入数列list,将其按照区间存放到桶内,同一个桶内可能存在0个或多个值;
  • 遍历桶,将其内部值进行排序;
  • 遍历桶,输出所有元素,得到有序数列。
function sortByBucket(list: number[]) {
  let max = list[0];
  let min = list[0];
  for (let i = 1; i < list.length; i++) {
    if (list[i] > max) {
      max = list[i];
    }
    if (list[i] < min) {
      min = list[i];
    }
  }
  const dValue = max - min;
  // 确定桶的存值区间
  const partValue = dValue / (list.length - 1);
  // 建桶
  const bucket: number[][] = [];
  for (let i = 0; i < list.length; i++) {
    bucket.push([]);
  }
  // 插值
  for (let i = 0; i < list.length; i++) {
    const index = Math.floor((list[i] - min) / partValue);
    bucket[index].push(list[i]);
    // 内部排序
    for (let i = bucket.length - 1; i > 1; i--) {
      const singleBucket = bucket[index];
      if (singleBucket[i - 1] > singleBucket[i]) {
        singleBucket[i - 1] = singleBucket[i] ^ singleBucket[i - 1];
        singleBucket[i] = singleBucket[i] ^ singleBucket[i - 1];
        singleBucket[i - 1] = singleBucket[i] ^ singleBucket[i - 1];
      }
    }
    console.log(bucket)
  }
  // 输出
  const res = [];
  for (let i = 0; i < bucket.length; i++) {
    res.push(...bucket[i]);
  }
  return res;
}

总结

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

推荐阅读更多精彩内容