数据结构之链表

224 阅读3分钟

更多文章

前言

面试暂时告一段落,也收到了自己满意的offer,但这只是人生路上的一个小考,还是要继续向前走的,那么今天一起来了解一下数据结构之链表

什么是链表

链表是物理存储单元上非连续的、非顺序的存储结构。链表的节点包含两部分:

  1. 存储数据元素的数据域
  2. 指向下一个节点的指针域

概念还是比较抽象的,看组数据:

// 这只是一个简单的demo
Node {
  element: 1,
  next: Node {
    element: 2,
    next: null
  }
}

这就是一个简单的单链表,Node为节点,element为节点数据,next为指针,如果是双链表每个节点则还会有pre指向上个节点

链表&数组

我们通常会拿链表和数组做对比,它们均是线性结构,但优缺点却是完全相反的

  • 存储

数组从栈中分配空间,系统自动申请空间,属于静态分配内存,占用内存少

链表从堆中分配空间,每个元素需手动分配空间,属于动态分配内存,占用内存多

  • 查询

数组在物理内存上是连续存储的,随机查询时间复杂度为O(1),随机查询即访问arr[0]和arr[100]时使用的时间相同

链表通常认为是不连续的,每次查询都需要从第一个节点开始查起,所以访问list[0]和list[100]时list[100]用时更长,随机查询时间复杂度为O(n)

  • 插入、删除

数组是连续,正是因为如此,一旦插入/删除一条数据,后续所有数据下标都会发生改变,时间复杂度为O(n)

链表只需要改变指针即可,不会对其他节点的位置有影响,时间复杂度为O(1)

总结:链表占用内存更大,插入、删除效率高于数组,但随机查询劣于数组

单链表

链表通常有一个头结点、创建节点的类以及增删查等方法,一起来实现一下,首先创建一个节点类:

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

接着创建链表及新增方法:

class LinkedList{
  constructor(){
    this.head = null;
    this.length = 0;
  }
  append(element){
    const node = new Node(element);
    let current = null;
    if(this.head === null){
      this.head = node;
    }else{
      current = this.head;
      while(current.next){
          current = current.next;
      }
      current.next = node
    }
    this.length++
  }
}

ok,我们先来新增一些数据看一下:

const linkedList = new LinkedList();
linkedList.append('a');
linkedList.append('b');
console.log('head',linkedList.head)
console.log('length',linkedList.length) // 2
// head Node {
//   element: 'a',
//   next: Node {
//     element: 'b',
//     next: null
//   }
// }

这里不一一举例其他方法,直接看完整代码

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

class LinkedList {
  constructor() {
    this.head = null;
    this.length = 0;
  }
  // 尾部添加
  append(element) {
    const node = new Node(element);
    let current = null;
    if (this.head === null) {
      this.head = node;
    } else {
      current = this.head;
      while (current.next) {
        current = current.next;
      }
      current.next = node
    }
    this.length++
  }
  // 插入
  insert(position, element) {
    if (position > -1 && position < this.length) {
      const node = new Node(element)
      let current = this.head;
      let index = 0;
      let pre = null;
      if (position == 0) {
        node.next = current;
        this.head = node;
      } else {
        while (index++ < position) {
          pre = current;
          current = current.next;
        }
        node.next = current
        pre.next = node;
      }
      this.length++
      return true;
    }
    return false;
  }
  // 根据索引移除
  remove(position) {
    if (position > -1 && position < this.length) {
      let current = this.head;
      let index = 0;
      let pre = null;
      if (position == 0) {
        current = current.next;
        this.head = current;
      } else {
        while (index++ < position) {
          pre = current;
          current = current.next;
        }
        pre.next = current.next;
      }
      this.length--
      return true;
    } else {
      console.log('移除元素不存在');
      return false;
    }
  }
  // 查询索引
  findIndex(element) {
    let index = 0;
    let current = this.head;
    while (current) {
      if (current.element === element) {
        return index;
      }
      index++;
      current = current.next;
    }
    console.log('元素不存在');
    return -1;
  }
  // 根据元素移除
  removeElement(element) {
    const position = this.findIndex(element);
    this.remove(position)
  }
}

双链表

单链表可以访问下一个节点,但无法直接访问上一个节点的,所以逆向访问链表是非常耗时,而双链表中不仅有指向下一个节点的指针,也会保存指向上一个节点的指针,看下边一个demo:

