菜鸟前端刷算法第十一天(数组排序)

110 阅读5分钟

题目描述 - 数组排序

给你一个整数数组 nums,请你将该数组升序排列。

选择排序

在每一次排序中,从未排序部分中选出一个值最小的元素,与未排序部分第 1 个元素交换位置,从而将该元素划分到已排序部分。

思路分析:
  • 将数组分为两部分:已排序部分、未排序部分。
  • 每一次循环从未排序部分元素中选择一个值最小的元素与未排序部分最前面那个元素交换位置。
  • 如此下去,直到所有元素都变为已排序部分,排序结束。
步骤分解:
  • 第 1 次排序:

1.无已排序部分,n个元素都为未排序部分。

2.遍历 n 个元素,使用变量 min 记录 n 个元素中值最小的元素位置。

3.将 min 与未排序部分第 1 个元素交换位置。若未排序部分第 1 个元素就是值最小的元素位置,则不用交换。

4.此时第 1 个元素为已排序部分,剩余第 2 ~ n 个元素(总共 n - 1 个元素)为未排序部分。

  • 第 2 次排序:

1.遍历剩余未排序部分 n - 1 个元素,使用变量 min 记录 n - 1 个元素中值最小的元素位置。

2.将 min 与未排序部分第 1 个元素(序列的第 2 个元素)交换位置。若未排序部分第 1 个元素就是值最小的元素位置,则不用交换。

3.此时第 1 ~ 2 个元素为已排序部分,剩余第 3 ~ n 个元素(总共 n - 2 个元素)为未排序部分。

  • 依次类推,对剩余 n - 2 个元素重复上述排序过程,直到所有元素都变为已排序部分,则排序结束。
代码实现:
/**
 * 选择排序
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function(nums) {
    for(let i=0;i<nums.length; i++){
        let min = i;
        for(let j = i+1;j<nums.length; j++){
            if(nums[j] < nums[min]){
                min = j
            }
        }
        if(i !== min){
            let temp = nums[i]
            nums[i] = nums[min]
            nums[min] = temp
        }
    }
    return nums
};
插入排序

在每一次排序中,将无序序列的第 1 个元素,插入到有序序列的适当位置上。

思路分析:
  • 将数组分为两部分:已排序部分、未排序部分。
  • 每一次循环从未排序部分元素取第一个元素与已排序部分每个元素对比,直至找到合适插入位置。
  • 如此下去,直到所有元素都变为已排序部分,排序结束。
步骤分解:
  • 第 1 次排序:

1.第 0 个元素为有序序列,后面 1 ~ n-1 元素为无序序列。此时无序序列第一个元素为 arr[1]。

2.从右至左遍历有序序列中的元素,如果遇到 有序序列的元素 > 无序序列的第 1 个元素(arr[1]) 的情况时,则将向有序序列的元素后移动一位, 无序序列元素插入此位置。

3.如果遇到 有序序列的元素 <= 无序序列的第 1 个元素 的情况 或者 到达数组开始位置 时,则说明找到了插入位置。将 无序序列的第 1 个元素 插入该位置。

  • 第 2 次排序:

1.第 0 ~ 1 个元素为有序序列,后面 2 ~ n-1 元素为无序序列。此时无序序列第一个元素为 arr[2]。 2.从右至左遍历有序序列中的元素,如果遇到 有序序列的元素 > 无序序列的第 1 个元素(arr[2]) 的情况时,则将向有序序列的元素后移动一位,无序序列元素插入此位置。

3.如果遇到「有序序列的元素 <= 无序序列的第 1 个元素 的情况或者 到达数组开始位置 时,则说明找到了插入位置。将 无序序列的第 1 个元素 插入该位置。

  • 依次类推,对剩余元素重复上述排序过程,直到所有元素都变为有序序列,则排序结束。
代码实现:
/**
 * @param {number[]} nums
 * @return {number[]}
 */
 // 插入排序
var sortArray = function(nums) {
    // 循环开始, 第0个位为数组有序部分,从第1个开始为无序部分
    for(let i=1; i<nums.length; i++){
       let item = nums[i];
       let j = i
       while(j>0 && nums[j-1] > item){
           nums[j] = nums[j-1]
           nums[j-1] = item
           j--
       }
    }
    return nums
};
计数排序

