数组排序

242 阅读4分钟

数组排序

思考

  1. 数据结构中稳定的排序算法有哪些?不稳定的排序算法有哪些?

冒泡排序

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

function bubleSot(array){
    const len = array.length
    if(len<2) return array
    
    for(let i = 0; i<len; i++){ // 相当于一个标记指针向前走
        for(let j = 0; j < i; j++){ // 相当于一个指针重复遍历已经排好序的元素与标记指针元素比较交换位置
            if(array[j] > array[i]){
                let temp = array[j]
            	array[j] = array[i]
                array[i] = temp
            }
        }
    }
    return array
}

bubleSot([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

快速排序

  1. 首先设定一个分界值,通过该分界值将数组分成左右两部分
  2. 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
  3. 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
  4. 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了
function quickSort(array){
    // 定义快速排序的方法
    let quick = function(arr){
        if(arr.length <= 1) return arr
        let len = arr.length
        let index = Math.floor(len >> 1) // 移位运算,相当于除以2
        let pivot = arr.splice(index,1)[0] //取中间值,splice()会改变原数组
        // 此处不能用arr[index]获取中间值,会造成循环调用走不出去导致栈溢出
        let left = [] // 收集数组中小于等于中间值的元素
        let right = [] // 收集数组中大于中间值的元素
        for(let i=0; i<len; i++){
            if(arr[i] > pivot){
                right.push(arr[i]) // 收集数组中大于中间值的元素
            }else if(arr[i] <= pivot){
                left.push(arr[i]) // 收集数组中小于等于中间值的元素
            }
        }
        return quick(left).concat([pivot],quick(right)) // 循环调用quick将收集的数组整合就可获取排序好的数组
    }
    
    const result = quick(array)
    return result
}

quickSort([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动

function insertSort(array){
    let len = array.length
    let current // 获取当前值
    let prev // 相当于定义的移动指针,去遍历已经排好序的元素。指向已经排好序的元素最后一位的下标
    for(let i = 1; i<len; i++){
        current = array[i] // 获取当前值
        prev = i - 1 // 指向当前元素的前一个元素下标
        // 核心循环,当prev大于等于0时,比较两个数的大小
        while(prev>=0 && array[prev] > current){
            array[prev + 1] = array[prev] // 满足条件,当前prev + 1指向的元素重新赋值,赋值为array[prev]
            prev-- // 指针向前移位,重复比较
        }
        // 循环执行完,比current大的数都向前位移一位,将current放入prev + 1位置,完成排序
        array[prev + 1] = current
    }
    return array
}

insertSort([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

function selectSort(array){
    let len = array.length
    let temp // 记录当前下标元素值
    let minIndex // 记录后续遍历元素最小值的下标
    for(let i = 0; i<len - 1; i++){
        minIndex = i // 从零开始遍历
        for(let j = i + 1;j<len;j++){ // 指针向后移位
            if(array[j] <= array[minIndex]){ // 找出后续遍历元素最小值的下标
                minIndex = j // 找到小的就重新赋值
            }
        }
        // 下标元素值互换
        temp = array[i]
        array[i] = array[minIndex]
        array[minIndex] = temp
    }
    return array
}

selectSort([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

堆排序

​ 堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。

前置须知条件

  1. 下标为i的节点的父节点下标:(i - 1)/ 2 [整数除法]
  2. 下标为i的节点的左孩子下标:i*2+1
  3. 下标为i的节点的右孩子下标:i*2+2

function heap_sort(array){
    let len = array.length
    // 交换位置函数
    function swap(i,j){
        let temp = array[i]
        array[i] = array[j]
        array[j] = temp
    }
    /**
    *维护大顶堆函数
    *start代表父节点的下标
    *end代表数组的长度
    */ 
    function max_heapify(start, end){
        let dad = start
        let son = dad*2 + 1 // 获取子节点下标
        if(son >= end) return 
        // 找出左右子节点的最大值
        if(son+1 < end && array[son] < array[son+1]){
            son++
        }
        // 满足条件互换位置,互换位置后需要重新维护大顶堆
        if(array[dad] <= array[son]){
            swap(dad,son)
             max_heapify(son,end)
        }
    }
    
    // 从Math.floor(len/2) - 1,即最后一个元素的父节点开始维护大顶堆
    for(var i = Math.floor(len/2) - 1; i>=0; i--){
        max_heapify(i,len)
    }
    
    for(let j=len-1;j>0;j--){
        swap(0,j) // 将最大的元素换到数组末尾,下次循环就不带它玩了
        max_heapify(0,j) // 从下标为零的地方重新维护大顶堆,然后继续循环,循环结束数组排序成功
    }
    return array
}

heap_sort([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
function mergeSort(array){
    function merge(leftArr, rightArr) {
      let temp = [];
      while (leftArr.length > 0 && rightArr.length > 0) {
        if (leftArr[0] < rightArr[0]) {
          temp.push(leftArr.shift());
        } else {
          temp.push(rightArr.shift());
        }
      }
      return temp.concat(leftArr).concat(rightArr);
    }
    if (array.length === 1) return array;
    const midIdx = Math.floor(array.length / 2);
    return merge(mergeSort(array.slice(0, midIdx)), mergeSort(array.slice(midIdx)));     
}

mergeSort([1,6,7,8,3,4,5,6,3]) //[1, 3, 3, 4, 5, 6, 6, 7, 8]

总结

image-20230224163251406.png