前端 用js封装部分数据结构

文章目录

Stack

栈,先进后出,后进先出。用数组来进行模拟,通过push存入,通过pop取出。

typescript 复制代码
class Stack {

  // 带#表示这是一个私有变量,不能在外部对其进行直接修改
  #items = []

  push(val) {
    this.#items.push(val);
  }

  pop() {
    return this.#items.pop();
  }

  getLength() {
    return this.#items.length;
  }

  clear() {
    this.#items = [];
  }

  peek() {
    return this.#items.at(-1) || this.#items[this.#items.length - 1];
  }

  isEmpty() {
    return this.#items.length === 0;
  }
}

const stack1 = new Stack();
stack1.push(1);
stack1.push(2);
console.log(stack1);
stack1.push(4);
console.log(stack1);
console.log(stack1.pop());


// 用辗转相除法,将一个十进制转化为任意进制的数 2到十六进制(一般为)

function tansformTenToNumber(targetNumber,base) {
  let num = targetNumber;
  const stack = new Stack();
  let result = '';
  const stringMap='0123456789ABCDEF';

  while(num > 0) {
    stack.push(stringMap[num % base]);
    num = Math.floor(num / base);
  }

  while(stack.getLength() !== 0) {
    result = result + stack.pop()
  }

  return result
}

console.log(tansformTenToNumber(50,2)) // 110010
console.log(tansformTenToNumber(1501,16)) // 5DD

队列

为什么用对象来封装队列而不用数组呢,因为当数组shift的时候,会造成一定的性能问题。

当你将数组第一个移动,实际上,整个数组所有的子元素都会跟着移动。

typescript 复制代码
class Queue {
// 带#表示这是一个私有变量,不能在外部对其进行直接修改
    #items = {};
    #min = 0;
    #max = 0;

    push(val) {
        this.#items[this.#max] = val
        this.#max++
    }

    shift() {
        // 如果已经为空,就没有东西可以取
        if(this.isEmpty()) {
            return undefined
        }

        // 取出数字最小的,先进先出麻
        let result = this.#items[this.#min]
        this.#min++
        return result
    }

    clear() {
        this.#items = {}
        this.#max = 0;
        this.#min = 0;
    }

    size() {
        return this.#max - this.#min
    }

    isEmpty() {
        return this.size() === 0
    }
}

const queue = new Queue()

queue.push('deng')
queue.push('xi')
queue.push('yang')
queue.push('xi')

console.log(queue.size()) // 4
console.log(queue.isEmpty()) // false
console.log(queue.shift()) // deng
console.log(queue.shift()) // xi
console.log(queue.shift()) // yang
console.log(queue.shift()) // xi
console.log(queue.shift()) // undefined
console.log(queue.isEmpty()) // true

queue.push('deng')
queue.push('xi')



console.log(queue.shift()) //deng
console.log(queue.clear())
console.log(queue.isEmpty()) // true
queue.push('yang')
queue.push('xi')
console.log(queue.size()) // 2



// 击鼓传花
// 参与者按照一定顺序,一次被选中,当鼓声停止时,被选中的人淘汰,
// 直到场上剩下最后一人为止。

// 接受两个参数,第一个参数,参与人的数组, 
// 第二个参数,鼓声每次敲击多少下,敲击多少次后,必须淘汰拿花的人
function passingFlower(list, num) {
    const queue = new Queue()
    // 先将list 全部放入队列中
    for(let i =0,len=list.length;i < len; i++) {
        queue.push(list[i])
    }

    // 当只剩一个人的时候停止循环
    while(queue.size() > 1) {
        for(let j=0, len=num; j < len; j++) {
            // 每一次循环,都要将队列最前面的人放到最后面去
            let result = queue.shift()
            queue.push(result)
        }
 // 当循环结束,淘汰掉一个人,直接从队列最前面拿掉,不放入队列最后
        queue.shift()
    }

    // 将最后一人作为获胜者返回
    return queue.shift()
}

const winner = passingFlower(['d','e','n','g','x','i'], 7)

