Code前端首页关于Code前端联系我们

前端算法:时间、空间复杂度与数据结构栈和队列实现

terry 2年前 (2023-09-27) 阅读数 91 #数据结构与算法

1.做这个系列的理由和计划

前段时间遇到了一个实际问题,如何最优选币。数学描述如下: 求解线性方程问题:

1x + 5y +10z + 15k + 20*j = 16;我刚刚开始思考如何求解多元方程并转向矩阵解。结果我越研究它,它就变得越复杂。后来我发现这和背包问题很相似;然后复习了一些算法和数据结构的知识,这个系列就诞生了。我的计划是一一介绍相关的数据结构以及如何使用它们。一旦实现了JavaScript,然后是一些经典的应用程序和示例;废话不多说,先从最基本的开始:复杂度、栈、队列;

2. 复杂性

说到算法和数据结构,无非是解决两个问题两个问题: 1、如何更快、更准确地得到预期的结果; 2、如何用尽可能少的资源达到预期效果;这两个问题就是我们平时讲的性能问题,这两个问题都解决了。这解决了大多数性能问题;那么两者如何衡量或选择呢?有时您无法同时实现两者。如果不是为了占用更少的资源而放弃时间,那不就是牺牲一些资源存储来更快地得到预期的结果吗?这包括空间复杂度和时间复杂度

空间复杂度:这意味着为了实现某个功能或方法,我们需要占用我们的空间。在许多情况下,计算机内存资源可能不是优先考虑的问题。只要速度能够达到,例如sort中的counting sort就会定义一个中间数组。字段长度是要排序的字段的最大值。毫无疑问是的。需要更多内存;

时间复杂度:时间复杂度以大写O表示。虽然我们无法从代码中准确计算出执行时间,但也是不现实的,因为这个时间与操作系统和硬件有关,所以通常会有一个估计表达的价值;最喜欢的一点是看代码被重复执行了多少次 O(n);

O(1):这种情况下,无论计数方法如何执行,++n在方法中只执行一次,不会随着参数n的增加而改变,这个时间复杂度是恒定的;

function count (n) { return ++n; } count(n);

O(n):这种情况是随机的,随着参数的变化,代码执行次数呈线性变化;

function consoleFn(n) { 
    for(let i = 0; i < n; i++){
        console.log(i) 
    }
}

O(log2n): 我们称之为对数复杂度;就像二分查找一样; 2^i = n => 原来i = log2n;

function consoleFn(n) { 
    let i = 1;
    while(i < n){
        i = i * 2; 
        console.log(i); 
    } 
}

O(nlog2n):线性对数;作为快速排序的时间复杂度

function consoleFn(n) {  
    for(let j = 0; j < n; j++) { 
        let i = 1;
        while(i < n){
            i = i * 2;
            console.log(i);
        }
    }
}

O(n2): 这种情况是执行次数随着n的增加,会成倍增加;

function consoleFn(n) {
   for(let i = 0; i < n; i++){
     for(let j = 0; j < n; j++){
      console.log(i)
     }
   }
}

前端算法:时间、空间复杂度及数据结构栈、队列的实现

数据结构:

1。堆垛机

堆垛机是一套遵循 FILO(先进后出)原则的有序装置。新添加的元素或要移除的元素都存储在栈的同一端,称为栈顶,另一端称为栈底。在堆栈中,新元素位于堆栈顶部附近,旧元素位于底部附近。

前端算法:时间、空间复杂度及数据结构栈、队列的实现

如何实现栈数据结构?

首先,我们定义一些栈API:

push(val): 向栈顶添加一个元素;

size(): 返回堆栈大小;

isEmpty():如果堆栈为空则返回

pop():出栈

clear():

:

返回顶部元素堆栈

export default class Stack {
    constructor() {
        this.items = [];
    }
    push(val) {
        this.items.push(val);
    }
    size() {
        return this.items.length;
    }
    isEmpty() {
        return this.items.length === 0;
    }
    pop() {
        return this.items.pop();
    }
    peek() {
        return this.items[this.items.length - 1]
    }
    clear() {
        this.items = [];
    }
}

操作简单:

const stack = new Stack();
console.log(stack.isEmpty()); // true
stack.push(5);
stack.push(8);
stack.push(11);
stack.push(15);
console.log(stack.isEmpty()); // false
console.log(stack.size());// 4
console.log(stack.peek());//15
stack.pop();// 15
console.log(stack.size());// 3
console.log(stack.peek());//11

前端算法:时间、空间复杂度及数据结构栈、队列的实现

您是否在想:堆栈的实际使用?

在Vue中解析模板时,会使用栈来判断模板字符是否合法。这和很多编辑器在编写代码时检查我们编写的 HTML 元素是否关闭的原理是一样的。

2。正面;

队列是一组有序的项目,遵循先来先服务(FIFO,也称为先来先服务)策略。队列在末尾添加新元素并从顶部删除元素。最后添加的元素必须位于队列的末尾。事实上,队列最常见的例子就是队列。

前端算法:时间、空间复杂度及数据结构栈、队列的实现

如何实现队列数据结构?

首先,我们定义一些队列API:

enqueue(val):向队列中添加一个元素

size(): 返回队列的大小;

isEmpty():返回队列是否为空。相当常见,像很多任务事件队列、vue更新队列、vue mixin合并队列,都是基于先进先出先出原则