大多数的排序算法都是基于元素之间的比较来进行排序的,计数排序却并非如此,这种算法是利用数组下标来确定元素正确位置的。

思路分析:
  • 三个数组, 一个待排序数组,一个空数组,一个结果数组
  • 循环待排序数组,空数组中 以待排序数组的值 为下标位置 的值为 待排序数组元素出现次数。
  • 循环空数组,将数组中有值的下标插入结果数组,值为几则需要插入几次。
  • 需要注意的是待排序数组负数的处理,数组下标不能为负数,空数组下标 = 待排序数组的值 - 数组最小的数字
步骤分解:

假设待排序数组:[1,3,-1,2,5,2],空数组 counts = [ ]

  • 找出数组最小值:-1
  • 待排序数组遍历,元素 - 数组最小值 = 空数组下标,值为出现次数;如数组第一个元素1,得到 counts[2] = 1; 数组第二个元素,得到 counts[4] = 1,...。 最后空数组结果为: [1, empty, 1, 2, 1, empty, 1]。
  • 反编译空数组:遍历数组, 下标 + 数组最小值 = 原数组值,插入结果数组。
代码实现:
/**
 * @param {number[]} nums
 * @return {number[]}
 */
 // 计数排序
var sortArray = function(nums) {
    // 数组最小值 规避下标不能为负数
    let min = Math.min(...nums)
    let counts = [];
    for(let i=0; i< nums.length; i++){
        let item = nums[i] - min
        // 空数组下标 = 数组值 - 数组最小值
        // 空数组下标值 = 数组值出现次数
        if(counts[item]) counts[item]++
          else counts[item] = 1
    }
    let resArr = [];
    // 反向编译空数组
    for(let i=0; i<counts.length; i++ ){
        let count = counts[i]
        while(count){
            resArr.push(i + min)
            count--
        }
    }
    return resArr
};
归并排序

大多数的排序算法都是基于元素之间的比较来进行排序的,计数排序却并非如此,这种算法是利用数组下标来确定元素正确位置的。

思路分析:

采用经典分治策略,先递归将当前序列平均分成两半。然后将有序序列两两合并,最终合并成一个有序序列。

步骤分解:

1.分割:先递归地将当前序列平均分成两半,直到子序列长度为 1。

  • 找到序列中心位置 mid,从中心位置将序列分成左右两个子序列 leftArr、rightArr。
  • 对左右两个子序列 leftArr、rightArr 分别进行递归分割。
  • 最终将数组分割为 n 个长度均为 1 的有序子序列。

2.归并:从长度为 1 的有序子序列开始,依次进行两两归并,直到合并成一个长度为 n 的有序序列。

  • 使用数组变量 resArr 存放归并后的有序数组。
  • 使用两个指针 leftIndex、rightIndex 分别指向两个有序子序列 leftArr、rightArr 的开始位置。
  • 比较两个指针指向的元素,将两个有序子序列中较小元素依次存入到结果数组 resArr 中,并将指针移动到下一位置。 重复上一步骤,直到某一指针到达子序列末尾。 将另一个子序列中的剩余元素存入到结果数组 resArr 中。

3.返回归并后的有序数组 arr。

代码实现:
/**
 * @param {number[]} nums
 * @return {number[]}
 */
 // 归并
var sortArray = function(nums) {
    // 分割
    if(nums.length < 2) return nums
    let mid = Math.floor(nums.length / 2)
    let leftArr = nums.slice(0, mid);
    let rightArr = nums.slice(mid);
    return merge (sortArray(leftArr), sortArray(rightArr))
    // 合并
    function merge (leftArr, rightArr){
        let res = []
        let lenLeft = leftArr.length;
        let lenRight = rightArr.length;
        let leftIndex = 0,rightIndex = 0
        while(leftIndex < lenLeft && rightIndex < lenRight){
            if(leftArr[leftIndex] < rightArr[rightIndex]){
                res.push(leftArr[leftIndex])
                leftIndex++
            }else{
                res.push(rightArr[rightIndex])
                rightIndex++
            }
        }
        while(leftIndex < lenLeft ){
            res.push(leftArr[leftIndex])
            leftIndex++
        }
        while(rightIndex < lenRight ){
            res.push(rightArr[rightIndex])
            rightIndex++
        }
        return res
    }
};