console.log(winner) // n

链表

为了解决队列里面,移动一个,后面的所有元素都会跟着移动的问题,出现了新的数据结构,链表。链表是不定长度的数据,但与数组相比,不利于数据的搜索。

typescript 复制代码
// 链表中的单个节点
class LinkElement {
  constructor(value) {
    this.value = value;
    this.next = null;
  }

  setNext(next) {
    this.next = next;
  }

  getNext() {
    return this.next;
  }
}
// 链表
class LinkList {
  constructor() {
    this.count = 0;
    this.head = null;
  }

  // 追加链表到里面,放置到最后一个
  push(val) {
    let link = new LinkElement(val);
    if (this.head === null) {
      // 让头指向第一个节点
      this.head = link;
    } else {
      let current = this.head;
      // 如果不为空,就要一直找到链表的最后一个,然后让最后一个的next指向新的节点
      while (current.getNext() !== null) {
        current = current.getNext();
      }
      current.setNext(link);
    }

    // 计算加一
    this.count++;
  }

  // 删除制定位置
  removeAt(index) {
    // 如果是空的,就直接返回
    if (index < 0 || index >= this.count) {
      return undefined;
    }

    let current = this.head;
    if (index === 0) {
      // 如果是0,就让head指向第二个节点
      this.head = current.getNext();
    } else {
      // 找到 index的上一个link节点
      let previous = this.findAt(index - 1);
      // current 就是 index 就是要删除的节点
      current = previous.getNext();
      // 让 index - 1 的节点的next指向 index + 1 的节点,这样就把 index 的节点删除了
      previous.setNext(current.getNext());
    }

    // 计算减一
    this.count--;
    // 将删除的节点返回
    return current;
  }

  // 找到指定位置的节点
  findAt(index) {
    if (index < 0 || index >= this.count) {
      return undefined;
    }

    let current = this.head;
    for (let i = 0; i < index; i++) {
      // 当循环到最后,current 是 index - 1,再getNext 就是 index
      current = current.getNext();
    }

    return current;
  }

  // 根据val值,找第一个节点的索引值
  indexOf(val) {
    let current = this.head;
    // 如果没找到返回 -1
    let result = -1;

    for (let i = 0; i < this.count; i++) {
      if (current.value === val) {
        result = i;
      }
      // 不断的往下一个链表找
      current = current.getNext();
    }

    return result;
  }
  // 删除指定val值的节点
  removeVal(val) {
    // 找到对应值的index
    let currentIndex = this.indexOf(val);
    if (currentIndex === -1) {
      return undefined;
    } else {
      // 如果有就利用removeAt删除
      return this.removeAt(currentIndex);
    }
  }

  // 链表的插入,在指定位置插入元素,第一个参数是你要插入元素的值,第二参数是你要插入链表的位置
  insert(val, index) {
    const link = new LinkElement(val);

    // 判断index 是否合理
    if (index < 0 || index > this.count) {
      return false;
    }

    // 如果是0,就让head指向新的节点
    if (index === 0) {
      let current = this.head;
      // 让新的节点的next指向第一个节点
      link.setNext(current);
      // 然后让指针指向新的第一个节点
      this.head = link;
    } else {
      // 找到index - 1 的节点
      let previous = this.findAt(index - 1);
      // 让新的节点的next指向 index 的节点
      link.setNext(previous.getNext());
      // 让 index - 1 的节点的next指向新的节点
      previous.setNext(link);
    }

    // 将count + 1
    this.count++;
  }

  size() {
    return this.count;
  }

  isEmpty() {
    return this.size() === 0;
  }

  getHead() {
    return this.head;
  }
}

const linkList = new LinkList();

linkList.push(1);
linkList.push(2);
linkList.push(3);
linkList.push(4);
linkList.insert(0, 0);
console.log(linkList.indexOf(1));

linkList.removeAt(1);
console.log(linkList);

linkList.removeVal(2);
console.log(linkList);

Set

