前端必会算法(二)

链表

数组与链表的区别

数组的创建需要申请一段连续的内存空间 (一整块内存),并且大小是固定的,当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如 2 倍,然后将原数组中的元素复制过去)

在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

而链表呢

  • 存储多个元素,另外一个选择就是使用链表。

  • 不同于数组,链表中的元素在内存中不必是连续的空间。

  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用 (有些语言称为指针) 组成。

  • 链表优点:

    内存空间不必是连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。

    链表不必在创建时就确定大小,并且大小可以无限延伸下去。

    链表在插入和删除数据时,时间复杂度可以达到 O(1) ,相对数组效率高很多。

  • 链表缺点:

    访问任何一个位置的元素时,需要从头开始访问。(无法跳过第一个元素访问任何一个元素)

    无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。

    虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。

常见操作

  • append(element) 向链表尾部添加一个新的项。
  • insert(position, element) 向链表的特定位置插入一个新的项。
  • get(position) 获取对应位置的元素。
  • indexOf(element) 返回元素在链表中的索引。如果链表中没有该元素就返回 -1。
  • update(position, element) 修改某个位置的元素。
  • removeAt(position) 从链表的特定位置移除一项。
  • remove(element) 从链表中移除一项。
  • isEmpty() 如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false
  • size() 返回链表包含的元素个数,与数组的 length 属性类似。
  • toString() 由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。

单向链表

整体封装

kotlin 复制代码
function LinkedList() {
  this.head = null;
  this.length = 0;
  function Node(data) {
    this.data = data;
    this.next = null;
  }
}

append方法

ini 复制代码
this.append = function (data) {
    const node = new Node(data);
    if (this.length === 0) {
      this.head = node;
    } else {
      let current = this.head;
      while (current.next !== null) {
        current = current.next;
      }
      current.next = node;
    }
    this.length++;
  };

初始化的时候,链表没有数据,所以第一个加入的数据就替代了头部的

通过while循环,找到当前链表的最后一个节点,并将这个节点的next指向将要添加的新节点

最后一个节点指向null

toString方法

ini 复制代码
 this.toString = function () {
    let current = this.head;
    let result = "";
​
    while (current !== null) {
      result += current.data + "->";
      current = current.next;
    }
    return result + "null";
  };

定义一个伪指针,从head开始,使用while进行循环,直到指针指向最后一个null的时候停止

再次期间,我们每次循环将结点的值拼接到result中,并且将伪指针指向下一个。当循环结束时将result返回,如此便是返回的最后的结果

insert方法

ini 复制代码
this.insert = function (data, position) {
    // 对poisition进行位置判断
    if (position < 0 || position > this.length) return false;
    //创建新的结点
    let node = new Node(data); 
    //分情况进行判断
    if (position === 0) {
        //插入到第一个结点前  //注意设置的顺序要对
      node.next = this.head; //将新的结点的下一个结点为当前head指向的结点
      this.head = node;//再将head指向新的结点  
    } else {
        //剩下就是在第一个之后插入的
        //我们需要考虑,假设在第二个结点之前插入,那么第一个结点的next要指向新结点,新节点的next要指向当前第二个结点
      let index = 0;
      let current = this.head;
      let previous = null; // 用于引用前一个节点
      while (index++ < position) {
        previous = current; // 更新前一个节点
        current = current.next;
      }
      node.next = current.next;
      previous.next = node;
    }
    this.length++;
    return true;
  };

getPoition方法

ini 复制代码
this.getPoition = function (position) {
    if (position < 0 || position >= this.length) return false;
​
    let current = this.head;
    let index = 0;
    while (index++ < position) {
      current = current.next;
    }
    return current.data;
  };

indexOf方法

ini 复制代码
this.indexOf = function (data) {
    if (this.length === 0) return -1;
    let current = this.head;
    let index = 0;
    while (current) {
      if (current.data === data) {
        return index;
      }
      current = current.next;
      index++;
    }
    return -1;
  };

update方法

ini 复制代码
this.update = function (position, element) {
    if (position < 0 || position >= this.length) return false;
    let current = this.head;
    let index = 0;
    while (index < position) {
      current = current.next;
      index++;
    }
    current.data = element;
    return true;
  };

removeAt方法

ini 复制代码
this.removeAt = function (position) {
    if (position < 0 || position >= this.length) return null;
    let current = this.head;
    if (position === 0) {
      this.head = current.next;
    } else {
      let index = 0;
      let previous = null;
      while (index++ < position) {
        previous = current;
        current = current.next;
      }
      previous.next = current.next;
    }
    this.length--;
    return current.data;
  };

remove方法

kotlin 复制代码
this.remove = function (ele) {
    const index = this.indexOf(ele);
    return this.removeAt(index);
  };

isEmpty与size方法

kotlin 复制代码
 this.isEmpty = function () {
    return this.length === 0 ? false : true;
  };
  this.size = function () {
    return this.length;
  };

reverse方法

ini 复制代码
this.reverse = function () {
    let previous = null; // 初始化前一个节点为null
    let current = this.head; // 当前节点从头部开始
    let next = null; // 临时节点用于存储下一个节点
​
    // 遍历链表,直到当前节点为null
    while (current !== null) {
      next = current.next; // 存储下一个节点
      current.next = previous; // 反转当前节点的指针
      previous = current; // 前一个节点移动到当前节点
      current = next; // 当前节点移动到下一个节点
    }
    // 最后,将头节点设置为原来的尾节点,即previous
    this.head = previous;
  };

测试

js 复制代码
​
const list = new LinkedList();
​
list.append("abcd");
list.append("efgh");
list.append("lonb");
console.log(list.toString());
list.insert("asdass", 0);
console.log(list.toString());
console.log(list.getPoition(2));
console.log("====================================");
console.log(list.indexOf("efgh"));
console.log("====================================");
console.log(list.update(0, "000"));
console.log("====================================");
console.log(list.removeAt(3));
console.log("====================================");
console.log(list.toString());
console.log("====================================");
console.log(list.reverse());
console.log(list.toString());
相关推荐
Microsoft Word8 分钟前
c++基础语法
开发语言·c++·算法
吃杠碰小鸡10 分钟前
commitlint校验git提交信息
前端
天才在此21 分钟前
汽车加油行驶问题-动态规划算法(已在洛谷AC)
算法·动态规划
虾球xz41 分钟前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇1 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒1 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员1 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐1 小时前
前端图像处理(一)
前端
莫叫石榴姐1 小时前
数据科学与SQL:组距分组分析 | 区间分布问题
大数据·人工智能·sql·深度学习·算法·机器学习·数据挖掘
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端