排序

作者: Tlion | 来源:发表于2019-04-09 19:36 被阅读0次

    分析排序算法

    执行效率

    1. 最好、最坏、平均时间复杂度

    数据的有序程度不一样,对排序算法时间复杂度的影响很大,所以需要对最好、最坏情况复杂度有了解

    2. 时间复杂度的系数、常数

    当两个算法的时间复杂度是一个阶层的,这时就需要考虑它们的系数或者常数项,以此来判断快慢

    3. 比较次数和交换次数

    基于比较的排序算法基本都有比较和交换的操作,计算时间复杂度的时候,需要把这两种操作计算进去

    内存消耗

    内存消耗可以用空间复杂度来表示,对于空间复杂度为 O(1) 排序算法,称其为 原地排序

    稳定性

    针对排序算法,还有另外一个指标,即算法的稳定性。
    算法的稳定性指的是,排序关键字相同的两个项,在排序之前和之后的相对位置是否变化,若相对位置没有变化,则是稳定排序算法,否则是非稳定排序算法

    O(n2) 复杂度排序算法

    算法 原地排序 稳定排序 最好时间复杂度 最坏时间复杂度 平均时间复杂度
    冒泡排序 O(n) - 数组有序 O(n2) - 数组逆序 O(n2)
    插入排序 O(n) - 数组有序 O(n2) - 数组逆序 O(n2)
    选择排序 O(n2) O(n2) O(n2)

    1. 冒泡排序

    思想:每次比较相邻的两个数,满足交换条件则交换两个数,所以每次都能将未排序的最大(小)的数冒泡到末尾,循环 n 次后,数组已经有序

    可优化点:当某次冒泡中没有发生数据交换时,此时数组已经有序,不需要再进行后续的冒泡比较了

    function quickSort(arr) {
      if(!arr || arr.length < 2) {
        return arr;
      }
    
      for (let i = 0; i < arr.length; i++) {
    
        // j 和 j + 1 比较,最后一个元素就是 j + 1,所以 - 1
        const top = arr.length - i - 1;
        let isSorted = true;
        for(let j = 0; j < top; j++) {
          if (arr[j] > arr[j+1]) {
            const temp = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = temp;
            isSorted = false;
          }
        }
        if (isSorted) {
          break;
        }
      }
      return arr;
    }
    

    2. 插入排序

    思想: 将数组分为 已排序区间和未排序区间,每次从将未排序区间第一个元素插入到已排序区间的正确位置上

    // 使用 js 数组方法
    function insertSort(arr) {
      for (let i = 1; i < arr.length; i++) {
        const ordered = arr.slice(0, i);
        const value = arr[i];
        const index = ordered.findIndex(val => val > value);
        if (index !== -1) {
          arr.splice(i, 1);
          arr.splice(index, 0, value);
        }
      }
      return arr;
    }
    
    // 不使用 js 数组方法
    function insertSort(arr) {
      if (arr.length < 2) {
        return arr;
      }
    
      for (let i = 1; i < arr.length; i++) {
        const value = arr[i];
        let j = i;
        while (j--) {
          if (arr[j] > value) {
            arr[j + 1] = arr[j];
          } else {
            break;
          }
        }
        arr[j + 1] = value;
      }
      return arr;
    }
    

    3. 选择排序

    思想: 也是将数组分为 已排序区间和未排序区间,每次从未排序区间中找到最小值,和未排序区间第一个值做交换

    function selectSort(arr) {
      if (arr.length < 1) {
        return arr;
      }
    
      let orderedTrail = 0;
      while (orderedTrail < arr.length){
        let j = orderedTrail;
        let min = arr[j];
        let minIndex = j;
        while (j < arr.length) {
          if (arr[j] < min) {
            min = arr[j];
            minIndex = j;
          }
          j += 1;
        }
        arr[minIndex] = arr[orderedTrail];
        arr[orderedTrail++] = min;
      }
      return arr;
    }
    

    O(nlogn) 复杂度排序算法

    算法 原地排序 稳定排序 最好时间复杂度 最坏时间复杂度 平均时间复杂度
    归并排序 否 - O(n) O(nlogn) O(nlogn) O(nlogn)
    快速排序 O(O(nlogn)) - 分区比较均衡 O(n2) - 分区及其不均衡 O(O(nlogn))

    1. 归并排序

    思想: 把数组分为前后两个部分,分别对两个部分进行排序,然后将排好序的两部分合并,则数组有序( 分而治之的思想 )

    function mergeSort(arr) {
      if (!arr || !Array.isArray(arr) || !arr.length) {
        return [];
      }
    
      const merge = function(left, middle, right) {
        const tempArr = Array(right - left);
        let index = 0;
        let leftIndex = left;
        let rightIndex = middle;
    
        while (leftIndex < middle && rightIndex < right) {
          const leftValue = arr[leftIndex];
          const rightValue = arr[rightIndex];
          if (leftValue <= rightValue) {
            tempArr[index++] = leftValue;
            leftIndex += 1;
          } else {
            tempArr[index++] = rightValue;
            rightIndex += 1;
          }
        }
    
        let start = leftIndex < middle ? leftIndex : rightIndex;
        let end = leftIndex < middle ? middle : right;
    
        while (start < end) {
          tempArr[index++] = arr[start++];
        }
    
        end = right - left;
        for (start = 0; start < end; start++) {
          arr[start + left] = tempArr[start];
        }
      }
      const sort = function(left, right) {
        if (left >= right) {
          return;
        }
        const middle = ((right + left) / 2) >> 0;
        sort(left, middle);
        sort(middle + 1, right);
        merge(left, middle, right);
      }
      sort(0, arr.length)
      return arr;
    }
    

    时间复杂度分析: T(n) = T(n/2)(左伴部分的时间复杂度) + T(n/2)(右半部分的时间复杂度) + K(合并时的时间复杂度),可以以此推出 T(n) = 2^k*T(n/2^k) + k*n,当 2^k == 1时,k = logn,带入即可得 T(n) = nlogn + Cn

    2. 快速排序

    思想: 取区间任意位置为标准值,遍历区间,比标准值小的都放在左边,比标准值大的都放在右边,以此类推,直到只有一个元素,此时数组有序( 分而治之的思想 )

    function quickSort(arr) {
      if (!arr || !Array.isArray(arr) || !arr.length) {
        return [];
      }
    
      const swap = function(i ,j) {
        const temp = arr[j];
        arr[j] = arr[i];
        arr[i] = temp;
      }
    
      const partial = function(left, right) {
        const reference = arr[right];
        let i = left;
        for (let j = left; j < right; j++) {
          if (arr[j] < reference) {
            swap(i, j)
            i += 1;
          }
        }
        swap(i, right);
        return i;
      }
    
      const sort = function(left, right) {
        if (left >= right) {
          return;
        }
        
        border = partial(left, right);
        sort(left, border - 1);
        sort(border + 1, right);
      }
    
      sort(0, arr.length - 1);
      return arr;
    }
    

    注意: 由于在交换元素的过程中可能会破坏数组项的原本的先后顺序,所以快速排序是非稳定排序

    O(n) 复杂度排序算法

    1. 桶排序

    思想: 根据数据范围,分几个桶,将对应区间的数据放到对应的桶中进行排序,然后根据桶的顺序合并每个桶中的数据

    桶排序

    复杂度分析: 当划分为 m个桶时,如果划分均匀,则每个桶有 k = n/m 个项,若对每个桶采用快速排序,则复杂度为 O(k*logk),m 个桶的总复杂度为 O(m*k*logk),又有 k = n/m,则为 O(n*log(n/m)),可见,当桶的个数越接近项的个数,时间复杂度越接近 O(n)

    2. 基数排序

    思想: 根据数据的每一位分别进行排序,需要使用稳定的排序算法

    相关文章

      网友评论

          本文标题:排序

          本文链接:https://www.haomeiwen.com/subject/ebiiiqtx.html