set用对象来封装,key和value保存一致。这样就能确保Set里面的内容不会被重复保存。如果要想确保万无一失,可以用symboal模拟set,这样可以在set里面保存任意内容。

typescript 复制代码
class MySet {
  #object = {};

  delete(value) {
  // 删除之前先判断该值是否存在
    if (this.has(value)) {
      delete this.#object[value];
      return true;
    }

    return false;
  }

  add(value) {
   // 添加之前也得先判断是否已经存在了,不能重复添加
    if (!this.has(value)) {
      this.#object[value] = value;
      return true;
    }

    return false;
  }

  has(value) {
    return this.#object.hasOwnProperty(value);
  }

  clear() {
    this.#object = {};
  }

  size() {
    return Object.values(this.#object).length;
  }

  values() {
    return Object.values(this.#object);
  }
}

const set = new MySet();
set.add(1);
set.add(2);

console.log(set.values()); // [1, 2]

set.delete(2);

console.log(set.values()); // [1]
console.log(set.size()); // 1

console.log(set.has(1)); // true

set.clear();
console.log(set.values()); // []

set 用来数组去重

typescript 复制代码
const arr1 = [1,2,3,4,4,5]
const arr2 = [3,4,5,6]

const result = Array.from(new Set(arr1))
console.log(result) // [ 1, 2, 3, 4, 5 ]

set用来取两个数组的并集

typescript 复制代码
const arr1 = [1,2,3,4,4,5]
const arr2 = [3,4,5,6]

// 并集 将两个数组重复的内容,全部去重
const union = Array.from(new Set([...arr1, ...arr2]))

set用来取两个数组的交集

typescript 复制代码
const arr1 = [1,2,3,4,4,5]
const arr2 = [3,4,5,6]
const set2 = new Set(arr2)
// 交集 将两个数组重复的内容,全部保留
const intersection = arr1.filter(item => set2.has(item))

set用来取两个数组的差集

typescript 复制代码
const arr1 = [1,2,3,4,4,5]
const set1 = new Set(arr1)
const arr2 = [3,4,5,6]
const set2 = new Set(arr2)
// 差集 取出arr1对arr2的差集 // 1 2
const intersection = arr1.filter(item => !set2.has(item))
// 差集 取出arr2对arr1的差集 // 6
const intersection2 = arr2.filter(item => !set1.has(item))

字典

typescript 复制代码
class NewMap {
  #obj = {};

  keyToString(key) {
    let KeyStr = "";
    switch (key) {
      case null:
        KeyStr = "null";
        break;
      case undefined:
        KeyStr = "undefined";
      default:
        KeyStr = JSON.stringify(key);
        break;
    }

    return KeyStr
  }

  // 社区比较受欢迎的计算hashcode方式之一
  hashCode(key) {
    let mapKey = this.keyToString(key) || '';
    let hash = 5381;
    for(let i=0,len=mapKey.length; i<len; i++) {
      hash = (hash * 33) + mapKey.charCodeAt(i);
    }
    return hash % 1013
  }

  set(key, value) {
    // 通过hashcode来保存key
    this.#obj[this.hashCode(key)] = {
      key,
      value,
    };
  }

  remove(key) {
    if(this.#obj[this.hashCode(key)]) {
      delete this.#obj[this.hashCode(key)]
      return true
    }
    return false
  }

  has(key) {
    return this.get(key) !== undefined
  }

  get(key) {
    if(this.#obj[this.hashCode(key)]) {
      return this.#obj[this.hashCode(key)].value
    }
  }

  keyValues() {
    return Object.entries(this.#obj)
  }

  size() {
    return this.keyValues().length
  }
}

const map1 = new NewMap()

map1.set(null,null)
map1.set(undefined,undefined)
map1.set({a:1},{a:1})
console.log(map1.keyValues())
console.log(map1.has({a:1})) // true

console.log(map1.get({a:1})) // {a:1}

console.log(map1.size()) // 3
相关推荐
前端大卫25 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘41 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare42 分钟前
浅浅看一下设计模式
前端
Lee川1 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端