思考:我们在做什么有没有更好的方法实现上面的队列和堆栈?上述实现有什么问题吗?

3。双边队列

双边队列是一种特殊的队列,它允许我们同时从前端和后端添加和删除元素。它基于基本前端定义的变体

前端算法:时间、空间复杂度及数据结构栈、队列的实现

API 接口:

addFront(element): 此方法将一个新元素添加到双边队列的前端。

addBack(element): 此方法向后端双端队列添加一个新元素。

removeFront(): 此方法从双端队列的前面删除第一个元素。

removeBack(): 此方法从后端双端队列中删除第一个元素。

peekFront(): 此方法返回双端队列开头的第一个元素。

peekBack(): 此方法返回后端双端队列的第一个元素。

代码实现:

import Queue from "./QueueArr";

export class Deque extends Queue{
    constructor() {
        super();
    }
    addFront(element){
        this.items.unshift(element);
    }
    addBack(element) {
        this.enqueue(element);
    }
    removeFront() {
        return this.dequeue();
    }
    removeBack() {
        return this.items.pop();
    }
    peekFront() {
        return this.items[0];
    }
    peekBack() {
        return this.items[this.items.length - 1];
    }
}

简单控制:

const deque = new Deque();
deque.addFront('b');
deque.addBack('c');
deque.addFront('a');
deque.addBack('d');
console.log(deque.size()) // 4
console.log(deque.peekFront());;// a
console.log(deque.peekBack());// d
console.log(deque.removeFront());//a
console.log(deque.removeBack());//d

使用双边队列:

检查回文;

检查一段英文是否不是回文。一般来说,比较简单的方法是转为数组,然后倒序转为字符串,看两者是否相等来判断是否是回文;例如: ababa 倒序后仍然是 ababa;这是一个回文。同样,我们也可以使用双队列来判断是否是回文。它不是回文;总体思路就是将一个字符串放入一个双端队列中,然后提取队列头和尾部看是否相等,直到队列列出的元素小于等于1个元素为止。代码实现如下:

import {Deque} from "./Deque";

export const palindromeChecked = (str) => {
    if(!str){
        return false;
    }
    const deque = new Deque();
    const strs = str.toLowerCase().split('');
    for (let i = 0; i < strs.length; i++) {
        deque.addBack(strs[i]);
    }
    let start = '', end = '', isTrue = true;
    while (deque.size() > 1 && isTrue) {
        start = deque.removeFront();
        end = deque.removeBack();
        if (start != end) {
            isTrue = false;
        }
    } 
    return isTrue
}
console.log(palindromeChecked('adfds'));// false
console.log(palindromeChecked('adada'));// true

思考:学习了队列和栈之后,如何利用队列和栈实现像cal('1+2-3+6/3')这样的四算术运算,结果是 16

思考与解答部分

确定标签是否已关闭

想法:

1。对字符串

2 执行标签分析。创建标签栈,当碰到开始标签时将其插入栈中,当遇到结束标签

3 时将其弹出。匹配最后,检查托盘是否为空。如果为空,则表示已关闭。如果标签仍然存在,则说明该标签无法关闭。

队列和堆栈的更好实现

我们使用了上面的堆栈和队列实现。数组用于向数组中删除和添加元素,数组操作实际上相当耗时。和其他静态语言一样,JavaScript 的数组操作其实很复杂,但是 js 引擎帮我们做了这些事情,比如从数组头删除或者添加一个元素,其他数组元素其实都得在内部进行翻译。例如,要向数组中插入一个元素,首先需要增加长度,然后将所有元素后移一个位置,使表头留空,然后放置要添加的元素。 ,所以最好使用对象而不是数组来实现

export default class Queue {
    constructor() {
        this.counter = 0;// 计数器计算队列大小
        this.items = {}; // 队列存储
        this.lowestCount = 0; // 队列头
    }
    // 返回队列首位
    peek() {
        return this.items[this.lowestCount];
    }
    enqueue(element) {
        this.items[this.counter] = element;
        this.counter++;
    }
    dequeue() {
        if (this.isEmpty()) {
            return undefined;
        }
        const result = this.items[this.lowestCount];
        delete this.items[this.lowestCount];
        this.lowestCount++;
        return result;
    }
    isEmpty() {
        return this.size() === 0;
    }
    size() {
        return this.counter - this.lowestCount;
    }
    clear() {
        this.counter = 0;
        this.lowestCount = 0;
        this.items = {};
    }
}

四种算术运算

想法:

1。实现词法分析,分析词法队列:tokens;

2。获取标记 要处理,请定义一堆数字和一堆操作数

3。当您击中一个数字时,将该数字放入数字堆栈中。当他遇到操作员时,确定是否可以进行操作。如果该操作可以执行,则将从号码堆栈中删除。从栈中选择2个数字,然后将运算符从运算符栈中取出,然后执行相应的运算并将运算结果放入数栈中作为下一个操作数

1+2-3+6/3 =>标记 = [

'{类型:'数字',值:'1'},

{类型:'运算符',值:'+'},

'{类型:'数字' ,value:'2'},

{type: '运算符',value: '-'},

'{type: '数字',value:'3'},

{type: '运算符', value: '+'},

'{ type: 'number', value:'6'},

{type: 'operator', value: '/'},

'{type: 'number',value:'3'},

]'

前端算法:时间、空间复杂度及数据结构栈、队列的实现

相关源码实现地址

源码点击这里

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门