数组 链表 散列表

155 阅读5分钟

数组、链表和散列表是常见的数据结构,它们之间有一些不同的特点和应用,但也有一定的关联。下面是它们之间的关系和区别:

  1. 数组 (Array)

    • 数组是一个固定大小的连续内存空间,可以通过索引来快速访问元素。

    • 优点:

      • 可以快速访问任意位置的元素(时间复杂度为O(1))。
      • 对于静态大小的数据,效率高。
    • 缺点:

      • 大小固定,无法动态改变。
      • 插入或删除操作可能需要移动大量元素(时间复杂度O(n))。
      • 插入或删除的操作不够灵活。
  2. 链表 (Linked List)

    • 链表是由一系列节点构成,每个节点包含数据和指向下一个节点的指针。

    • 优点:

      • 插入和删除操作非常高效(时间复杂度O(1)),只需要改变指针。
      • 动态大小,能根据需求增减。
    • 缺点:

      • 访问特定位置的元素需要从头节点开始逐一遍历(时间复杂度O(n))。
      • 占用更多的内存,因为每个节点需要存储指针。
  3. 散列表 (Hash Table)

    • 散列表是一种通过哈希函数将键映射到数组索引的数据结构,通常用于实现快速查找。

    • 优点:

      • 在理想情况下,查找、插入和删除操作的时间复杂度为O(1)。
      • 不需要保持顺序,适用于频繁的查找操作。
    • 缺点:

      • 哈希冲突可能影响性能,需要解决冲突(例如链式法、开放寻址法)。
      • 哈希函数的设计对性能有很大影响。

它们之间的关系:

  • 数组与链表

    • 在链表中,每个节点指向下一个节点,而在数组中,元素的位置是连续的,直接通过索引进行访问。
    • 链表通过指针实现动态存储,而数组是静态存储的。链表灵活,但访问效率不如数组。
  • 数组与散列表

    • 散列表内部通常使用一个数组来存储数据。通过哈希函数计算键的哈希值,得到数组的索引位置。
    • 在发生哈希冲突时,可能会采用链表等方法来处理。
  • 链表与散列表

    • 链表可以作为散列表中解决冲突的一种方式(例如,链式哈希)。在这种情况下,哈希表的每个槽位可以存储一个链表,所有哈希到同一位置的元素都会被放入链表中。

总结来说,数组、链表和散列表各自有其特点和优势,选择使用哪种数据结构通常依赖于实际应用的需求。例如,散列表适用于高效查找,链表适用于频繁插入删除的场景,而数组则适用于索引访问频繁的情况。

对应在js中的数据结构

在 JavaScript 中,数组、链表和散列表都有对应的数据结构或实现方式。虽然 JavaScript 并没有直接提供链表的内置类型,但我们可以使用内置的对象和数组来实现它们。以下是它们在 JavaScript 中的对应关系:

1. 数组 (Array)

  • 对应数据结构:JavaScript 内置的 Array 类型。

  • 特点:数组是一个有序的集合,可以通过索引来访问元素。JavaScript 中的 Array 实际上是一个动态数组,支持自动扩展大小。

  • 操作

    • 快速访问元素:array[index](时间复杂度 O(1))。
    • 增加、删除元素:push()pop()shift()unshift() 等操作(对于数组前端的操作,性能较差,时间复杂度通常为 O(n))。
let arr = [1, 2, 3, 4];
console.log(arr[0]); // 输出 1
arr.push(5); // 添加元素 5 到数组末尾

2. 链表 (Linked List)

  • 对应数据结构:JavaScript 并没有内置的链表类型,但可以通过自定义类来实现。链表是由一系列节点组成,每个节点包含数据和指向下一个节点的指针(或引用)。
  • 实现:通常我们会创建一个 Node 类来表示链表中的节点,并创建一个 LinkedList 类来实现链表的操作(如插入、删除、遍历等)。
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
  }

  append(data) {
    const newNode = new Node(data);
    if (!this.head) {
      this.head = newNode;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = newNode;
    }
  }

  display() {
    let current = this.head;
    while (current) {
      console.log(current.data);
      current = current.next;
    }
  }
}

const list = new LinkedList();
list.append(10);
list.append(20);
list.display(); // 输出 10 20

对于类不懂的同学可以简单看一下 《类与对象的理解》

3. 散列表 (Hash Table)

  • 对应数据结构:JavaScript 内置的 Object 类型或 Map 类型。Object 用于存储键值对,Map 是 ECMAScript 6 引入的一个新的对象类型,也用于存储键值对,但相比 Object,它在处理哈希冲突和键类型方面具有更好的性能和灵活性。

  • 实现

    • ObjectObject 是 JavaScript 中最常用的散列表实现。它的键是字符串类型(或者可以转化为字符串),值可以是任何数据类型。Object 实现了一个基本的哈希表。
    • MapMap 是 ES6 引入的,它允许任何数据类型作为键,并且在内部使用更高效的散列机制来处理键值对。
// 使用 Object 实现散列表
let obj = {};
obj['key1'] = 'value1';
obj['key2'] = 'value2';
console.log(obj['key1']); // 输出 'value1'

// 使用 Map 实现散列表
let map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
console.log(map.get('key1')); // 输出 'value1'

区别

  • Object

    • 键只能是字符串或可以转换为字符串的类型。
    • 在早期的 JavaScript 引擎中,Object 可能不会很好地处理哈希冲突,但现在大多数现代引擎已经优化了它的性能。
    • 不保证顺序,尽管现代 JavaScript 引擎在 Object 的插入顺序上有改进。
  • Map

    • 键可以是任何数据类型(如对象、函数、原始值等)。
    • 保证插入顺序,即按键值对插入的顺序进行遍历。
    • 在许多操作上(如键的查找、删除)比 Object 更高效。

总结:

  • 数组:使用 JavaScript 内置的 Array 类型。
  • 链表:没有内置支持,但可以通过自定义类来实现链表。
  • 散列表:使用 Object 或 Map 类型来实现,Map 更适合存储键值对并提供更好的性能和灵活性。