各种排序算法的图解、js实现以及优缺点

一、插入排序

最普通的排序算法, 从数组下标1开始每增1项排序一次,越往后遍历次数越多;

已知一组升序排列数据a[1]、a[2]、……a[n],一组无序数据b[1]、 b[2]、……b[m],需将二者合并成一个升序数列。首先比较b[1]与a[1]的值,若b[1]大于a[1],则跳过,比较b[1]与a[2]的值,若b[1]仍然大于a[2],则继续跳过,直到b[1]小于a数组中某一数据a[x],则将a[x]~a[n]分别向后移动一位,将b[1]插入到原来 a[x]的位置这就完成了b[1]的插入。b[2]~b[m]用相同方法插入。(若无数组a,可将b[1]当作n=1的数组a)

原理图:

插入排序

// 插入排序 从下标1开始每增1项排序一次,越往后遍历次数越多
function sort1(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 sort2(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 sort3(array){
  var len = array.length, gap = parseInt(len/2), 
      i, j, tmp, result;
  // 复制数组
  result = array.slice(0);
  while(gap > 0){
    for(i = gap; i < len; i++){
      tmp = result[i];
      j = i - gap;
      while(j>=0 && tmp < result[j]){
        result[j + gap] = result[j];
        j = j - gap;
      }
      result[j + gap] = tmp;
    }
    gap = parseInt(gap/2);
  }
  return result;
}

 

四、冒泡排序

很常见很容易理解的排序算法, 排序思路:遍历数组,每次遍历就将最大(或最小)值推至最前。越往后遍历查询次数越少, 跟插入排序刚好相反。

原理图:

冒泡排序

// 冒泡排序 每次将最小元素推至最前
function sort4(array) {
  var len = array.length,
  i, j, tmp, result;
  result = array.slice(0);
  for (i = 0; i < len; i++) {
    for (j = len - 1; j > i; j--) {
      if (result[j] < result[j - 1]) {
        tmp = result[j - 1];
        result[j - 1] = result[j];
        result[j] = tmp;
      }
    }
  }
  return result;
}

优点:稳定,快;

缺点:比较次数不一定,比较次数越少,插入点后的数据移动越多,特别是当数据总量庞大的时候,但用链表可以解决这个问题。

五、改进冒泡排序

对上述冒泡排序的一种优化, 优化思路:当一次遍历前后数组不产生变化时,说明该数组已经有序,结束排序。

原理图:

改进冒泡排序

// 如果在某次的排序中没有出现交换的情况,
// 那么说明在无序的元素现在已经是有序了,就可以直接返回了。
function sort5(array) {
  var len = array.length,
  i, j, tmp, exchange, result;

  result = array.slice(0);
  for (i = 0; i < len; i++) {
    exchange = 0;
    for (j = len - 1; j > i; j--) {
      if (result[j] < result[j - 1]) {
        tmp = result[j];
        result[j] = result[j - 1];
        result[j - 1] = tmp;
        exchange = 1;
      }
    }
    if (!exchange) return result;
  }
  return result;
}

六、快速排序

快速排序在诸多算法排序中可能不是最好的, 但个人认为在JS语言实现中是最快的!以前公司项目中对比过二分插入排序优化冒泡排序快速排序的JS实现执行时间,几千条数据的数组在firefox下快速排序的速度比冒泡、插入排序快3至4秒(数组元素为复杂的对象,根据对象某一属性值排序)。

原理图:

快速排序

//(1)在数据集之中,选择一个元素作为"基准"(pivot)。
//(2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
//(3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
function sort6(array) {
  var tmp_array = array.slice(0), result,
  quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
  };
  result = quickSort(tmp_array);
  return result;
}

优点:极快,数据移动少;

缺点:不稳定。

七、选择排序

实现思路跟冒泡排序差不多, 可以说是冒泡排序的衍生版本;

原理图:

选择排序

// 在无序区中选出最小的元素,然后将它和无序区的第一个元素交换位置。
// 原理跟冒泡排序一样,算是冒泡的衍生版本
function sort7(array) {
  var len = array.length,
  i, j, k, tmp, result;

  result = array.slice(0);
  for (i = 0; i < len; i++) {
    k = i;
    for (j = i + 1; j < len; j++) {
      if (result[j] < result[k]) k = j;
    }
    if (k != i) {
      tmp = result[k];
      result[k] = result[i];
      result[i] = tmp;
    }
  }
  return result;
}

优点:移动数据的次数已知(n-1次);

缺点:比较次数多。

八、堆排序

因为js模拟二叉树比较麻烦,所以堆排序的优势用js语言无法体现, 相对而言C语言的链表在实现上更能表现堆排序,堆排序或许更适合指针类的计算机语言。本文注重图解各排序的基本思路,所以该排序的具体实现没讲太细, 如想深究实现细节请看:堆排序及其分析

原理图:

1.调整二叉树,形成大根堆(子节点都比父节点小)。

堆排序

2.交换堆第一元素跟最后元素位置,最后元素弹出堆。然后继续回到1,调整堆。

堆排序

2.交换堆第一元素跟最后元素位置,最后元素弹出堆。然后继续回到1,调整堆。

堆排序

// 1) 初始堆:将原始数组调整成大根堆的方法——筛选算法:子节点都比父节点小
// 2) 堆排序: 每次将堆顶元素与数组最后面的且没有被置换的元素互换。
// 参考代码: http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
function sort8(array) {
  var result = array.slice(0);

  function swap(array, i, j) {
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }

  function maxHeapify(array, index, heapSize) {
    var iMax, iLeft, iRight;
    while (true) {
      iMax = index;
      iLeft = 2 * index + 1;
      iRight = 2 * (index + 1);

      if (iLeft < heapSize && array[index] < array[iLeft]) {
        iMax = iLeft;
      }

      if (iRight < heapSize && array[iMax] < array[iRight]) {
        iMax = iRight;
      }

      if (iMax != index) {
        swap(array, iMax, index);
        index = iMax;
      } else {
        break;
      }
    }
  }

  function buildMaxHeap(array) {
    var i, iParent = Math.floor(array.length / 2) - 1;

    for (i = iParent; i >= 0; i--) {
      maxHeapify(array, i, array.length);
    }
  }

  function sort(array) {
    buildMaxHeap(array);

    for (var i = array.length - 1; i > 0; i--) {
      swap(array, 0, i);
      maxHeapify(array, 0, i);
    }
    return array;
  }

  return sort(result);
}

九、归并排序

很容易理解且执行效率一般(js实现)的排序, 排序思路:将无序的数组 拆成N部分进行有序处理,然后合并;

原理图:

归并排序

// 合并排序:将无序的数组 拆成N部分进行有序处理,然后合并;
// 参考代码: https://gist.github.com/paullewis/1982121
function sort9(array) {
  var result = array.slice(0);

  // 递归调用合并函数
  function sort(array) {
    var length = array.length,
    mid = Math.floor(length * 0.5),
    left = array.slice(0, mid),
    right = array.slice(mid, length);

    if (length === 1) {
      return array;
    }
    return merge(sort(left), sort(right));
  }

  // 合并 两有序的数组
  function merge(left, right) {
    var result = [];

    while (left.length || right.length) {

      if (left.length && right.length) {

        if (left[0] < right[0]) {
          result.push(left.shift());
        } else {
          result.push(right.shift());
        }

      } else if (left.length) {
        result.push(left.shift());
      } else {
        result.push(right.shift());
      }
    }
    return result;
  }

  return sort(result);
}

后话:

欢迎点赞评论关注~

猜你喜欢

转载自blog.csdn.net/weixin_38131507/article/details/100512025