选择排序/快速排序/归并排序/计数排序

436 阅读5分钟

一.选择排序

时间复杂度: O(n²)

选择排序就是不停地选择最小的数放到最前面,然后再对后面的数做同样的事情

算法 : 先假设并取第一个数为最小值,然后与后面的每个数进行比较,并把比较后的最小值放到数组最前。然后对后面的数进行排序,对后面的数进行排序时把最小的数提到前面来;然后再对后面的数进行排序,对后面的数进行排序时再把最小的数提到前面,... ,直到后面的数只剩下两个,最后假设倒数第2个值为最小值,并与倒数第一个数比较大小即可。

let minIndex=(numbers)=>{ //minIndex函数找出并返回每次比较时最小值的下标
    let index=0 
    for(let i=1;i<numbers.length;i++){ //从数组第二个数开始遍历
        if(numbers[i]<numbers[index]){
            index = i //贪心算法,遍历时只要遇到比自己小的就赋给自己
        }
    }
    return index
}

let swap=(array,i,j)=>{
    let temp=array[i]
    array[i]=array[j]
    array[j]=temp
}

let sort=(numbers)=>{ 
    for(let i=0;i<numbers.length-1;i++)
    //设数组有五个数,因第四个数要跟第五个数比大小,故循环到数组的第四个数即可,所以是长度减1.
    //从数组第一个数开始循环,到数组的倒数第二个数(下标为numbers.length-2)结束.
    //最后一个数(下标为numbers.length-1)不用参与循环.
    //共比较大小numbers.length-1次
    //共选择numbers.length-2次最小值(第一个值和最后一个值不用选)
    {
        console.log(`----------`)
        console.log(`i: ${i} ,第${i+1}次比较`)
        let index=minIndex(numbers.slice(i))+i
        //minIndex返回的是当前减掉i个值的数组的最小值下标,要把减掉的下标加回来才是相对整个参与排序的数组的下标,所以在这里要加回i
        console.log(`最小值下标index为: ${index}`)
        console.log(`最小值min为: ${numbers[index]}`)
        if(index!==i){
            swap(numbers,index,i)
            console.log(`下标为${index}的值与下标为${i}的值交换位置`)
            console.log(numbers)
        }//整个参与排序的数组中,下标为index和下标为i的两个值交换位置
    }
    return numbers
}

PS:如果是从大到小排序,把minIndex里的numbers[i]<numbers[index]改成numbers[i]>numbers[index]即可。

二.快速排序

时间复杂度 : O(nlogn)

算法 : 每次随机指定一个没指定过的数(这个数就是基准),把比这个数小的熟放到这个数的前面,把比这个数大的数放到后面。

let quickSort=(numbers)=>{
    if(numbers.length<=1){
        return numbers
    }
    let pivotIndex=Math.floor(numbers.length/2) 
    //获取基准值下标 Math.floor():往下找一个最靠近的整数
    let pivot=numbers.splice(pivotIndex,1)[0]
    //获取基准值.把基准值从数组中删出来.
    let left=[]
    let right=[]
    for(let i=0;i<numbers.length;i++){ //遍历已经去掉了基准值的数组
        if(numbers[i]<pivot){
            left.push(numbers[i])
        }else{
            right.push(numbers[i])
        }
    }
    return quickSort(left).concat([pivot],quickSort(right))
    //对左边和右边进行快速排序,左边排好序的数组+基准值+右边排好序的数组
}

三.归并排序

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

算法 : 不设定基准值,把数组分成左右两边,再把左右两边的数组中的每个值单独拆成只有一个数的数组,再从少到多进行排序并合并。归并算法只会把两个排好序的数组合并起来。PS:归并排序中的每个数组均为已排好序的数组。

时间复杂度:O(nlogn)

let mergeSort = arr =>{
  if(arr.length===1){return arr}
  let left = arr.slice(0, Math.floor(arr.length/2))
  let right = arr.slice(Math.floor(arr.length/2))
  return merge(mergeSort(left), mergeSort(right))
}
let merge = (a, b) => {
  if(a.length === 0) return b
  if(b.length === 0) return a
  return a[0] > b[0] ?
     [b[0]].concat(merge(a, b.slice(1))) :
     [a[0]].concat(merge(a.slice(1), b))
}

归并算法具体图示:

四.计数排序

时间复杂度:O(n+max)

计数排序需要一个额外的哈希表做记录!

计数排序类似于给扑克牌排序,先把四张同大小的扑克牌放在一起,再将牌堆按大小排序即可.

算法:先把所有值和其出现的次数记在哈希表中,在记的同时找出已出现的数的最大值。然后再从0开始到最大值遍历哈希表,如果有相应的值就按已记录的次数打印到一个新的数组中,没有则跳过该值。(比如:有一个数组[2,6,1,0,9,4,8,11,15,12],则遍历0,1,2,...,15)

思路:
1.用一个哈希表做记录.
2.发现数字n就记n:1,如果再次发现n就加上1变成n:2,以此类推.
3.最后把哈希表中的key全部打出来,再按value的值决定打印key的次数.假设有n:4,则n需要打印4次.

let countSort=(numbers)=>{
    let hashTable={}
    let max=0
    let result=[] //建立新的哈希表,最大值和需要存放结果的数组
    for(let i =0;i<numbers.length;i++){ //开始遍历数组
        if(!numbers[i] in hashTable){ 
            hashTable[numbers[i]]=1
        }else{
            hashTable[numbers[i]]+=1
        } //将未在哈希表中存在的数记录进哈希表并设定出现次数为1,不是第一次出现就再加1.
        if(numbers[i]>max){
            max=numbers[i]
        }
    } //遍历数组并记录进哈希表中,并找出数组中的最大值.得到哈希表和max.
    for(let j=0;j<=max;j++){ //开始遍历哈希表,j代表哈希表中的每个key
        if(j in hashTable){
            for(let i=0;i<hashTable[j];i++){ //再次遍历哈希表,要拿到j的value才能决定打印几次j
                result.push(j)
            }
        }
    }
}