公共模块

export enum Compare {
  LESS =  -1,
  EQUAL = 0,
  GREATER = 1
}
 
// 比较大小的方法
export function defaultCompareFunction<T>(a: T, b: T){
  if(a === b){
    return Compare.EQUAL
  }
  return a > b ? Compare.GREATER : Compare.LESS;
}
 
// 数组元素交换的方法
export function swap<T>(arr:Array<T>, beginIndex:number , endIndex:number){
    [arr[beginIndex], arr[endIndex]] = [arr[endIndex], arr[beginIndex]]
}

冒泡排序

比较所有相邻的两个项,如果第一个比第二个大,则交换它们.元素向上移动到正确的位置,就好像气泡升至表面一样,冒泡排序因此得名

let arr = [9,5,4,5,4,8,4,2,45,1,5,41];
 
import {defaultCompareFunction, Compare ,swap} from "./common"
 
 
export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction){
    let len = arr.length;
    for (let i = 0; i < len; i++) {
        // 冒上去的元素都不需要在做比较,所以这里的
        for (let j = 0; j < len - 1 - i; j++) {
            // 前一个元素
            const before = arr[j];
            // 后一个元素
            const after = arr[j + 1];
            // 如果前一个元素比后一个元素的大,交换位置
            if(compareFn(before, after) === Compare.GREATER){
                swap(arr, j , j + 1);
            }
        }
    }
}
sortArray(arr)
console.log(arr);

性能差: 需要双循环

  • 外循环保证循环次数足够
  • 内循环调换相邻元素的位置

注意点:

  • 已经冒到水面的元素(已排好的元素),不需要在做比较 [每次必定会有一个最大值到最后]
  • 是前一个元素和后一个元素的比较大小,所以外层循环只是为了保证循环次数

插入排序

将当前数组的划分成两部份,前部分为已排序部分 , 后部分为未排序部分,然后将后部分数据一个一个的向前部分插入

let arr = [9,5,4,5,4,8,4,2,45,1,5,41];
 
import {defaultCompareFunction, Compare ,swap} from "./common"
 
 
function sortArray<T>(arr:Array<T>, compareFn:Function = defaultCompareFunction){
   for (let i = 0; i < arr.length; i++) {
      let current = i;
      // 注意循环从i开始,0结束,注意要从后面开始交换,这样子才能做到一步一步的向前挪
      // 如果从0开始,向后的话 会把当前数据移动回来
      for (let j = i; j >= 0; j--) {
        const external = arr[current];
        const within = arr[j];
        // 比较外层的值是否小于当前值
        if(compareFn(external, within) === Compare.LESS){
            // 交换位置
            swap(arr, current, j);
            // 并更新当前min指针
            current = j;
        }
      }
   }
}
 
sortArray(arr)
console.log(arr)

第一个元素默认有序

后面的元素需要从有序部分的尾部插入

注意交换位置后要追踪当前元素

选择排序

找到数据中最小值并将其放置在第一位,接这找第二小的值,放在第二位 ....

let arr = [9,5,4,5,4,8,4,2,45,1,5,41];
 
import {defaultCompareFunction, Compare ,swap} from"./common"
 
function sortArray<T>(arr:Array<T>, compareFn:Function = defaultCompareFunction){
    for (let i = 0; i < arr.length; i++) { 
        // 记录当前为最小下标
        let min = i;
        for (let j = i + 1; j < arr.length; j++) {
        const external = arr[min];
        const within = arr[j];
        // 比较最小的与当前大小 ,如果最小的值比
        if(compareFn(external, within) === Compare.GREATER){
            // 将最小值转为当前下标 
            min = j;
        }
    }
    // 如果最小下标不等于当前下标 ,就交换位置
    if(min !== i)swap(arr, min, i);
  }
}
 
sortArray(arr);
console.log(arr)

定义最小的位置,然后记录下其下标

外层循环代表着顺序(第几小的数)

找到最小下标后,与外层的下标交换(不相同的情况,相同交换没有意义)

归并排序

归并使用的是分而治之的思想,将一个大数组分成n个小数组,当分成一个元素的时候就相当于默认有序,然后将有序的数组进行合并排序,最后达到排序的效果

import {defaultCompareFunction, Compare } from "./Common"

