文章目录

  • 数组排序
  • 选择排序
  • 快速排序
  • 冒泡排序
  • 插入排序
  • 桶排序
  • sort 排序


数组排序

下面的所有排序,如果有对解释不好理解的,可以直接选择代入数组,自己梳理一下逻辑就会明白了。

也可以去Typescript的Playground 然后把代码拷贝进去自己跑一下。


这里说一个知识点:

if (array[j] > array[j + 1]) {
    temp = array[j];
    array[j] = array[j + 1];
    array[j + 1] = temp;
}

大家都见过这样的代码吧,典型的值的交换,这里给大家提供一种简单方法:

if (array[j] > array[j + 1]) {
    [array[j], array[j + 1]] = [array[j + 1], array[j]]
}
# 利用es6的解构赋值,不用定义空变量存储,直接交换。
# 当然,我下面的代码里,还是使用的是上面的旧的方法,好理解一点。

选择排序

选择排序:

  • 首先从原始数组中找到最小的元素
  • 然后把该元素放到数组的最前面,然后再从剩下的元素里寻找最小元素,放到之前最小元素的后面
  • 以此类推,直到排序完毕。

选择排序示例:

const arr = [1, 2, 4, 3, 6, 5, 8, 7, 9];

let index: number, temp: number;
for (let i = 0; i < arr.length - 1; i++) {
    index = i;
    for (let j = i + 1; j < arr.length; j++) {
        if (arr[j] < arr[index]) {
            index = j
        }
    }
    temp = arr[i];
    arr[i] = arr[index];
    arr[index] = temp;
}
console.log(arr);

# 输出结果:
# [1,2,3,4,5,6,7,8,9]

第二层循环其实就是在找最小值的位置下标,每次循环碰见比记录的下标【index】小的值就记录更小的值的下标 【index = j】,然后拿小的值继续向后比较。

快速排序

快速排序:

  • 首先找一个基准值(一般是数组中的中间元素 length / 2 所在)
  • 找到基准元素后,拿数组中的元素与基准值比较,小于基准值放到左边,大于基准值的放到右边。
  • 按照顺序,每个值与基准值比较后,会形成左右两个子集,基准的值左边都是小于基准值的元素,右边则相反。
  • 然后在基准值的左右两边子集中再各选一个基准值,然后在重复上述比较。
  • 直到所有子集中只剩下一个元素,则排序完成。

快速排序示例:

const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];

const sort = (arr: Array<any>): any => {
    if (arr.length <= 1) return arr;
    const centerIndex = Math.floor(arr.length / 2);
    const centerValue = arr.splice(centerIndex, 1)[0]; // 基准值

    let left = [];
    let right = [];

    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < centerValue) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    return sort(left).concat([centerValue], sort(right));
};
const res = sort(array);
console.log(res);

# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

快速排序可能听起来很麻烦,但是实现起来还是比较简单的

不要被我写的 sort 所影响,上面的 sort 只是我定义的一个变量名字,和 数组的sort方法 是风马牛不相及的。

冒泡排序

冒泡排序:

  • 比较数组中相邻的两个数,如果前者大于后者,则两个数交换位置。
  • 一轮过后,就可以选出最大的一个数,放到最后面。
  • 然后一直这样比较,直到排序完成。

冒泡排序示例:

const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];

let temp: any;
for (let i = 0; i < array.length; i++) {
    for (let j = 0; j < array.length - i; j++) {
        if (array[j] > array[j + 1]) {
            temp = array[j];
            array[j] = array[j + 1];
            array[j + 1] = temp;
        }
    }
}
console.log(array);

# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

外层循环控制循环次数,内层循环对相邻的两个值进行比较。之所以内层循环让 length - i,主要是为了减少不必要的循环次数。

插入排序

插入排序:

  • 第一元素开始,该元素可以认为已经被排序。
  • 取出第二个元素,在已经排序的序列中从后向前扫描。
  • 如果当前排序的元素大于已排序的该元素,这将该元素向后移动一个位置。
  • 直到遇到 已排序的元素小于或等于当前排序的元素。
  • 将元素插入到当前位置中。
  • 循环 2 - 5 步,直到结束。

举个例子:

