算法的定义:
- 一个有限指令集,每条指令的描述不依赖与语言。
- 接受一些输入
- 产生输出
- 一定在有限步骤后终止
算法的通俗理解
- 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)