基础知识篇
前端算法入门 – 数据结构
数据结构指的是“一组数据的存储结构”,算法指的是“操作数据的一组方法”。
数据结构是为算法服务的,算法是要作用再特定的数据结构上的。
最常用的数据结构预算法:
数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Tire树
算法: 递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
基础知识篇
1.什么叫算法?
算法就是计算或解决问题的步骤。
2.算法和程序有什么区别?
区别在于,程序是以计算机能够理解的编程语言编写的,可以在计算机上运行,而算法是以人类能够理解的数学方式来描述的,用于编程之前。但算法和编程没有具体边界。
3.如何选择算法?
同样的问题,不同的开发者解法不同,不同的编程语言,写法不同,为算法设立评判标准的目的在于选择最优标准的算法。评判算法的优劣有两个标准:一是从运行到计算出结果需要耗费空间的大小,另一个是从运行到计算出结果需要花费多少时间。分别称为, 空间复杂度 、 时间复杂度 。通常,复杂度越低,耗费内存越少,执行越快,也更容易被人理解。一般, 最为重视的是算法的运行时间 。
3.1 时间复杂度
-
是什么?
执行当前算法所“花费的时间”
-
干什么?
在写代码的过程中,就可以大概知道代码运行的快与慢
-
表示
大O表示法 《解析数论》
O表示有很多,例举几个:O(1)、O(n)、O(n^2)、O(logn)…
3.2 空间复杂度
-
是什么?
执行当前算法需要占用多少内存空间
-
表示法
O(1)、O(n)、O(n^2)…
多项式阶:随着数据规模的增长,算法的执行时间和空间占用,按照多项式的比例增长。包括,
O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n2)(平方阶)、O(n3)(立方阶)
非多项式阶:随着数据规模的增长,算法的执行时间和空间占用暴增,这类算法性能极差。包括,
O(2^n)(指数阶)、O(n!)(阶乘阶)
4.如何反映算法的运行时间?
算法不同、设备不同、数据量不同都会导致算法时间有差异,通过理论计算出的运行时间是一个多项式,而我们需要能最直观的了解到时间随数据量变化的关系,常常会忽略掉非重要项,得到一个最简单的并且最能反映运行时间趋势的表达式,我们把:忽略掉不重要项、能最简表示运行时间随数据量变化关系写成O(n)的形式,其中O是大写,表示忽略重要项以外的内容,读音同order;n表示参与算法的数据量。
前端算法 – 常见的八种数据结构
基础数据结构
数据结构就是关系,没错,就是数据元素相互之间存在的一种或多种特定关系的集合。
传统上,我们把数据结构分为逻辑结构和物理结构。
逻辑结构:是指数据对象中数据元素之间的相互关系,也是我们今后最需要关注和讨论的问题。
物理结构:是指数据的逻辑结构在计算机中的存储形式。
1.数组(Array)
数组是最简单、也是使用最广泛的数据结构。数组是可以在内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。
数组:查询快,增删慢
查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素
增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新的数组,把原数组的数据复制过来
2.栈(Stack)
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。
栈: 先进后出(入口出口在同一侧)
let arr = [];
arr.push(1);
arr.push(2);
arr.push(3); //顺序插入
console.log(arr);
arr.pop(); //删除对尾
arr.shift(); //删除队头
console.log(arr);
<script type="text/javascript">
var isValid = function (s) {
var stack = [];
for (let i = 0; i < s.length; i++) {
const start = s[i];
if (s[i] == "(" || s[i] == "{" || s[i] == "[") {
stack.push(s[i]);
} else {
const end = stack[stack.length - 1];
if (
(start == ")" && end == "(") ||
(start == "]" && end == "[") ||
(start == "}" && end == "{")
) {
stack.pop();
} else {
return false;
}
}
}
return stack.length == 0;
};
console.log(isValid("(){}"));
console.log(isValid("({)}"));
console.log(isValid("({})"));
</script>
3.队列(Queue)
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队。
队列:先进先出(入口出口在两侧,分开的)
let arr = [];
arr.push(1);
arr.push(2);
arr.push(3); //顺序插入
console.log(arr);
arr.pop(); //删除对尾
arr.shift(); //删除队头
console.log(arr);
4.链表(Linked List)
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
链表:查询慢,增删快
查询慢:链表地址不是连续的,每次查询都要从头开始
增删快:增加/删除一个元素,对链表的整体结构没有影响,所以增删快
链表入门
一、什么是链表
1. 多个元素存储的列表
2. 链表中的元素在内存中不是顺序存储的,而是通过"next"指针联系在一起的。
***js中的原型链 原理就是 链表结构
二、链表和数组的区别
1. 数组:有序存储的,在中间某个位置删除或者添加某个元素,其他元素要跟着动。
2. 链表中的元素在内存中不是顺序存储的,而是通过"next"指针联系在一起的。
三、链表
单向链表
双向链表
![在这里插入图片描述](https://img-blog.csdnimg.cn/cd19514091fc4e1791f5cf37726efec9.png)
let a = {key:'aaa'};
let b = {key:'bbb'};
let c = {key:'ccc'};
let d = {key:'ddd'};
a.next = b;
b.next = c;
c.next = d;
d.next = null;
console.log( a );
//遍历链表
let obj = a;
while( obj && obj.key ){
console.log( obj.key );
obj = obj.next;
}
//链表中插入某个元素
let m = {key:'mmmmm'};
c.next = m;
m.next = d;
console.log( a );
//删除操作
c.next = d;
5.字典
jsES6中的map方法
阮一峰ES6 入门教程map
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
对象 : 键值对存储的(对象---键[key]都是字符串类型或者会转换成字符串类型)
字典 : 键值对存储的 map来表示的,map的键不会转换类型,可以存在任何数据类型
力扣01两数之和
<script type="text/javascript">
var twoSum = function(nums, target) {
let map = new Map();
for( let i=0;i<nums.length;i++){
num = target - nums[i];
if( map.has(num) ){
return [map.get(num),i];
}
map.set(nums[i],i);
}
};
</script>
5.散列表(Hash) 哈希表
在js中没有哈希表,哈希表是字典一种实现。
区别一:如果找key对应的value需要遍历key,那么想要省去遍历的过程,用哈希表来表示。
区别二:排列顺序
字典是根据添加的顺序进行排列的
哈希表不是添加的顺序进行排列的
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
Hash表是一种特殊的数据结构,它同数组、栈、链表等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。
哈希表是基于键值对的一种数据存储结构,key值不可以重复,value可以重复,后面添加的重复的key值的时候,会把之前key值对应的value给覆盖掉,JavaScript中的对象具有天然的哈希特性。
js模拟哈希表
<script type="text/javascript">
class HashTable{
constructor(){
this.table = [];
}
hashCode( key ){
let hash = 0;
for( let i =0;i<key.length;i++){
hash += key.charCodeAt(i);
}
return hash;
}
put( key , val ){
let hashKey = this.hashCode(key);
this.table[ hashKey ] = val;
}
get( key ){
let hashKey = this.hashCode(key);
return this.table[hashKey];
}
}
let hashTable = new HashTable();
hashTable.put('person','章三');
console.log( hashTable );
console.log( hashTable.get('person') );
</script>
6.树(Tree)
树:一种分层数据的抽象模型 ----简单来说:分层级关系
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树。
<script type="text/javascript">
const tree = {
val: "a",
children: [
{
val: "b",
children: [
{ val: "d", children: [] },
{ val: "e", children: [] },
],
},
{
val: "c",
children: [
{ val: "f", children: [] },
{ val: "g", children: [] },
],
},
],
};
// 深度优先遍历
// const fun1 = (root) => {
// console.log(root.val);
// 写法一
// root.children.forEach((item) => {
// fun1(item);
// });
// 写法二
// root.children.forEach(fun1);
// };
// fun1(tree);
// 广度优先遍历
const fun2 = (root) => {
const arr = [root];
while (arr.length > 0) {
const o = arr.shift();
console.log(o.val);
o.children.forEach((item) => {
arr.push(item);
});
}
};
fun2(tree);
</script>
7.堆(Heap)
堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质: 堆中某个节点的值总是不大于或不小于其父节点的值; 堆总是一棵完全二叉树。
最小堆的实现
<script type="text/javascript">
class MinHeap {
constructor() {
this.heap = [];
}
// 换位置
swap(i1, i2) {
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
// 找父节点
getParentIndex(index) {
return Math.floor((index - 1) / 2);
}
// 上前移操作
up(index) {
// 如果是0就不移动了
if (index == 0) return;
const parentIndex = this.getParentIndex(index);
// 如果父元素大于当前元素,就开始移动
if (this.heap[parentIndex] > this.heap[index]) {
this.swap(parentIndex, index);
this.up(parentIndex);
}
}
// 获取左侧子节点
getLeftIndex(index) {
return index * 2 + 1;
}
// 获取右侧子节点
getRightIndex(index) {
return index * 2 + 2;
}
// 下(后)移动
down(index) {
const leftIndex = this.getLeftIndex(index);
const rightIndex = this.getRightIndex(index);
if (this.heap[leftIndex] < this.heap[index]) {
this.swap(leftIndex, index);
this.down(leftIndex);
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swap(rightIndex, index);
this.down(rightIndex);
}
}
// 添加元素
insert(value) {
this.heap.push(value);
this.up(this.heap.length - 1);
}
// 删除堆顶
pop() {
this.heap[0] = this.heap.pop();
this.down(0);
}
// 获取堆顶
peek() {
return this.heap[0];
}
// 数量
size() {
return this.heap.length;
}
}
let arr = new MinHeap();
arr.insert(5);
arr.insert(4);
arr.insert(6);
arr.insert(1);
arr.pop();
console.log(arr);
console.log(arr.size());
console.log(arr.peek());
</script>
8.图(Graph)
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。
算法中必备知识
1. 递归
递归就是自己调自己,递归在前端里面算是一种比较常用的算法
-
使用递归函数需要注意什么
1,可以终止递归的条件【如果没有这个条件就会进入死循环】
2,可以是递归函数一直地柜下去的条件【如果没有这个条件,那么递归函数将会递归不下去】 -
实例
function fn(n) {
if (n <= 2) { //可以终止递归的条件
return 1;
}
return fn(n - 1) + fn(n - 2); //可以是递归函数一直地柜下去的条件
}
// 1. f(4) + f(3)
// 2. f(3)+f(2) + f(2) +f(1)
// 3. f(2)+f(1)+f(2) + f(2)+f(1)
// 4. 1 + 1 + 1 + 1 + 1
console.log(fn(5)); // 5
2. JS中的任务队列
<script type="text/javascript">
Promise.resolve('3333').then(res=>{
console.log( res );
setTimeout(()=>{
console.log('Promise setTimeout')
},0)
})
setTimeout(()=>{
console.log( 111 );
Promise.resolve('setTimeout Promise').then(res=>{
console.log( res );
})
},0)
console.log( 222 );
</script>