数组为:[4, 2, 1, 3]

  • 第一遍,当 4 是已经排序的元素,取出 2 与 已经排序的 4 比较,4 > 2 所以把 4 向后移动一个位置,又因为 4 是 0号元素,所以 2 插入4 所在的位置,4 移动到 2所在的位置。
  • 得到数组:[2, 4, 1, 3]
  • 第二遍:2 ,4 是已经排序的元素,取出 1 与已经排序的元素比较,4 > 1 所以 4 向后移动一个位置,1 继续向前比较,因为 2 > 1 所以 2 向后移动一个位置 又因为 2 是 0号元素,所以,1 插入到 0号元素的位置
  • 得到数组:[1, 2, 4, 3]
  • 第三遍:1, 2, 4是已经排序的元素,取出 3 与已经排序的元素比较,4 > 3 所以 4 向后移动一个位置,3 继续向前比较,因为 2 < 3 ,所以将 3 插入到原先4所在的位置。

插入排序示例:

const array = [4, 2, 1, 3, 6, 5, 8, 7, 9];

let preIndex: number, current;
for (let i = 1; i < array.length; i++) {
    preIndex = i - 1;
    current = array[i];
    while (preIndex >= 0 && current < array[preIndex]) {
        array[preIndex + 1] = array[preIndex];
        preIndex--;
    }
    array[preIndex + 1] = current;
}
console.log(array);

# 输出结果:
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

有前面的例子做铺垫,想必理解这个插入排序应该不是很麻烦了

桶排序

桶排序:

  • 设置一个固定数量的空桶。
  • 将数据放到对应的空桶中。
  • 定义一个新数组,将这些不为空的桶进行排序。
  • 得到结果。

注意:

  • 桶排序什么时候最快?当输入的数据可以均匀分布到每一个桶中的时候。
  • 桶排序什么时候最慢?当输入的数据分配到了一个桶中的时候。

首先来看不定桶长度的桶排序:

  • 这里的不定长,指定的是根据传入的数据来动态的设置桶的数量。
const array = [4, 2, 11, 3, 16, 5, 8, 17, 9];

const bucketSort = (arr: Array<any>) => {
    if (arr.length < 2) return;

	// 定义桶
    let bucket: any[] = [];
    arr.forEach((item) => {
        if (bucket[item] !== undefined) {
            bucket[item]++;
        } else {
            bucket[item] = 1;
        }
    });
    
    // 第一输出位置

	// 存放排序后的数据
    const newArr: any[] = [];
    bucket.forEach((item, index) => {
        if (item !== undefined) {
            for (let i = 0; i < item; i++) {
                newArr.push(index);
            }
        }
    });

	// 第二输出位置

    arr.splice(0, arr.length, ...newArr);
    return arr;
}
const res = bucketSort(array);
console.log(res);

# 输出结果:
# [2, 3, 4, 5, 8, 9, 11, 16, 17]

解析:

  • 如果我们在 第一输出位置 输出一下 bucket ,我们就会得到一个有意思的数组:
bucket  [, , 1, 1, 1, 1, , , 1, 1, , 1, , , , , 1, 1] 
# 这个bucket数组的长度是 18,其实就是我们传入的数组的最大值。
# 这也就是我们刚开始说的,桶的长度不固定,由给的数组中的最大数据确定。

不要蒙,我解释一下 bucket 的那一段就明白了为啥 bucket的长度刚好是我们传入的数组的最大值:

  • 首先遍历传入的数组 ,arr.foreach 中 item 就是我们传入数组中的每一项的值
  • 然后,如果 bucket 中 item 位置有值,则让 bucket[item] ++ ,否则bucket[item] = 1;
  • 举例:
  • 传入的数据第一项是 4
  • bucket[4] 很明显示 undefined 的,所以 bucket[4] = 1,此时我们得到的数组是这样的:
[, , , , 1]
  • 如果下一项还是 4 ,那么就让 bucket[item] ++, 此时得到的数组是这样的:
[, , , , 2]
  • 明白了吧,bucket 数组中的值,就代表着我们有几个相同的此项,bucket 数组中每一个不为空值的下标,其实就是我们传入的数组中的每一个值。

说的再简单一点:

传入的数组中的每一项,都变成了 bucket 数组的下标。

