一、求任意长度数组的最小数
1.1求两个数中最小的数
let minOf2 = ([a,b]) => a < b ? a : b
//调用:
minOf2.call(null,[1,2]) //call的第一个参数是this,设置为null
复制代码
JS有现成的API:Math.min
Math.min(1,2) //1
Math.min.call(null,1,2)
Math.min.apply(null,[1,2])//apply和call调用的区别,前者后半部分是一个数组
复制代码
- Math看来像是和Object一样的构造函数,但其实只是一个普通对象
- 这是唯一一个特例:首字母大写的是构造函数
1.2求三个数中最小的数
let minOf3 = ([a,b,c]) => {
return minOf2([minOf2([a,b]),c])
}
//推理求四个数中的最小的数字
let minOf4 = ([a,b,c,d]) => {
return minOf2([a,minOf3([b,c,d])])
}
//任意长度数组求最小值,都可以通过minOf2来实现
复制代码
1.3求任意长度数组的最小数
let min = (numbers) => {
if(numbers.length > 2 ){
return min([numbers[0],min(numbers.slice(1))] )
}
else{
return Math.min.apply(null,numbers)
}
}
//上述就是递归
复制代码
递归的特点:
-
函数不停的调用自己,每次调用的参数略有不同
-
当满足某个简单条件时,则会实现一个简单的调用
-
最终算出结果
如何理解递归:
- 代入法理解递归
- 调用栈理解递归
二、选择排序
2.1递归的思路
2.1.1长度为2的数组排序
let sort2 =([a,b]) =>
a < b ? [a,b] :[b,a]
复制代码
2.1.2长度为3的数组排序
let minIndex = (numbers) => numbers.indexOf(min(numbers));
let sort3 = (numbers) => {
let index = minIndex(numbers) //返回最小值的下标
let min = numbers[index] //numbers里的最小值
numbers.splice(index, 1)//从number里面删掉最小的数
return [min].concat(sort2(numbers))//把最小的数放在第一位,然后连接后面的数字
}
复制代码
2.1.3长度为4的数组排序
let sort4 = (numbers) => {
let index = minIndex(numbers)
let min = numbers[index]
numbers.splice(index, 1)
return [min].concat(sort3(numbers))
}
复制代码
2.1.4求任意长度数组的长度
let min = (numbers) => {
if(numbers.length > 2 ){
return min([numbers[0],min(numbers.slice(1))] )
}else{
return Math.min.apply(null,numbers)
}
};
let minIndex = (numbers) => numbers.indexOf(min(numbers));
let sort = (numbers) => {
if(numbers.length > 2){
let index = minIndex(numbers) //返回最小值的下标
let min = numbers[index]//numbers里的最小值
numbers.splice(index, 1)//从number里面删掉最小的数
return [min].concat(sort(numbers))//把最小的数放在第一位,然后连接后面的数字
}else{
return numbers[0]<numbers[1] ? numbers :numbers.reverse()
}
}
//用代入法理解递归 sort([12,5,8,7,9])
复制代码
2.2循环的思路
2.2.1用循环实现sort
let sort = (numbers) => {
for(let i=0; i< numbers.length -1; i++){
console.log(`----`) //这个log很精髓
console.log(`i: ${i}`)
let index = minIndex(numbers.slice(i))+ i
//如果上次循环找到第一个最小数字,那么之后找数字的时候,可以忽略第一个
//+i,如果不加i,numbers.slice(i)返回的新数组下标总是从0算起
console.log(`index: ${index}`)
console.log(`min: ${numbers[index]}`)
if(index!==i){
swap(numbers, index, i)
console.log(`swap ${index}: ${i}`)
console.log(numbers)
}
}
return numbers
}
//交换数组中两个数字的位置
let swap = (array, i, j) => {
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
//循环实现最小数字的下标
let minIndex = (numbers) => {
let index = 0 //假设最小数字下标是0
for(let i=1; i<numbers.length; i++){
if(numbers[i] < numbers[index]){
index = i
}
}
return index
}
复制代码
总结:
- 所有的递归都能改成循环
- 但循环有很多细节:
- 边界条件
- 长度为0和1的数组处理
- debug的时候要看控制台,学会打log,打log的时候注意标记
三、快速排序
递归的思路
let quickSort = arr => {
if (arr.length <= 1) { return arr; }
let pivotIndex = Math.floor(arr.length / 2); //选择一个基准
let pivot = arr.splice(pivotIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i])
} else { right.push(arr[i]) }
}
return quickSort(left).concat([pivot], quickSort(right) )
}
复制代码
四、归并排序
递归的思路
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))
}
复制代码
五、计数排序
思路:
- 用一个哈希表做记录
- 发现数字N就记为N:1,如果再次发现N就加1
- 最后把哈希表的key全部打出来,假设N:m,那么N需要打印m次
let countSort = arr =>{
let hashTable = {}, max = 0, result = []
for(let i=0; i<arr.length; i++){ // 遍历数组
if(!(arr[i] in hashTable)){
hashTable[arr[i]] = 1
}else{
hashTable[arr[i]] += 1
}
if(arr[i] > max) {max = arr[i]}
}
for(let j=0; j<=max; j++){ // 遍历哈希表
if(j in hashTable){
for(let i = 0; i<hashTable[j]; i++){//如果j出现的次数不止一次,就要遍历
result.push(j)
}
}
}
return result
}
复制代码
- 使用额外的hashTable,只便利一次数组和一次hashTable,用空间换时间
六、四种排序的时间复杂度
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END