JavaScript中没有队列这个数据结构,但是可以用数组来实现所有的功能。
队列是一个先进先出的数据结构,一般JavaScript中采用队列解决问题时会用到
入队push ()
:在数组的尾部添加元素出队shift ()
:移除数组中第一个元素queue (0)
:取数组的第一个元素isEmpty ()
:确定队列是否为空size ()
:获取队列中元素的数量1️⃣栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进⬇后出⬆的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
2️⃣JavaScript中没有栈,但是可以用Array实现栈的功能。
3️⃣栈中数组长度减一即为栈尾元素,也就是最后进入的那个元素,最先出去的那个元素
JavaScript中对栈的操作一般会使用到
push()
方法,将元素压入栈顶pop()
方法,从栈顶弹出(删除)元素,并返回该元素peek()
方法,返回栈顶元素,不删除clear()
方法,清空栈length
拿到栈中元素数量链表是由多个元素组成的列表,链表中的元素储存不连续,用next指针连接在一起。
数组:增删非数组元素需要移动元素
链表:增删非首尾元素不需要移动元素只需要更改next的指向即可
链表是一个链式数据结构,每个节点由两个信息组成:节点的数据和指向下一个节点的指针。链表和传统数组都是线性数据结构,具有序列化的存储方式。
操作 | 数组 | 链表 |
---|---|---|
内存分配 | 编译和序列化过程中静态分配 | 运行过程中动态分配 |
获取元素 | 从索引中读取 | 读取队列中的所有节点,直到特定元素,较慢 |
增加/删除元素 | 顺序增加删除,较慢 | 动态分配,内存消耗小,速度快 |
空间结构 | 一维或者多维 | 单边/多边,循环链表 |
JavaScript中没有链表,但是可以用object来模拟链表
const a = { val: 'a' }
const b = { val: 'b' }
const c = { val: 'c' }
const d = { val: 'd' }
// a的next属性指向b
a.next = b;
b.next = c;
c.next = d;
// 这个嵌套的object就相当于一个链表
遍历链表
遍历链表就是跟着链表从链表的头元素(head)一直走到尾元素(但是不包含链表的头节点,头通常用来作为链表的接入点)
♂️还有一个问题,链表的尾元素指向一个null节点
// 声明一个指针,指向a
let p = a
// 当p还有值得时候
while (p) {
console.log(p.val)
// 不断得让p指向下一个位置
p = p.next
}
插入链表
// 在链表中插入值
const f = { val: 'f' }
c.next = f
f.next = d
删除链表
// 删除值
c.next = d
集合:一种无序且唯一的数据结构,集合区别队列、栈、链表最大的区别就是元素不能重复
JavaScript中ES6中新增了集合这种数据结构,可以通过实例化Set对象来创建集合const set = new Set()
集合常用来解决的问题:
set = new Set(arr)
,返回一个不含重复元素的集合set.has(num)
,判断num是否在集合中const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
const set3 = new Set([...set1].filter(item = > set2.has(item)))
//set3{4}
const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
let set3 = new Set([...set1, ...set2]);
// set3 {1, 2, 3, 4}
const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
let set3 = new Set([...set1].filter(x => !set2.has(x)));
// set3 {1,2}
filter() 方法创建一个新的数组,新数组返回指定数组中符合条件的所有元素
...
是ES6中新增的扩展运算符,对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。[...set]
的意思就是将集合转换为数组
前端树结构还是比较常见的,例如级联选择、层级目录等都是树形结构。
javascript中没有树这个数据结构,但是一般用object和arrey来模拟树。
const tree = {
values:a,
children:[
{
values:b,
children:[
{
values:d,
children:[]
},
{
values:e,
children:[]
},{
values:f,
children:[]
}
]
},
{
values:c,
children:[
{
values:g,
children:[]
},
{
values:h,
children:[]
}
]
}
]
}
树的常用遍历方式
上图中的树使用深度优先遍历
const deepNood = (node)=>{
//打印根节点
console.log(node.values);
//遍历子节点
node.children.forEach(child => {
//递归
deepNood(child)
})
}
deepNood(tree)
//a b d e f c g h
优先访问距离根节点最近的节点。广度优先遍历需要使用到队列这个数据结构
上面的树使用广度优先遍历
const breadth = (node)=>{
//将树加入队列(整个object对象,)
const arr = [node];
//队列是否为空
while(arr.length > 0){
//从队列中取出根节点
const val = arr.shift()
console.log(val.values)
//遍历子节点
for(let child of val.children){
//将子节点加入队列
arr.push(child)
}
// 打开console.log(arr),就能看出不断的将childern入队,然后再将队头取出
}
}
breadth(tree)
//a b c d e f g h
二叉树
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成
树的每个节点最多只能有两个子节点
js中自然也没有二叉树这个数据结构,一般还是用object对象来模拟二叉树
二叉树代码实现
const tree = {
val:1,
left:{
val:2,
left:{
val:4,
left:null,
right:null
},
right:{
val:5,
left:null,
right:null
}
},
right:{
val:3,
left:{
val:6,
left:null,
right:null
},
right:
{
val:7,
left:null,
right:null
}
}
}
二叉树遍历(递归)
(1)前序遍历
DLR:根节点——左子树——右子树
每次遍历到一个节点都重复一次前序遍历
代码实现
const perorder = (node) =>{
//如果当前节点值为空则返回
if(!node) return;
//打印根节点
console.log(node.val);
//递归左子树
perorder(node.left);
//递归右子树
perorder(node.right);
}
perorder(tree)
//1 2 4 5 3 6 7
(2)中序遍历
LDR:左子树——根节点——右子树
每次遍历到一个节点都重复一次中序遍历
代码实现
const perorder = (node) =>{
if(!node)return;
perorder(node.left);
console.log(node.val);
perorder(node.right);
}
perorder(tree)
//4 2 5 1 6 3 7
(3)后序遍历
LRD:左子树——右子树——根节点
每次遍历到一个节点都重复一次后序遍历
代码实现
const perorder = (node) =>{
if(!node)return;
perorder(node.left);
perorder(node.right);
console.log(node.val);
}
perorder(tree)
//4 5 2 6 7 3 1
注意:
前序遍历第一个为根节点
中序遍历根节点左边为左子树,右边为右子树
后序遍历最后一个为根节点
堆是一种特殊的完全二叉树
每层节点全部都填满,最后一层如果不是满的,只能缺少右边的节点,下图为最大堆的示例
所有的节点都大于等于(最大堆)或者小于等于(最小堆)他的子节点
js中的堆
js中通常用数组来表示堆,按照广度优先的方式存储在数组中
根据广度优先的特点可以得到子节点在数组中的存储位置(index代表当前节点在数组中的下标)
左侧子节点的位置是2 * index+1
右侧子节点的位置是2 * index+1
父节点位置是(index - 1)/ 2
第k个最大元素
主要方法 :
插入元素(最小堆)
构建一个最小堆,并将元素依次添加到堆中,
删除堆顶
当堆的容量超过了k就删除堆顶
获取堆顶
插入结束后堆顶就是第k个最大元素
获取堆大小
数组长度就是堆的大小
代码实现最小堆类
class minHeap{
constructor(){
this.heap = [];
}
// 获取父节点在数组中的下标
getParentIndex(i){
return Math.floor((i-1)/2)
//二进制写法
// return (i - 1) >> 1
}
// 获取左侧节点在数组中的下标
getLeftIndex(i){
return i * 2 + 1
}
// 获取右侧节点在数组中的下标
getRightIndex(i){
return i * 2 + 2
}
// 定义节点交换的方法
swap(i1,i2){
const temp = this.heap[i1];
this.heap[i1] = this.heap[i2];
this.heap[i2] = temp;
}
// 定义上移方法
shiftUp(index){
if(index==0){
return;
}
// 不停的和父节点交换位置,直到小于等于父节点的值
const parentIndex = this.getParentIndex(index)
if(this.heap[parentIndex] > this.heap[index]){
this.swap(parentIndex,index)
this.shiftUp(parentIndex)
}
}
// 定义下移方法
shiftDown(index){
const liftIndex = this.getLeftIndex(index)
const RightIndex = this.getRightIndex(index)
if(this.heap[liftIndex] < this.heap[index]){
this.swap(liftIndex,index)
this.shiftDown(liftIndex)
}
if(this.heap[RightIndex] < this.heap[index]){
this.swap(RightIndex,index)
this.shiftDown(RightIndex)
}
}
// 插入方法
insert (value){
// 将元素插入到数组的最后一位
this.heap.push(value)
// 因为插入的元素为数组的最后一位,所以传入的参数是heap.length-1
this.shiftUp(this.heap.length-1)
}
// 删除方法
delect(){
// pop可以删除数组的最后一个元素并返回这个元素
this.heap[0] = this.heap.pop();
this.shiftDown(0)
}
//获取堆顶
peek(){
return this.heap[0]
}
//获取堆大小
size(){
return this.heap.length
}
}
// 实例化一个最小堆
const h = new minHeap();
//插入几个数字测试
h.insert(3);
h.insert(2);
h.insert(1);
//虽然不能保证是按照最小堆来排列,但是能保证堆顶最小,也就是父节点的元素一定大于子节点的元素