// 拆分
export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction) {
    const len:number = arr.length;
    if(len > 1){
        // 算出中位数
        let middle = Math.floor(len / 2);
        // 拆分左边  注意slice分割数组是不会改变数组本身的
        let left = sortArray(arr.slice(0, middle), compareFn)
        // 拆分右边
        let right = sortArray(arr.slice(middle, len), compareFn)
        // 将左右俩个数组归并, 并排序, 然后返回回去
        arr = mergeSort(left , right , compareFn);
    }
    return arr;
}


// 归并
function mergeSort<T>(leftArr: Array<T>,rightArr: Array<T>,  compareFn: Function = defaultCompareFunction):Array<T> {
    let result = [];
    // 左边数组指针
    let left = 0;
    // 右边数组指针
    let right = 0;
    // 不管左右数组那边越界都结束循环
    while(left < leftArr.length && right < rightArr.length){
        // 如果左边小(大)就将左边丢入数组中,并左数组指针+1, 反之亦然
        // 这里注意一定要指针变化
        result.push(compareFn(leftArr[left], rightArr[right]) === Compare.LESS ? leftArr[left++] : rightArr[right++])
        // if(compareFn(leftArr[left], rightArr[right]) === Compare.LESS){
        //     result.push(leftArr[left]);
        //     left++;
        // }else{
        //     result.push(rightArr[right]);
        //     right++;
        // }
    }
    return result.concat(left < leftArr.length ? leftArr.slice(left) : rightArr.slice(right));
}

先切割数组

后合并并排序这个两个数组

注意的是这个里面涉及到一个有序的数组的排序

排序使用双指针,然后如果左指针的值(对应做边数组)小于右指针的值(对应右边数组),那么就添加左边数组的值到新数组中,并且左指针+1,但是右指针不动,直到最后那边指针出界,但是两个都是有序的数组,所以直接拼接上去即可

快速排序

快速排序也是使用了分而治之的思想,但是并不会真的切分(指针上的区分)

import {defaultCompareFunction, Compare ,swap} from "./Common"


export function sortArray<T>(arr: Array<T>, compareFn: Function = defaultCompareFunction){
    quickSort(arr, 0, arr.length - 1, compareFn)
}

function quickSort<T>(arr: Array<T>, left:number, right:number, compareFn: Function) {
    // 最小数组和最大数组的中界线
    let index:number;
    // 当前数组长度大于1个
    if(arr.length > 1){
        // 获取划分的界限  左边为最小值  右边为最大值
        index = partition(arr, left ,right, compareFn);
        if(left < index - 1){
            // 快排左部分
            quickSort(arr, left, index - 1, compareFn);
        }
        if(index < right){
            // 快排右部分
            quickSort(arr, index, right, compareFn)
        }
    }
    //当前数组长度等于1  一个元素是有序的  直接返回
    return arr;
}


// 划分
function partition<T>(arr:Array<T>, left:number, right:number, compareFn:Function):number {
    // 选择主元的位置 :这里选取数组中的中间值
    const pivotIndex = Math.floor((left + right) / 2)
    //取主元 具体值
    const pivot = arr[pivotIndex];
    // 当左右两边交叉的时候结束
    while(left <= right){
        // 左边值大于(等于)主元的时候结束循环   =  得到左边大于主元的值
        while (compareFn(arr[left] , pivot) === Compare.LESS){
            left++;
        }
        // 右边值中有值小于(等于)主元的时候结束循环   =  得到右边小于主元的值
        while (compareFn(arr[right] , pivot) === Compare.GREATER){
            right--;
        }
        // 当left小于right 就交换
        if(left <= right){
            // 将元素进行交换
            swap(arr, left , right);
            // 左指针 +1 缩小范围
            left++;
            // 右指针 -1 缩小范围
            right--;
        }
    }
    return left;
}

let arr = [1,2,54,8,45,7, 45,9,8,452,35,754,127,6,21,124,454]
sortArray(arr)

// @ts-ignore
console.log(arr)

从数组中选择一个值作为主元,也就是数组中间的那个值

创建两个指针,左边指向数组的第一个值,右边指向数组的最后一个值.移动左边的指针直到找到比主元小的值,然后移动右边的指针,找到比主元小的值,然后交换位置,重复该过程,直到左指针超过了右指针.这个过程将使比主元小的值集中在主元的左边,比主元大的值集中在主元的右边.这一步叫做划分

算法对划分后的小数组,继续上面的步骤,直到数组完全有序

posted on 2020-12-06 17:46  人生之外的路途  阅读(136)  评论(0编辑  收藏  举报