排序算法

141 阅读2分钟

二分查找

function binarySearch(data, arr, start, end){
    if(start < end){
    	return -1
    }
    let mid = Math.floor((start + end)/2)
    if(data === arr[mid]){
    	return mid
    }else if(data < arr[mid]){
    	return binarySearch(data, arr, start, mid - 1)
    }else{
    	return binarySearch(data, arr, mid + 1, end)
    }
}

1、冒泡排序 O(n^2)

算法步骤:

  • 比较相邻元素。如果第一个比第二个大,就交换
  • 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  • 针对所有的元素重复以上的步骤,除了最后一个;
  • 重复步骤1~3,直到排序完成。

bubbleSort1

function bubbleSort(arr){
    let len = arr.length
    for(let i=0;i<len;i++){ // 每一轮移动一个最大的
        for(let j=0;j<len-1-i;j++){
            if(arr[j] > arr[j+1]){
            	let temp = arr[j+1]
                arr[j+1] = arr[j]
                arr[j] = temp;
            }
        }
	}
    return arr
}

优化改进,利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。

function bubbleSort2(arr){
    let low = 0
    let high = arr.length - 1
    let temp, j;
    while(low < high) {
    	for(j=low;j<high;j++){
            if(arr[j] > arr[j+1]){
            	temp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = temp
            }
          }
          --high
          for(j=high; j>low;j--){
            	if(arr[j] < arr[j-1]){
                    temp = arr[j]
                    arr[j] = arr[j-1]
                    arr[j-1] = temp
                }
            }
            ++low
      }
      return arr
}

2、选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

function selectionSort(arr){
	let len = arr.length
    let minIndex, temp
    for(let i=0;i<len -1;i++){
    	minIndex = i
        for(let j = i+1;j<len;j++){
        	if(arr[j] < arr[minIndex]){
            	minIndex = j // 将最小数的索引保持
            }
        }
        temp = arr[i]
        arr[i] = arr[minIndex]
        arr[minIndex] = temp
    }
    return arr
}

3、插入排序

算法描述:

  • 从第一个元素开始,该元素可以认为已经被排序;
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  • 将新元素插入到该位置后;
  • 重复步骤2~5。
function insertionSort(arr){
    if(Array.isArray(arr)){
    	for(let i=1;i<arr.length;i++){
            let key = arr[i]
            let j = i - 1
            while(j >=0 && arr[j] > key){
            	arr[j+1] = arr[j]
                j--
            }
            arr[j+1] = key
        }
        return arr
    }
}

改进插入排序,查找插入位置使用二分查找的方式

function binaryInsertionSort(arr){
	if(Array.isArray(arr)){
    	for(let i = 1; i<arr.length; i++){
            let key = arr[i]
            let left = 0
            let right = i - 1 
            while(left <= right){
            	let middle = parseInt((left + right) / 2)
                if(key < arr[middle]){
                	right = middle -1
                } else {
                   left = middle + 1 
                }
            }
            // 最后一个的情况
            for(let j = i - 1; j>= left; j--){
            	arr[j+1] = arr[j]
            }
            arr[left] = key
        }
        return arr
    }
}

4、归并排序(分治法)

算法描述:

  • 把长度为n的输入序列分成两个长度为n/2的子序列;
  • 对这两个子序列分别采用归并排序;
  • 将两个排序好的子序列合并成一个最终的排序序列。
function mergeSort(arr){
	let len = arr.length
    if(len < 2){
    	return arr
    }
    let middle = Math.floor(len / 2)
    let left = arr.slice(0, middle)
    let right = arr.slice(middle)
    return merge(mergeSort(left), mergeSort(right))
}

function merge(left, right){
	let result = []
    while(left.length && right.length){
    	if(left[0] <= right[0]){
        	result.push(left.shift())
        } else {
        	result.push(right.shift())
        }
    }
    while(left.length) {
    	result.push(left.shift())
    }
    while(right.length){
    	result.push(right.shift())
    }
    return result
}

5、快速排序

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 "基准"(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
/*方法说明:快速排序
@param  array 待排序数组*/
//方法一
function quickSort(arr, left, right){
    if(left >=right){
        return arr
    }
    let temp = arr[left] // 基准值
    let i = left
    let j = right
    while(i !==j){
    	// 找右边小于基准的值
        while(arr[j]>=temp && i<j){
            j--
        }
        // 找左边大于基准的值
        while(arr[i] <= temp && i<j){
            i++
        }
        // 互换
        if(i<j){
            let t = arr[i]
            arr[i] = arr[j]
            arr[j] = t
        }
        // 继续直到重合
    }
    // 把基准值放到i 的位置上
    arr[left] = arr[i]
    arr[i]= temp
    // 递归左边和右边
    quickSort(arr, left, i-1)
    quickSort(arr, i+1, right)
    return arr
}
相比冒泡排序,每次交换是跳跃式的, 复杂度 平均下来是 nlog(n)。最坏情况O(n2)

//  缺点:浪费存储空间,写法简单
    function quickSort2(array) {
      if (array.length < 2) {
        return array;
      }
      const target = array[0];
      const left = [];
      const right = [];
      for (let i = 1; i < array.length; i++) {
        if (array[i] < target) {
          left.push(array[i]);
        } else {
          right.push(array[i]);
        }
      }
      return quickSort(left).concat([target], quickSort(right));
    }