算法的定义:
- 一个有限指令集,每条指令的描述不依赖与语言。
- 接受一些输入
- 产生输出
- 一定在有限步骤后终止
算法的通俗理解
- Algorithm 这个单词本意就是解决问题的办法/步骤逻辑
- 数据结构的实现,离不开算法
1.数组
JS的数组API掌握的已经足够熟练了,所以不讲了。
这里只讲一下js与其他语言有关的地方
- 首先常见语言的数组不能存放不同的数据类型,因此封装是通常存放在数组中的是object类型
- 常见的语言的数组的容量不会自动改变,因此需要扩容。扩容在c是极为效率低的一个操作
- 同样低效率的操作还有在数组中间进行插入和删除的操作。因为要移动后面的或者前面的所有节点。
- 优点:
- 在有关于下标的时候,数组方便的很。
2.栈结构
- 数组是可以在任何位置进行增加或者删除的一种线性结构。
- 有时候必须对数据进行限制,对其任意性加以限制。
- 栈和队列就是这种的受限的线性结构。
- 特性:后进先出(last in first out)
- 只允许在一端进行插入和删除操作,这端叫栈顶。相对的其中另一端叫栈低
- 插入叫入栈,进栈或者压栈。
- 删除叫出栈或者退栈。
- 程序中什么是使用栈实现的呢?
- 学了这么久的编程,是否听说过,函数调用栈呢?
- 我们知道函数之间和相互调用:A调用B,B中又调用C,C中又调用D.
- 那样在执行的过程中,会先将A压入栈,A没有执行完,所有不会弹出 栈
- 在A执行的过程中调用了B,会将B压入到栈,这个时候B在栈顶,A在 栈底
- 如果这个时候B可以执行完,那么B会弹出栈,但是B有执行完吗?没有 它调用了C.
- 所以C会压栈,并且在栈顶.而C调用了D,D会压入到栈顶.
- 所以当前的栈顺序是:栈顶A->B->C->D栈顶
- D执行完,弹出栈.C/B/A依次弹出栈.
- 所以我们有函数调用栈的称呼,就来自于它们内部的实现机制.(通过 栈来实现的)
例题
- 有六个元素6,5,4,3,2,1的顺序进栈,问下列哪一个不是合法的出栈序列?(c)
- A.543612 B.453216 C.346521 D.234156
// 栈的封装 | |
function Stack() {// 栈中的属性this.items = [] | |
| |
} | |
// 原型要放在外面,这样不会重复执行。 | |
// 实测放在外面新建五个对象平均0.05ms。放在外面要0.15ms | |
| |
// 1.入栈操作 | |
Stack.prototype.push = function (params) { | |
this.items.push(params) | |
} | |
// 2.取出栈顶 元素 | |
Stack.prototype.pop = function () { | |
return this.items.pop() | |
} | |
// 3.查看栈顶 元素 | |
Stack.prototype.peek = function () { | |
return this.items[this.items.length - 1] | |
} | |
| |
// 4.判断栈是否为空 | |
Stack.prototype.isEmpty = function () { | |
return this.items.length === 0 | |
} | |
| |
// 5.获取栈中元素个数 | |
Stack.prototype.size = function () { | |
return this.items.length | |
} | |
// 6.toString方法 | |
Stack.prototype.toString = function () { | |
let str = this.items.reduce((pre, cur) => { | |
return pre + ' ' + cur | |
}) | |
return str | |
} | |
| |
console.time('global') | |
| |
// 栈的使用 | |
let s = new Stack() | |
let s1 = new Stack() | |
let s2 = new Stack() | |
let s3 = new Stack() | |
| |
| |
console.timeEnd('global') | |
| |
// 栈的操作 | |
// s.push(10) | |
// s.push(20) | |
// s.push(30) | |
// console.log(s); // Stack{ items:[10,20,30]} | |
// s.pop() | |
// console.log(s); | |
// console.log(s.peek()); | |
// console.log(s); | |
// console.log(s.size()); | |
// s.push(40) | |
// console.log(s.toString()); | |
| |
export default Stack | |
import Stack from './1_栈的封装.js' | |
| |
// 100 | |
// 计算:100/2余数:0 | |
// 计算:50/2余数:0 | |
// 计算:25/2余数:1 | |
// 计算:12/2余数:0 | |
// 计算:6/2余数:0 | |
// 计算:3/2余数:1 | |
// 计算:1/2余数:1 | |
// 从底下到上面:1100100 | |
| |
// 主要是运用队栈的进去再拿出来 | |
// 思路: | |
// 1.计算主要留下两个有用的数,就是余数和除法的结果。 | |
// 2.结果保存下来接着用 | |
// 3.余数直接放到栈里 | |
// 4.结束条件是当除法结果为0的时候,结束。 | |
// 5.然后把数组捣腾出来组合成字符串就行了 | |
| |
function dec2bin(decNumber) { | |
let stack = new Stack() | |
while (decNumber > 0) { | |
stack.push(decNumber % 2) | |
decNumber = Math.floor(decNumber / 2) | |
} | |
let res = '' | |
| |
console.log(stack.isEmpty()); | |
while (!stack.isEmpty()) { | |
res += stack.pop() | |
} | |
console.log(res); | |
} | |
| |
dec2bin(1) | |
// 队列本质还是数组 | |
// 这波用的class 性能和直接构造原型的方法基本一致。 | |
| |
class Queue { | |
constructor() { | |
this.items = [] | |
} | |
// enqueue | |
emqueue(element) { | |
this.items.push(element) | |
} | |
// dequeue | |
dequeue() { | |
return this.items.shift() | |
} | |
// front | |
front() { | |
return this.items[this.items.length - 1] | |
} | |
// isEmpty | |
isEmpty() { | |
return this.items.length === 0 | |
} | |
// size | |
size() { | |
return this.items.length | |
} | |
// toString | |
toString() { | |
let str = this.items.reduce((pre, cur) => { | |
return pre + ' ' + cur | |
}) | |
return str | |
} | |
} | |
| |
| |
console.time('global') | |
let queue = new Queue() | |
console.timeEnd('global') | |
queue.emqueue(10) | |
queue.emqueue(20) | |
queue.emqueue(30) | |
queue.dequeue() | |
console.log(queue); | |
| |
// 导出,供2用 | |
export default Queue | |
// 击鼓传花是一个常见的面试算法题.使用队列可以非常方便的实现最终的结果. | |
| |
// 原游戏规则: | |
// 口 班级中玩一个游戏,所有学生围成一圈,从某位同学手里开始向旁边的同学传一束花. | |
// 口 这个时候某个人(比如班长),在击鼓,鼓声停下的一颗,花落在谁手里,谁就出来表演节目. | |
| |
// 修改游戏规则: | |
// 口 我们来修改一下这个游戏规则. | |
// 口几个朋友一起玩一个游戏,围成一圈,开始数数,数到某个数字的人自动淘汰 | |
// 口最后剩下的这个人会获得胜利,请问最后剩下的是原来在哪一个位置上的人? | |
| |
// 封装一个基于队列的函数 | |
// 口 参数:所有参与人的姓名,基于的数字 | |
// 口结果:最终剩下的一人的姓名 | |
| |
| |
// 解决思路 | |
// 1.队列里不断循环,队列外加一个计数器。 | |
// 2.出队一个计数器+1,看计数器等不等于5,不过不等于,则证明不是第五个,所以不淘汰。 | |
// 3.不淘汰的元素出队,然后再加入到队尾,计数器加1 | |
// 4.淘汰的直接出队,不加入到队尾,计数器归为1 | |
// 5.这样人越来越少,判断结束的条件是队列的元素是否等于一个 | |
| |
import Queue from './1_封装队列.js' | |
| |
let q1 = new Queue() | |
| |
let arr = ['小李', 'bill'] | |
| |
for (let item of arr) { | |
q1.emqueue(item) | |
} | |
let i = 1 | |
// console.log(typeof q1.size()); | |
while (q1.size() > 1) { | |
console.log(111);let tmp = q1.dequeue() | |
if (i != 5) { | |
q1.emqueue(tmp) | |
i++ | |
} else if (i == 5) { | |
i = 1 | |
} | |
} | |
| |
console.log(q1); | |
// 优先级队列的特点: | |
// 口 我们知道,普通的队列插入一个元素,数据会被放在后端.并且需要前面所有的元素都处理完成后才会处理前面的数据. | |
// 口 但是优先级队列,在插入一个元素的时候会考虑该数据的优先级. | |
// 口 和其他数据优先级 进行比较. | |
// 口 比较完成后,可以得出这个元素在队列中正确的位置 | |
// 口 其他处理方式,和基本队列的处理方式一样. | |
| |
// 二优先级队列主要考虑的问题: | |
// 口每个元素不再只是一个数据,而且包含数据的优先级 | |
// 口在添加方式中,根据优先级放入正确的位置. | |
| |
// 现实中的优先级队列 | |
// 1. 机场登机 老人和小孩,或者商务舱的多 | |
// 2. 医院的急诊室和普通病房 | |
import Queue from './1_封装队列.js' | |
| |
// 优先级队列的封装 | |
// 优先级队列和普通队列的区别仅在于 优先级队列的插入与普通的不同 | |
// 所以可以仅仅通过定义一个插入队列,然后剩下的直接通过extends继承下来就可以了。 | |
class PriorityQueue extends Queue { | |
| |
constructor() { | |
// 要继承的话必须执行super()方法,因为super是调用原来的父类,将方法和值赋值到this上。 | |
// 和原先老的es5写法是不一样的,原来的es5是通过原型链来实现的.super() | |
this.items = [] | |
} | |
| |
emqueue(element, priority) { | |
let queueElement = new QueueElement(element, priority) | |
if (this.items.length === 0) { | |
this.items.push(queueElement) | |
} else { | |
let added = falsethis.items.every((item, index) => { | |
if (queueElement.priority < item.priority) { | |
this.items.splice(index, 0, queueElement) | |
added = true; | |
// 这里用every 或者 some 都可以break。 | |
// break的方法就是 every中用return false | |
// some中用return true | |
return false | |
} | |
}) | |
if (!added) { | |
this.items.push(queueElement) | |
} | |
} | |
} | |
} | |
| |
| |
// es6 class 没有内部类的概念,所以不推荐用function写在里面这种写法 | |
// 直接在外面写就行,性能高。 | |
// 内部类的写法就是重复的去新建QueueElement,写在里面没有任何意义。 | |
class QueueElement { | |
constructor(element, priority) { | |
this.element = element | |
this.priority = priority | |
} | |
} | |
| |
let pq = new PriorityQueue() | |
| |
pq.emqueue(111, 1) | |
pq.emqueue(222, 2) | |
pq.emqueue(333, 0) | |
pq.dequeue() | |
## 优先级队列 | |
## 例题 | |
## 实现 |
- 只能在前端进行删除
- 只能在后盾进行插入
先进先出(fifo first in first out)