数据结构和算法之数组和链表

一、数组

数组是一种线性数据结构,它是由一组连续的内存单元组成的,用于存储相同类型的数据。在JavaScript中,数组可以包含任意类型的数据,不只限于基本数据类型。

1.存储方式

在内存中,数组的元素是连续存储的,通过下标来访问数组中的元素。例如,一个包含整型数据的数组可以用类似以下方式来表示:

let arr = [1, 2, 3, 4, 5];

2.访问方式

通过数组下标来访问数组中的元素,数组下标从0开始。例如,要访问数组中的第三个元素,可以使用以下方式:

console.log(arr[2]); // 输出3

3.时间复杂度

  • 访问:由于数组中的元素是连续存储的,通过下标访问数组中的元素的时间复杂度是O(1)。因为可以直接通过下标计算出元素的内存地址。

4.插入删除操作的复杂度

-== 插入和删除操作对数组的时间复杂度为O(n)==。因为在插入或删除一个元素时,需要将数组中的元素进行移动以保持元素的连续性,这将导致额外的时间开销。例如,在数组的开头插入一个元素将需要将之后的元素全部向后移动一个位置,这将是一个线性操作。

综上所述,数组是一种灵活的数据结构,可以用于存储和访问大量元素。访问操作的时间复杂度是固定的O(1),但插入和删除操作的时间复杂度取决于操作的位置,可能是O(n)。

二、链表

链表是一种数据结构,它由多个节点组成,每个节点包含数据和指向下一个节点的指针。

从存储方式来看,链表的节点是通过指针相连接的方式存储在内存中。每个节点都包含一个指针,指向下一个节点的地址,这样就形成了节点之间的连接。

举例来说,可以用JavaScript实现一个简单的链表节点:

class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

接着可以创建一个链表, 将多个节点连接起来:

let node1 = new Node(1);
let node2 = new Node(2);
node1.next = node2;

从访问方式来看,链表的访问是通过依次遍历节点来访问元素的。为了访问特定位置的元素,需要从链表的头节点开始顺着指针找到对应位置的节点。

访问链表的时间复杂度为O(n),其中n为链表的长度

对于插入和删除操作,链表的复杂度取决于要插入或删除的位置。如果要在链表头部插入或删除元素,时间复杂度为O(1);如果要在链表尾部插入或删除元素,仍然是O(n)。

综上所述,链表是一种灵活的数据结构,适合频繁进行插入或删除操作。

三、数组和链表的区别

数组和链表是两种常见的数据结构,它们之间的主要区别在于数据的存储方式和访问方式。

1. 存储方式:

  • 数组是一种连续的内存结构,所有元素在内存中相邻存储。数组的大小在创建时就确定了,可以通过下标来访问任意位置的元素。
  • 链表是一种非连续的内存结构,元素在内存中通过指针相连。链表的大小可以动态增长或减少,每个节点保存了下一个节点的指针,通过遍历来访问元素。

2. 访问方式:

  • 数组的元素可以直接通过下标来访问,时间复杂度为O(1)。但插入或删除元素时,需要移动其他元素,时间复杂度为O(n)。
  • 链表的元素需要通过遍历从头节点开始找到目标节点,时间复杂度为O(n)。但插入或删除元素时,只需要修改指针,时间复杂度为O(1)。

综上所述,数组适合需要频繁随机访问元素的情况,而链表适合需要频繁插入、删除元素的情况。在实际应用中,我们根据具体的需求选择不同的数据结构。

四、用js实现链表

// 定义节点类
class Node {
  // 节点类构造函数,接收数据作为参数
  constructor(data) {
    this.data = data; // 节点存储的数据
    this.next = null; // 指向下一个节点的指针
  }
}

// 定义链表类
class LinkedList {
  // 链表类构造函数
  constructor() {
    this.head = null; // 链表的头节点
  }

  // 添加节点到链表末尾
  append(data) {
    let newNode = new Node(data); // 创建新节点
    if (this.head === null) { // 如果链表为空
      this.head = newNode; // 新节点就是头节点
    } else { // 如果链表不为空
      let current = this.head; // 从头节点开始
      while (current.next !== null) { // 遍历链表
        current = current.next; // 移动到下一个节点
      }
      current.next = newNode; // 将新节点添加到链表末尾
    }
  }

  // 删除指定值的节点
  delete(data) {
    if (this.head === null) { // 如果链表为空
      return; // 直接返回
    }
    if (this.head.data === data) { // 如果头节点就是要删除的节点
      this.head = this.head.next; // 头节点指向下一个节点
      return;
    }
    let current = this.head; // 从头节点开始
    let prev = null; // 记录当前节点的前一个节点
    while (current !== null) { // 遍历链表
      if (current.data === data) { // 如果找到要删除的节点
        prev.next = current.next; // 前一个节点指向当前节点的下一个节点
        return;
      }
      prev = current; // 记录当前节点
      current = current.next; // 移动到下一个节点
    }
  }

