什么是数据结构
数据结构实在计算机中组织和存储数据的一种特殊方式,是的数据可以高效的的别访问或者修改。准确的说,数据结构是数据的集合,表示数据之间的关系,包括作用在数据上的函数或者操作。
为什么我们需要数据结构
- 数据是计算机科学当中最关键的实体,而数据结构则可以将数据以某种组织形式存储,因此,数据结构的价值不言而喻。
- 无论你以何种方式解决何种问题,你都需要处理数据——无论是涉及员工薪水、股票价格、购物清单,还是只是简单的电话簿问题。
- 数据需要根据不同的场景,按照特定的格式进行存储。有很多数据结构能够满足以不同格式存储数据的需求。
常见的八大数据结构
- 数组: Array
- 堆栈: Stack
- 队列:Queue
- 链表: Linked Lists
- 树: Trees
- 图: Graphs
- 字典树:Trie
- 散列表(哈希表): Hash Tables
在较高的层次上,基本有三种类型的数据结构:
- 堆栈和队列类似于数组的结构,仅在项目的插入和删除方式上有所不同。
- 链表、树、图结构的节点是引用到其他节点。
- 散列表依赖于三列韩式来保存和定位数据。
在复杂性方面:
- 堆栈和队列是最简单的,并且可以从中构建链表。
- 树和图是最复杂的,因为他们扩展了链表的概念。
- 散列表和字典树需要利用这些数据结构可靠地执行。
效率而言:
- 链表是记录和存储数据的最佳选择。
- 哈希表和字典树在搜索和检索数据方面效率最佳。
数组(你可能知道)
数组是最简单的数据结构,基本都能百度到,我们这里举一个例子,有个场景每个函数都运行10000次迭代
如:10000个随机密钥在10000个对象的数组中查找的效率对比图
[
{
id: "key0",
content: "第0次"
},
{
id: "key1",
content: "第1次"
},
{
id: "key2",
content: "第2次"
},
...
]
["key284", "key958", "key23", "key625", "key83", "key9", ... ]
for…in 为什么这么慢?像蜗牛
这个语法是我们常用,但是为啥子这么缓慢。根据是兼职发现比正常情况下慢了9倍还要多一点。why?这是因为for in 语法是第一个能够迭代对象键的javascript语法。循环对象键" { } “与在数组” [ ] "上的换换不同。因为引擎会执行一些额外的工作跟踪已经迭代的属性。
堆栈 Stack
堆栈是元素的集合,可以在顶部添加项目,思考一下常见的几个堆栈是列。
- 浏览器的历史记录
- 常见的撤销操作
- 递归以及其它遍历
根据这些场景,我们总结并解释一下堆栈
- 两个原则操作:push和pop;push是将元素添加到数组顶部,为pop是将他们从同一位置上删除。
- 遵循“后进,先出”,即LIFO。
堆栈的实现
我们写一个例子,用来颠倒堆栈的顺序;我们分别使用unshift和shift方法代替push和pop。
class Stack {
constructor(...items) {
this.reverse = false;
this.stack = [...items];
}
push(...items) {
return this.reverse
? this.stack.unshift(...items)
: this.stack.push(...items);
}
pop() {
return this.reverse ? this.stack.shift() : this.stack.pop();
}
}
const stack = new Stack(4, 5);
stack.reverse = true;
console.log(stack.push(1, 2, 3) === 5) // true
console.log(stack.stack ===[1, 2, 3, 4, 5]) // true
队列
在计算机科学中,一个队列(queue)是一种特殊类型的抽象数据类型或集合。集合中的实体按顺序保存。
而在前端开发中,最著名的队列使用当属浏览器/NodeJs中 关于宏任务与微任务,任务队列的知识。这里就不再赘述了。
以编程西乡而言,Queue可以描述为:
- 只要具有两个主要操作的数组:unshift和pop。
- 遵循先进先出,即FIFO。
队列的实现
我们写个小例子,用来颠倒队列的顺序。
class Queue {
constructor(...items) {
this.reverse = false;
this.queue = [...items];
}
enqueue(...items) {
return this.reverse
? this.queue.push(...items)
: this.queue.unshift(...items);
}
dequeue() {
return this.reverse ? this.queue.shift() : this.queue.pop();
}
}
链表
与数组一样,链表是按顺序存储数据元素。链表不是保留索引,而是指向其他元素。
类似于流程图,第一个节点称为头部,最后一个节点称为尾部。一目了然清晰无比。
单链表和双链表:
- 单链表是表示一系列节点的数据结构,其中每个节点指向列表中的下一个节点。
- 链表通常需要遍历整个操作列表,因此性能较差。
- 提高链表性能的一种方法是在每个节点上添加指向列表中上一个节点的第二个指针。
- 双向链表具有指向其前后元素的节点。
链表的优点:
- 链接具有常量时间 插入和删除,因为我们可以只更改指针。
- 与数组一样,链表可以作为堆栈运行。
链表的应用场景(客户端和服务器上都很有用)
- 在客户端上,像Redux就以链表方式构建其中的逻辑。
- React 核心算法 React Fiber的实现就是链表。
1、React Fiber之前的Stack Reconciler,是自顶向下的递归mount/update,无法中断(持续占用主线程),这样主线程上的布局、动画等周期性任务以及交互响应就无法立即得到处理,影响体验。
2、React Fiber解决过去Reconciler存在的问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务,每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续。
3、在服务器上,像Express这样的Web框架也以类似的方式构建其中间件逻辑。当请求被接收时,它从一个中间件管道输送到下一个,直到响应被发出。
单链表的实现
单链表的操作核心方法主要有:
- push(value) - 在链表的末尾/头部添加一个节点
- pop() - 从链表的末尾/头部删除一个节点
- get(index) 返回指定索引处的节点
- delete(index) - 删除指定索引处的节点
- isEmpty() - 根据列表长度返回true或false
- print() - 返回链表的可见表示
class Node {
constructor(data) {
this.data = data
this.next = null
}
}
class LinkedList {
constructor() {
this.head = null
this.tail = null
// 长度非必要
this.length = 0
}
push(data) {
// 创建一个新节点
const node = new Node(data)
// 检查头部是否为空
if (this.head === null) {
this.head = node
this.tail = node
}
this.tail.next = node
this.tail = node
this.length++
}
pop(){
// 先检查链表是否为空
if(this.isEmpty()) {
return null
}
// 如果长度为1
if (this.head === this.tail) {
this.head = null
this.tail = null
this.length--
return this.tail
}
let node = this.tail
let currentNode = this.head
let penultimate
while (currentNode) {
if (currentNode.next === this.tail) {
penultimate = currentNode
break
}
currentNode = currentNode.next
}
penultimate.next = null
this.tail = penultimate
this.length --
return node
}
get(index){
// 处理边界条件
if (index === 0) {
return this.head
}
if (index < 0 || index > this.length) {
return null
}
let currentNode = this.head
let i = 0
while(i < index) {
i++
currentNode = currentNode.next
}
return currentNode
}
delete(index){
let currentNode = this.head
if (index === 0) {
let deletedNode
currentNode.next = this.head
deletedNode = currentNode
this.length--
return deletedNode
}
if (index < 0 || index > this.length) {
return null
}
let i = 0
let previous
while(i < index) {
i++
previous = currentNode
currentNode = currentNode.next
}
previous.next = currentNode.next
this.length--
return currentNode
}
isEmpty() {
return this.length === 0
}
print() {
const list = []
let currentNode = this.head
while(currentNode){
list.push(currentNode.data)
currentNode = currentNode.next
}
return list.join(' => ')
}
}
未完待续…