优化上个章节的代码
重写minIndex
let minIndex = (numbers) =>{
let index = 0
for(let i = 1; i < numbers.length; i++){
if(numbers[i] < numbers[index]){
index = i
}
}
return index
}
选择排序
所有的递归都可以改成循环
代码1:确定思路
思路不变,每次找到最小的数放在前面,然后i++
- 先忽略
i < ???写什么 - 提前写好
minindex和swap来占位能有效简化问题
let sort = (numbers) =>{
for (let i = 0; i < ???; i++){
// 找到最小的数
let index = minIndex(numbers)
// 每次把最小的数放在前面
swap(numbers,index,i)
}
}
代码2:实现swap
let swap =(array,i,j) =>{
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
swap的误区
在这里,因为a,b是简单类型,传输参数的时候会复制整个值到新的stack栈内存,所以事实上有两个a, 两个b。 原先的a和b不受影响
var numbers = [100, 200]
var swap = (a,b) =>{
let temp =a;
console.log(`a:${a},temp:${temp}`);
a =b;
console.log(`a:${a},b:${b}`);
b = temp;
console.log(`a:${a},b:${b}, temp:${temp}`)
}
swap(numbers[0], numbers[1])
// a:100,temp:100
// a:200, b:200
// a:200,b:100, temp:100
minIndex(numbers)的误区
let sort = (numbers) =>{
for (let i = 0; i < ???; i++){
// 找到最小的数
let index = minIndex(numbers)
// 每次把最小的数放在前面
swap(numbers,index,i)
}
}
let index = minIndex(numbers)
在上面的代码中,如果数组的长度为4, 那么i的值分别为0,1,2,3。
minIndex的查找范围有问题,因为上次循环已经找到了第一个最小的数字,所以下次循环查找中可以把它给剔除,不然就会在不同的i里反复替换最小的数字
let index = minIndex(numbers.slice(i) + i)
在这里,numbers.slice(i)就会把前面已经查找过的i剔除,在剩下的数字里找到最小的值。+i是寻找实际数组中被替换数组的位置,不然index总是从新一轮的0数起。
使用console.log()调试
将下面的代码写入循环里,可以显示每轮循环的开始
console.log(`---`)
最终代码
let swap =(array,i,j) =>{
let temp = array[i]
array[i] = array[j]
array[j] = temp
}
let minIndex = (numbers) =>{
let index = 0
for(let i = 1; i < numbers.length; i++){
if(numbers[i] < numbers[index]){
index = i
}
}
return index
}
let sort = (numbers) =>{
for(let i = 0; i < numbers.length-1; i++){
let index = minIndex(numbers.slice(i)) + i
if(index !== i ){
swap(numbers, index, i)
}
}
return numbers
}
sort([1,199,27,93,124,4903,4])
总结
- 所有的递归都能改成循环
- 循环的时候有很多细节,需要动手才能找出规律,边界条件很难确定,也没有处理长度为
0和1的数组 - 需要学会
console.log调试,不断的debug
quickSort快速排序
思路
以某个数为基准,比它小的去前面,比它大的去后面,重复操作,就可以得到完整的排序。
代码
var quickSort = function(arr) {
if (arr.length <= 1) { return arr; }
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var 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));
};
mergeSort合并排序
思路
把一个数组,分为两部分,左右两边分别拍好顺序,再进行合并,得到完整的数组
代码
let mergeSort = arr =>{
let k = arr.length
if(k===1){return arr}
let left = arr.slice(0, Math.floor(k/2))
let right = arr.slice(Math.floor(k/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))
}
countSort计数排序
思路
用一个哈希表最为记录,发现数字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++){
result.push(j)
}
}
}
return result
}
特点
数据结构不同,使用了额外的hashTable哈希表, 只遍历了一次数组和一次哈希表,这叫做用空间换时间
花费时间的对比
- 选择排序
O(n^2) - 快速排序
O(n * log2 n) - 归并排序
O(n * log2 n) - 计数排序
O(n + max)