  // 查找指定值的节点
  find(data) {
    let current = this.head; // 从头节点开始
    while (current !== null) { // 遍历链表
      if (current.data === data) { // 如果找到指定值的节点
        return current; // 返回该节点
      }
      current = current.next; // 移动到下一个节点
    }
    return null; // 如果没有找到,返回 null
  }

  // 修改指定节点的值
  update(data, newData) {
    let node = this.find(data); // 查找指定值的节点
    if (node !== null) { // 如果找到该节点
      node.data = newData; // 修改该节点的值
    }
  }

  // 打印链表
  print() {
    let current = this.head; // 从头节点开始
    let arr = [];
    while (current !== null) { // 遍历链表
      arr.push(current.data); // 将节点的数据添加到数组中
      current = current.next; // 移动到下一个节点
    }
    console.log(arr.join('=>')); // 打印链表
  }
}

// 测试链表功能
let list = new LinkedList();
list.append(1); // 添加节点 1
list.append(2); // 添加节点 2
list.append(3); // 添加节点 3

console.log("Initial Linked List:");
list.print(); // 打印链表

list.delete(2); // 删除节点 2
console.log("After deleting '2':");
list.print(); // 打印链表

list.update(3, 4); // 将节点 3 的值修改为 4
console.log("After updating '3' to '4':");
list.print(); // 打印链表

let newNode = new Node(1); 
console.log(newNode); // 输出 Node { data: 1, next: null }

在这里插入图片描述

五、移除链表元素

// 打印
function printLinkedList(head) {
    let tempHead = head; // 临时头节点,用于遍历
    let result = []; // 用于存储链表中的元素,以便打印
    while (tempHead !== null) {
        result.push(tempHead.data);
        tempHead = tempHead.next;
    }
    console.log(result.join("=>")); // 打印处理后的链表
}

// 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
// 输入:head = [1,2,6,3,4,5,6], val = 6
// 输出:[1,2,3,4,5]

// 思路:创建一个虚拟头节点,然后遍历链表,如果当前节点的值等于val,则删除当前节点,否则继续遍历
// 时间复杂度:O(n)
// 空间复杂度:O(1)
function removeElements(head, targetValue) {
  // 创建一个新的节点作为哨兵节点,其 next 指向链表的头节点
  let sentinel = new Node();
  sentinel.next = head;
  // 创建一个指针变量,初始化为哨兵节点,用于遍历链表
  let current = sentinel;
  // 遍历链表直到链表结束
  while (current.next) {
    // 如果当前节点的下一个节点的值等于目标值,则将当前节点的 next 指向下下个节点,从而移除下一个节点
    if (current.next.data == targetValue) {
      current.next = current.next.next;
    } else {
      // 如果当前节点的下一个节点的值不等于目标值,则将指针移动到下一个节点
      current = current.next;
    }
  }
  // 返回哨兵节点的下一个节点,即处理后的链表的头节点
  return sentinel.next;
}
// 测试
let head = new LinkedList();
head.append(1);
head.append(2);
head.append(6);
head.append(3);
head.append(4);
head.append(5);
head.append(6);
head.print(); // 1=>2=>6=>3=>4=>5=>6
// 移除元素
let newHead = removeElements(head.head, 6); // 注意这里传入的是 head.head,因为 removeElements 需要 Node 类型的参数

// 打印移除指定元素后的链表
printLinkedList(newHead);

// 方法二:递归
// 时间复杂度:O(n)
// 空间复杂度:O(n)
function removeElements2(head, val) {
  // 如果当前节点为空,表示链表已经遍历完毕,返回 null 结束递归
  if (head === null) {
    return null;
  }
  // 递归调用,处理当前节点的下一个节点
  head.next = removeElements2(head.next, val);
  // 如果当前节点的值等于目标值 val,则需要移除当前节点
  // 通过返回 head.next 实现,即跳过当前节点
  // 如果当前节点的值不等于目标值,直接返回当前节点
  return head.data === val ? head.next : head;
}
// 测试
let testList = new LinkedList();
testList.append(10);
testList.append(20);
testList.append(30);
testList.append(40);
testList.append(50);
testList.print(); // 10=>20=>30=>40=>50
// 移除元素
let modifiedListHead = removeElements(testList.head, 30); // 注意这里传入的是 testList.head,因为 removeElements 需要 Node 类型的参数

// 打印移除指定元素后的链表
printLinkedList(modifiedListHead);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieyucx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值