每一项重复了多少次,就是这一下标的值。

  • 接下来第二输出位置输出的就是拼接后的数据了。
大家可以自行从脑海里抽象一下: 数组的结构 下标在上 值在下 —— 桶 的形象了:

Typescript写入数组 typescript 数组排序_js 数组排序

然后我们再来看固定桶长度的排序:

  • 固定桶长度的代码解释起来比较麻烦,我先给大家说一下思想。
  • 思想 【以固定的5个桶为例】:
  • 首先我们要找到我们传入的数据的最大值和最小值,然后由 最大值和最小值和桶数 得出每一个桶能存放的数据范围。
(max - min + 1) / 5
# max 最大值,min最小值 5 为桶数
  • 然后开始遍历数据,先找到这个数组应该存放在那个桶里
Math.floor((cur - min) / range)
# cur当前值 min最小值 range范围(上一步求出的范围)
  • 当向同一个序列的桶中第二次插入数据时,判断桶中已存在的数字与新插入的数字的大小,按从左到右,从小到大的顺序插入 。
  • 全部的数据都装入到这 5 个桶中后,按照序列,从小到达合并桶。
  • 得到排序后的数据。

固定桶长度排序代码示例:

const array = [4, 4, 2, 11, 3, 16, 5, 8, 17, 9];

/**
 * arr 传入数组
 * count 桶的数量
 */
const bucketSort = (arr: Array<number>, bucketCount: number): any => {
    if (arr.length <= 1) return arr;

    // 如果没有传桶数则设置默认桶数
    const count = bucketCount ?? 10;

    // 数组最大值和最小值
    const max = Math.max(...arr);
    const min = Math.min(...arr);

    // 根据传入的桶的数量计算每一个桶的范围
    const range = (max - min + 1) / count;

    // 定义桶数组
    let bucket: any[] = [];
    // 遍历数组,插入桶中
    arr.forEach(item => {
        const num = Math.floor((item - min) / range);
        // 如果 num 桶中是否存在值
        if (bucket[num]) {

            // 按照从小到大的顺序排列 [这里其实就是插入排序]
            let bLength = bucket[num].length - 1;
            while (bLength >= 0 && bucket[num][bLength] > item) {
                // 每一项都向后移动
                bucket[num][bLength + 1] = bucket[num][bLength];
                bLength--;
            }
            bucket[num][bLength + 1] = item;

        } else {
            // 构建一个二维数组
            bucket[num] = [];
            bucket[num].push(item);
        }
    });

    // 拼接数据
    let result: any[] = [], n = 0;
    while (n < count) {
        if (bucket[n]) {
            result = result.concat(bucket[n]);
        }
        n++;
    }
    return result;
}
// 调用
const res = bucketSort(array, 5);
console.log(res);

# 输出结果
# [2, 3, 4, 4, 5, 8, 9, 11, 16, 17]

大家可以结合我前面说的思想,来对应代码理解一下。中间往桶中插入数据时,其实就是利用了插入排序来保证从小到大的排序。

那么现在,我们可以抽象一下这个 固定桶数量的的 桶的抽象:

Typescript写入数组 typescript 数组排序_数据_02

区别:

如果大家认认真真看到这里的话,想必对 定长的桶 和 不定长的桶 的区别,心里应该有一点数了:

  • 定长的桶:
  1. 数据均匀分布到每一个桶中
  2. 桶数组下标即为传入的数组的每一项值。
  3. 桶数组的值即为传入的数组的每一项的重复次数。
  • 不定长的桶:
  1. 数据按照所处的范围分布到固定数量某一桶中。
  2. 桶数组的下标代表的当前的桶号。
  3. 桶数组的值即为传入的数组的每一项的值,且可能会有多个。

sort 排序

sort 排序就不多说了,老生长谈的话题了,这里就不多说了,可以去看W3school - JavaScript 数组排序

当然,要是你能把 sort 排序玩出花来,请把代码评论到下面,我们共勉。嘿嘿。


以上就是我要说的一些排序了。完结·撒花

结语[废话整一句]:其实做一个数组排序,写这么麻烦的代码完全没啥必要,数组的sort方法用来排序完全够用且够简洁,但是怎么说呢,嘿嘿,思想很重要,逻辑理解很重要,锻炼思维很重要。嘿嘿。