一,了解算法
算法是如何解决一类问题的明确规范。
算法有大致分为排序类、搜索类、集合操作类、字符串操作类、数学类、树、图、和一些其他的分类比如判断回文算法等等。
通常学习算法都会先学习排序类的冒泡排序算法,因为入门简单,但是从运行时间的角度看,这个算法效率比较底下,后面主要了解一些常用的算法。
二,排序类
2.1 冒泡排序
原理:比较相邻的两个项,如果第一个比第二个大,则交换他们。以此类推。元素向上移动到正确的顺序,就像气泡上升到表面一样。
示例:
// 外循环,遍历整个数组
for (let i = 0; i < arr.length; i++) {
// 每次内循环执行完毕,外循环的i就+1,这时最大的值就已经冒泡到数组最末尾
// 所以内循环遍历数组的第1个到未排序的最后一个(即arr.length - 1 - i)
for (let j = 0; j < arr.length -1 - i; j++) {
if(arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
说明:冒泡算法的复杂度较高,因此不建议使用。
2.2 选择排序
原理:找到数据结构中的最小值,放到第一位,接着找第二小的值,放在第二位,以此类推。
示例:
// 外循环,遍历整个数组
for (let i = 0; i < arr.length; i++) {
// i从头遍历,每次内循环执行完,数组最左边一定是最小的值
// 所以内循环从第i+1之后的i开始,比较后面的每个未比较大小的值
for (let j = i; j < arr.length; j++) {
if (arr[i] > arr[j]) {
let temp = arr[j]; // 最小值变成了arr[j],赋值给一个变量
arr[j] = arr[i];
arr[i] = temp;
}
}
}
2.3 插入排序
原理:假设第1项已经排序,第2项和第一项再进行比较排序,第3项和前2项再进行比较排序,第4项再和前3项比较排序,以此类推。
示例:
let j = undefined;
let temp = undefined;
// 外循环,从第2项开始
for (let i = 1; i < arr.length; i++) {
j = i;
temp = arr[i];
// 依次两两判断当前项的前面n项,如果前一个值比当前的大,则交换他们,直至j为0
while (j > 0 && arr[j - 1] > arr[j]) {
arr[j] = arr[j - 1];
j--;
}
// 找到不比后面大的值时,就塞到当前位置(也说明没有循环时,也是当前位置)
arr[j] = temp;
}
说明:排序小型数组时,插入排序算法比选择排序和冒泡排序性能要好。
2.4 归并排序
概念:将原始数组切分成较小的数组,直到每个小数组只有一个位置;再将小数组归并成大数组,直到最后只有一个排序完毕的大数组。
示例:
// 递归拆分小数组,直到拆成一个数组只有一个元素
function mergeSort(arr) {
if (arr.length < 2) {
return arr;
}
let middleIndex = parseInt(arr.length / 2); // 获取数组最中心的索引
let leftArr = arr.slice(0, middleIndex);
let rightArr = arr.slice(middleIndex, arr.length);
// 先调用递归拆分数组,再调用merge函数归并数组
return merge(mergeSort(leftArr), mergeSort(rightArr));
}
// 递归归并数组
function merge(left, right) {
let result = [];
// 循环左侧小数组和右侧小数组,直到小数组长度为0
while (left.length && right.length) {
// 判断小数组元素值的大小,并返回小的值push到目标数组
if (left[0] < right[0]) {
// 小数组删除元素,同时返回当前元素(删除是为了可以退出while循环)
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;
}
说明:归并排序算法相比于前三个排序算法,他的性能最好,可以被实际使用的排序算法。
2.5 快速排序
原理:快速排序也用了与归并排序类似的分治方法,但是快速排序并不是把他们割开。他的流程是
(1)首先找一个数组中间的位置作为主元
(2)创建两个指针,左边指向第一个元素,右边指向最后一个元素。然后移动左指针直到找到比主元大的元素,移动右指针直到找到比主元小的元素,然后交换他们。重复这个过程,直到左指针超过右指针。
(3)最后对算法对划分后的小数组(比主元小的值组成的子数组,和比主元大的值组成子数组)重复之前的两个步骤,直到数组已完全排序。
示例:
function quick (array,left,right) {
var index;
// 对半切分两个小数组,直到小数组长度为1
if (array.length > 1) {
// 每次循环交换完中间点两边的值,就按中间点切成两个一半长度的小数组
index = partition(array,left,right);
if (left < index - 1) {
quick(array,left,index - 1); // 左侧的小数组重复
}
if (index < right) {
quick(array,index,right); // 右侧的小数组重复
}
}
}
function partition (array,left,right) {
var pivot = array[Math.floor((right + left) / 2)], // 取中间点
i = left, // 分别设置左指针和右指针的值
j = right;
// 一直循环到左指针大于右指针
while(i <= j) {
// 左半部分直到找到比主元大的值结束循环
while (array[i] < pivot) {
i++; // 指针移动
}
// 右半部分直到找到比主元小的值结束循环
while (array[j] > pivot) {
j--;
}
// 走到这说明左半部分和右半部分循环均已结束,需要交换两个值的位置
if (i <= j) {
[array[i],array[j]] = [array[j],array[i]]
i ++;
j --;
}
}
return i; // 返回左指针停止位置的索引(基本是外层数组中间点的左右)
}
quick(array, 0, array.length - 1);
说明:快速排序是最常用的排序算法。
三,搜索类
3.1 顺序搜索
原理:顺序搜索是最基本的搜索算法,但也是效率最低。主要机制是遍历数组,逐个对比。
示例:
function search(array, item) {
for (var i = 0; i < array.length; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
}
3.2 二分搜索
原理:数组已排序的前提下,选择数组中间的一个值比较,如果比中间值小,就在左半部分再次取中间值分割,以此类推;反之如果大于中间值,就在数组右半部分再次分割。
示例:
function binarySearch(arr, item) {
var minIndex = 0; // 设置最小值的索引
var maxIndex = arr.length - 1; // 设置最大值的索引
var midIndex = undefiend; // 设置中间值的索引
var midVal = undefined; // 中间值的值
while (minIndex < maxIndex) {
midIndex = Math.floor((maxIndex + minIndex) / 2); // 取中间值索引
midVal = arr[midIndex]; // 取中间值索引的值
if (item > midVal) {
minIndex = midIndex + 1; // 如果大于中间值,则修改最小值为中间值,最大值不变
} else if (item < midVal) {
maxIndex = midIndex - 1; // 反之,最小值不变,二分左半部分
} else {
return midVal; // 如果相等,则返回目标值
}
}
return -1; // 最终找不到,返回-1
}
四,其他
其他类别还有很多算法,比如数学类的阶乘、斐波那契、数据结构树和图的深度遍历、广度遍历、集合相关、贪心算法等等。先不做详细了解,可参考文章JavaScript 算法与数据结构 - 掘金