Node {
  element: "a",
  next: Node {
    element: "b",
    next: Node { element: "c", pre: Node, next: null },
    pre: Node { element: "a", pre: null, next: Node }
  }, 
  pre: null
}

所以,我们创建节点的类就变成了:

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

双链表实现:

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null; // 存储新添加的节点
    this.length = 0;
  }
  append(element) {
    let node = new Node(element)
    if (!this.head) {
      this.head = node
      this.tail = node
    } else {
      node.prev = this.tail
      // 为最后一层数据添加指针指向
      this.tail.next = node 
      // 将tail赋值为最新添加的数据
      this.tail = node
    }
    this.length++
    return true
  }
}

还是先来尝试一下:

const doublyLinkedList = new DoublyLinkedList();
doublyLinkedList.append('a')
doublyLinkedList.append('b')
doublyLinkedList.append('c')
console.log('list', doublyLinkedList) // 打印结果符合预期,自己copy下来看一下
console.log('length', doublyLinkedList.length) // 3

ok,看一下双链表完整代码:

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

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.length = 0;
  }
  append(element) {
    let node = new Node(element)
    if (!this.head) {
      this.head = node
      this.tail = node
    } else {
      node.prev = this.tail
      this.tail.next = node
      this.tail = node
    }
    this.length++
    return true
  }
  insert(position, element) {
    if (position > -1 && position <= this.length) {
      const node = new Node(element);
      let current = this.head;
      let index = 0;
      let previous = null;
      if (position === 0) {
        if (this.head === null) {
          this.head = node;
          this.tail = node;
        } else {
          node.next = current;
          current.prev = node;
          this.head = node;
        }
      } else if (position == this.length) {
        current = this.tail;
        current.next = node;
        node.prev = current;
        this.tail = node;
      } else {
        while (index++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current
        previous.next = node
        current.prev = node
        node.prev = previous
      }
      this.length++;
      return true;
    }
    return false;
  }
  remove(position) {
    if (position < 0 || position > this.length - 1) return false
    if (position == 0) {
      if (this.length == 1) {
        this.head = null
        this.tail = null
      } else {
        this.head = this.head.next
        this.head.prev = null
      }
    } else if (position == this.length - 1) {
      this.tail = this.tail.prev
      this.tail.next = null
    } else {
      let current = ''
      let index = 0
      if (position < this.length / 2) {
        current = this.head
        index = 0
        while (index++ < position - 1) {
          current = current.next
        }
      } else {
        index = this.length - 1
        current = this.tail
        while (index-- > position - 1) {
          current = current.prev
        }
      }
      current.next = current.next.next
      current.next.prev = current
    }
    this.length--
    return true
  }
  findIndex(element) {
    let index = 0;
    let current = this.head;
    while (current) {
      if (current.element === element) {
        return index;
      }
      index++;
      current = current.next;
    }
    console.log('元素不存在');
    return -1;
  }
  removeElement(element) {
    const position = this.findIndex(element);
    this.remove(position)
  }
}

循环链表

循环链表是在单链表的基础上,将尾节点的指针指向头节点,我们这里也做个简单实现:

class Node {
  constructor(element) {
    this.element = element;
    this.next = null;
  }
}
class LinkedList {
  constructor() {
    this.head = null;
    this.length = 0;
  }
  append(element) {
    const node = new Node(element);
    let current = null;
    if (this.head === null) {
      this.head = node;
      this.head.next = node
    } else {
      current = this.head;
      while (current.next !== this.head) {
        current = current.next;
      }
      current.next = node
      node.next = this.head
    }
    this.length++
  }
}

const linkedList = new LinkedList();
linkedList.append('a');
linkedList.append('b');
linkedList.append('c');
console.log('head', linkedList.head)
// 打印结果a->b->c->a->b-c....

反转链表

var reverseList = function (head) {
  let prev = null
  let curr = head
  while (curr) {
    [curr.next, prev, curr] = [prev, curr, curr.next]
  }
  return prev
};

合并两个有序链表

const mergeTwoLists = function (l1, l2) {
  if (l1 === null) {
    return l2;
  }
  if (l2 === null) {
    return l1;
  }
  if (l1.element < l2.element) {
    l1.next = mergeTwoLists(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoLists(l1, l2.next);
    return l2;
  }
};

结语

今年过年不回家,后悔了:cry: