排序

常见的排序算法

将常见的几种排序手写了一边,一些心得和理解写在了代码的注释中,其中桶排序、计数排序、堆排序未写代码,写了一些自己的理解。
本文参考:
1、排序图解:js排序算法实现
2、五分钟学会一个高难度算法:希尔排序
3、排序算法(九):桶排序
4、堆排序及其分析

let unSortArr = [17, 25, 3, 14, 20, 9];
let unSortArr2 = [1, 2, 3, 4, 5, 6];
//冒泡
function Bubble(preArr) {
  let temp,
    arr = preArr.slice(0);
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i; j++) {
      if (arr[j] > arr[j + 1]) {
        temp = arr[j + 1];
        arr[j + 1] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}
//冒泡改进,对于上次没有交换任何元素时(说明已经有序),可不进行接下来的遍历,减少运行次数
function Bubble2(preArr) {
  let temp,
    arr = preArr.slice(0),
    exchangeFlag = true;
  for (let i = 0; i < arr.length; i++) {
    exchangeFlag = false;
    for (let j = 0; j < arr.length - i; j++) {
      if (arr[j] > arr[j + 1]) {
        temp = arr[j + 1];
        arr[j + 1] = arr[j];
        arr[j] = temp;
        exchangeFlag = true;
      }
    }
    if (!exchangeFlag) break;
  }
  return arr;
}
// 插入排序 从下标1开始每增1项排序一次,越往后遍历次数越多
function insert(arr) {
  let res = arr.slice(0),
    j,
    temp;
  for (let i = 1; i < res.length; i++) {
    j = i - 1;

    //此处的j+1不应该写为i,因为每次的对比是相邻的两个对比,是变动的,纵观这几种排序,外部循环的值一般只参与控制和判断,不会深入到内部的循环中
    while (j >= 0 && res[j + 1] < res[j]) {
      temp = res[j];
      res[j] = res[j + 1];
      res[j + 1] = temp;
      j--;
    }
  }
  return res;
}
//改进插入排序,每次的交换稍显多余,可先用前一个数值覆盖,最后将需要插入的值赋到合适的地方
function insert2(array) {
  var len = array.length,
    i,
    j,
    tmp,
    result;

  result = array.slice(0);
  for (i = 1; i < len; i++) {
    tmp = result[i];
    j = i - 1;
    while (j >= 0 && tmp < result[j]) {
      result[j + 1] = result[j];
      j--;
    }
    result[j + 1] = tmp;
  }
  return result;
}
//改进插入排序,可优化的点在于可利用二分法查找应该插入的位置,减少遍历的时间
function insert3(array) {
  var len = array.length,
    i,
    j,
    tmp,
    low,
    high,
    mid,
    result;
  // 赋予数组副本
  result = array.slice(0);
  for (i = 1; i < len; i++) {
    tmp = result[i];
    low = 0;
    high = i - 1;
    while (low <= high) {
      mid = parseInt((low + high) / 2, 10);
      if (tmp < result[mid]) high = mid - 1;
      else low = mid + 1;
    }
    for (j = i - 1; j >= high + 1; j--) {
      result[j + 1] = result[j];
    }
    result[j + 1] = tmp;
  }
  return result;
}
//选择排序,其实在写插入排序改进版的时候就有这样的念头,这种交换效率的问题,因为冒泡的交换所持有的待比较的数并不确定
//不能像改进的插入排序那样,最后再选择插入位置。选择排序的核心有点像冒泡,一轮下来,将最大或者最小的放置在边缘
//(遍历时,保存当时的最大或者最小的序号)遍历结束后再进行交换
function selection(array) {
  let k,
    temp,
    res = array.slice(0);
  for (let i = 0; i < res.length; i++) {
    k = i;
    for (let j = i + 1; j < res.length; j++) {
      if (res[j] < res[k]) k = j; //保存当前最大的序号
    }
    if (k !== i) {
      //序号不一致则交换
      temp = res[i];
      res[i] = res[k];
      res[k] = temp;
    }
  }
  return res;
}

//快排:选取合适的中间数,比其小的放一侧,大的放另一侧,再迭代
function quick(arr) {
  if (arr.length <= 1) return arr;
  let mid,
    left = [],
    right = [];
  mid = arr.splice(Math.floor(arr.length / 2), 1);
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < mid) left.push(arr[i]);
    else right.push(arr[i]);
  }
  return quick(left).concat(mid, quick(right));
}

//希尔排序:选取不同的增量进行多次插入排序
//对于增量的选取,只要最后增量为1即可,回想希尔排序的实质,就是利用插入排序对有序序列效率高(运行次数小)这一点
//进行改进,让其在增量为1(插入排序增量一直为1)时,尽可能的有序,来减少遍历判断移动的次数
function shell(arr) {
  let i,
    j,
    temp,
    len = arr.length,
    gap,
    res = arr.slice(0);
  gap = parseInt(len / 2);

  while (gap > 0) {
    for (i = gap; i < len; i++) {
      temp = res[i];
      j = i - gap;
      while (j >= 0 && temp < res[j]) {
        res[j + gap] = res[j];
        j = j - gap;
      }
      res[j + gap] = temp;
    }
    gap = parseInt(gap / 2);
  }
  return res;
}
//归并排序,对无序数组拆分成若干个小数组(最小数组长度为1)进行排序,再把一个一个小数组合并,合并的时候根据大小顺序合并
function merge(arr) {
  function Sort(_arr) {
    if (_arr.length == 1) return _arr;
    let len = _arr.length,
      mid = parseInt(len / 2),
      left = _arr.slice(0, mid),
      right = _arr.slice(mid, len);

    return mergeArr(Sort(left), Sort(right));
  }
  function mergeArr(left, right) {
    let res = [];
    while (left.length || right.length) {
      //left和right肯定是已经排好序的,且left和right的长度最多相差1(sort函数中切分数组取的是中间mid),故分下三种情况
      if (left.length && right.length) {
        if (left[0] < right[0]) {
          res.push(left.shift());
        } else {
          res.push(right.shift());
        }
      } else if (left.length) {
        res.push(left.shift());
      } else {
        res.push(right.shift());
      }
    }
    return res;
  }
  return Sort(arr);
}

console.log(merge(unSortArr));
  • 对于堆排序,主要是利用大根堆这个数据结构(最大元素在堆顶),主要步骤为:
    1、调整二叉树,形成大根堆(一种完全二叉树)
    2、交换堆顶元素跟最后元素位置,最后元素弹出堆。然后继续回到1,调整堆,
    3、重复1、2步骤,直至所有元素出堆,得到的就是有序的序列。
  • 计数排序:根据取值的值域开辟连续的空间,遍历无需序列,依次累加相应的值次数,最后按值域大小依次输出,典型例子:统计分数(开辟分数值域数组,记录每个分数的学生个数,再按分数的高低输出)
  • 对于桶排序,其实是一种计数排序的优化,弱化了计数排序中空间的浪费,通俗些说,增大了桶,减少了桶的数量,即最小值到最大值之间每一个固定区域申请空间,尽量减少了元素值大小不连续情况下的空间浪费情况,之后每个桶再进行排序,排序的算法可以是之前的任何一种,桶排序算法的复杂度和稳定性,都根据选择的排序算法不同而不同。

    不同算法的时间空间复杂度分析

    参考链接八大排序算法时间和空间复杂度
全部评论

相关推荐

2024-12-23 10:55
已编辑
大连理工大学 Java
牛客930504082号:华子综测不好好填会挂的,而且填的时候要偏向牛马选项
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务