You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function cal(n) {
let sum_1 = 0;
let p = 1;
for (; p < 100; ++p) {
sum_1 = sum_1 + p;
}
for (; q < n; ++q) {
sum_2 = sum_2 + q;
}
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum_3 = sum_3 + i * j;
}
}
}
时间复杂度为 T(n) = O(n^2)
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
嵌套代码求乘积:比如递归、多重循环等。
function cal(n) {
let ret = 0;
let i = 1;
for (; i < n; ++i) {
ret = ret + f(i); // 重点为 f(i)
}
}
function f(n) {
let sum = 0;
let i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
T(n) = T1(n) * T2(n) = O(n*n) = O(n2) 。
4.多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加
function cal(m, n) {
let sum_1 = 0;
let i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
let sum_2 = 0;
let j = 1;
for (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
T(n) = O(m+n)
5.多个规模求乘法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相乘
function cal(m, n) {
let sum_3 = 0;
let i = 1;
let j = 1;
for (; i <= m; ++i) {
j = 1;
for (; j <= n; ++j) {
sum_3 = sum_3 + i * j;
}
}
}
这时循环结束,即只要算出x就能得到代码执行的次数。即 x = log2^n.所以时间复杂度为 T(n) = O(logn)
O(nlogn)(对数阶):
function aFun(n){
let i = 1;
while (i <= n) {
i = i * 2;
}
return i
}
function cal(n) {
let sum = 0;
for (let i = 1; i <= n; ++i) {
sum = sum + aFun(n);
}
return sum;
}
let arr = [1,2,3,4,5,2,3]
let result = arr.reduce((prev,cur)=>{
if (!prev.includes(cur)){
prev.push(cur)
}
return prev
},[])
console.log(result)
3.统计每个值,在数组中出现的次数
let arr = [1,2,3,4,5,2,3]
let result = arr.reduce((prev,cur)=>{
if (prev[cur] != undefined) {
prev[cur]++
} else {
prev[cur] = 1
}
return prev
},{})
console.log(result)
4.求数组中最大数
var a = [1,2,3,4,5,3,4,5,4,3,4,3,2,3,4,5,4,4,3,4,3,3,3,3,4]
var ret = a.reduce((m,n) => {
if (m.max < n) m.max = n;
return m;
}, {max: 0});
console.log(ret.max)
JavaScript 数据结构与算法之美 (最底下那个系列文章)
图解算法
动画算法
写给前端的算法进阶指南,我是如何两个月零基础刷200题
「算法与数据结构」梳理6大排序算法
「算法与数据结构」DFS和BFS算法之美
了不起的 IoC 与 DI
一些leecode数据结构相关js代码
复杂度分析-大O表示法
这里 时间复杂度 描述的是算法执行时间与数据规模的 增长变化趋势,所以时间复杂度是 T(n) = O(n)
时间复杂度分析规则
1.只关注循环执行次数最多的一段代码
2.加法法则:总复杂度等于量级最大的那段代码的复杂度
时间复杂度为 T(n) = O(n^2)
3.乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
嵌套代码求乘积:比如递归、多重循环等。
T(n) = T1(n) * T2(n) = O(n*n) = O(n2) 。
4.多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加
T(n) = O(m+n)
5.多个规模求乘法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相乘
T(n) = O(m*n)
常用的时间复杂度分析
包括 O(1)(常数阶)、O(logn)(对数阶)、O(n)(线性阶)、O(nlogn)(线性对数阶)、O(n2) (平方阶)、O(n3)(立方阶)。
O(logn)(对数阶):
=>
这时循环结束,即只要算出x就能得到代码执行的次数。即 x = log2^n.所以时间复杂度为 T(n) = O(logn)
O(nlogn)(对数阶):
O(2n)(指数阶)例子:
时间复杂度总结
常用的时间复杂度所耗费的时间从小到大依次是:
空间复杂度分析
表示 算法的存储空间与数据规模之间的增长关系 。
定义:算法的空间复杂度通过计算算法所需的存储空间实现,算法的空间复杂度的计算公式记作:S(n) = O(f(n)),其中,n 为问题的规模,f(n) 为语句关于 n 所占存储空间的函数。
这里的空间即数组长度n,所以S(n) = O(n).常见的空间复杂度就是 O(1)、O(n)、O(n2)
#《数据结构和算法描述》
array
1.count为0时,表示从index偏移开始,添加item1,item2...
2.count>0时,表示从index偏移开始,删除count个元素
(涉及到数组中每个元素的比较、交互等操作时,都可以用reduce)
reduce()方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
1.initialValue有值时,初始total = initialValue
2.initialValue无值时,初始total = array[0]
应用场景:
1.数组求和
2.简单数组去重
3.统计每个值,在数组中出现的次数
4.求数组中最大数
线性表与非线性表
线性表(Linear List):就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。数组、链表、队列、栈 等就是线性表结构。
非线性表:数据之间并不是简单的前后关系。二叉树、堆、图 就是非线性表。
数组
所以数组支持 随机访问,根据下标随机访问的时间复杂度为 O(1)。
数组为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效,因为底层通常是要进行大量的数据搬移来保持数据的连续性。
插入与删除的时间复杂度如下:
插入:从最好 O(1) ,最坏 O(n) ,平均 O(n)
删除:从最好 O(1) ,最坏 O(n) ,平均 O(n)
栈
定义
后进者先出,先进者后出,简称 后进先出(LIFO),这就是典型的栈结构。
新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。
在栈里,新元素都靠近栈顶,旧元素都接近栈底。
从栈的操作特性来看,是一种 操作受限的线性表,只允许在一端插入和删除数据。
不包含任何元素的栈称为空栈。
栈也被用在编程语言的编译器和内存中保存变量、方法调用等,比如函数的调用栈。
实现
栈的方法:
push(element):添加一个(或几个)新元素到栈顶。
pop():移除栈顶的元素,同时返回被移除的元素。
peek():返回栈顶的元素,不对栈做任何修改。
isEmpty():如果栈里没有任何元素就返回 true,否则返回 false。
clear():移除栈里的所有元素。
size():返回栈里的元素个数。
队列
1.普通队列
队列是遵循 FIFO(First In First Out,先进先出)原则的一组有序的项。
队列在尾部添加新元素,并从顶部移除元素。
最新添加的元素必须排在队列的末尾。
队列只有 入队 push() 和出队 pop()。
队列里面有一些声明的辅助方法:
enqueue(element):向队列尾部添加新项。
dequeue():移除队列的第一项,并返回被移除的元素。
front():返回队列中第一个元素,队列不做任何变动。
isEmpty():如果队列中不包含任何元素,返回 true,否则返回 false。
size():返回队列包含的元素个数,与数组的 length 属性类似。
print():打印队列中的元素。
clear():清空整个队列。
2.优先队列
优先队列中元素的添加和移除是依赖优先级的。
应用:
优先队列分为两类
最小优先队列是把优先级的值最小的元素被放置到队列的最前面(代表最高的优先级)。
比如:有四个元素:"John", "Jack", "Camila", "Tom",他们的优先级值分别为 4,3,2,1。
那么最小优先队列排序应该为:"Tom","Camila","Jack","John"。
最大优先队列正好相反,把优先级值最大的元素放置在队列的最前面。
以上面的为例,最大优先队列排序应该为:"John", "Jack", "Camila", "Tom"。
3.循环队列
循环队列,顾名思义,它长得像一个环。把它想像成一个圆的钟就对了。
关键是:确定好队空和队满的判定条件。
循环队列的一个例子就是击鼓传花游戏(Hot Potato)。在这个游戏中,孩子们围城一个圆圈,击鼓的时候把花尽快的传递给旁边的人。某一时刻击鼓停止,这时花在谁的手里,谁就退出圆圈直到游戏结束。重复这个过程,直到只剩一个孩子(胜者)。
链表
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的,它是通过 指针 将 零散的内存块 串连起来的。
每个元素由一个存储元素本身的 节点 和一个指向下一个元素的 引用(也称指针或链接)组成。
其中,data 中保存着数据,next 保存着下一个链表的引用。
上图中,我们说 data2 跟在 data1 后面,而不是说 data2 是链表中的第二个元素。值得注意的是,我们将链表的尾元素指向了 null 节点,表示链接结束的位置。
特点
链表是通过指针将零散的内存块串连起来的。
所以链表不支持 随机访问,如果要找特定的项,只能从头开始遍历,直到找到某个项。
所以访问的时间复杂度为 O(n)。
高效的插入和删除。
链表中插入或者删除一个数据,我们并不需要为了保持内存的连续性而搬移结点,因为链表的存储空间本身就不是连续的,只需要考虑相邻结点的指针改变。
所以,在链表中插入和删除一个数据是非常快速的,时间复杂度为 O(1)。
单链表
由于链表的起始点的确定比较麻烦,因此很多链表的实现都会在链表的最前面添加一个特殊的节点,称为 头节点,表示链表的头部。
经过改造,链表就成了如下的样子:
针对链表的插入和删除操作,我们只需要考虑相邻结点的指针改变,所以插入与删除的时间复杂度为 O(1)。
在 d2 节点后面插入 d4 节点:
删除 d4 节点:
实现
Node 类用来表示节点。
LinkedList 类提供插入节点、删除节点等一些操作。
单向链表的八种常用操作:
append(element):尾部添加元素。
insert(position, element):特定位置插入一个新的项。
removeAt(position):特定位置移除一项。
remove(element):移除一项。
indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回 -1。
isEmpty():如果链表中不包含任何元素,返回 true,如果链表长度大于 0,返回 false。
size():返回链表包含的元素个数,与数组的 length 属性类似。
getHead():返回链表的第一个元素。
toString():由于链表使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString() 方法,让其只输出元素的值。
print():打印链表的所有元素。
链表的insert和remove都必须获取到previousNode和currentNode才行
在 JavaScript 中,单链表的真实数据有点类似于对象,实际上是 Node 类生成的实例。
双向链表
单向链表只有一个方向,结点只有一个后继指针 next 指向后面的结点。
而双向链表,它支持两个方向,每个结点不止有一个后继指针 next 指向后面的结点,还有一个前驱指针 prev 指向前面的结点。
单向链表与又向链表比较
所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。
虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。
我们可以访问一个特定节点的下一个或前一个元素。
循环链表
循环链表是一种特殊的单链表。
循环链表和单链表相似,节点类型都是一样。
唯一的区别是,在创建循环链表的时候,让其头节点的 next 属性指向它本身。
即:
这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表。如下图所示:
环形链表从任意一个节点开始,都可以遍历整个链表。
链表总结
The text was updated successfully, but these errors were encountered: