04-数据结构-链表

基本概念

链表是线性表的⼀种,所谓的线性表包含顺序线性表和链表,顺序线性表是⽤数组实现的,在内存中有顺序排列,通过改变 数组⼤⼩实现。⽽链表不是⽤顺序实现的,⽤指针实现,在内存中不连续。意思就是说,链表就是将⼀系列不连续的内存联系起来,将那种碎⽚内存进⾏合理的利⽤,解决空间的问题。

单向链表

/**
 * 链表
 */

function defaultEquals(a, b) {
  return a === b;
}

// 需要添加到链表中的项,包含element和next,element是当前元素,next是当前元素的下一个元素
export class Node {
  element: any;
  next: any;
  constructor(element: any) {
    this.element = element;
    this.next = null;
  }
}

// 单链表
export class LinkedList {
  head: any;
  count: number; // 当前链表数据量(长度)
  equalsFn: (a: any, b: any) => boolean;
  constructor(equalsFn = defaultEquals) {
    this.count = 0;
    // 保存第一个元素的引用
    this.head = null;
    // 查找数据时需要判断数据是否相等
    this.equalsFn = equalsFn;
  }

  // 指定位置添加一个元素
  insert(element: any, index: number) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      const current = this.head;
      if (index === 0) {
        node.next = current;
        this.head = node;
      } else {
        // 拿到要插入的位置的前一项
        const prev = this.getElementAt(index - 1);
        // 获取到插入位置的下一项
        const current = prev.next;
        // 将前一项的下一项指向添加的元素
        prev.next = node;
        // 将添加的元素的下一项指向插入位置的下一项
        node.next = current;
      }
      this.count++;
      return node;
    }
    return undefined;
  }

  // 向链表尾部添加一个元素
  push(element: any) {
    const node = new Node(element);
    let current: Node;
    // 如果当前链表为空
    if (!this.head) {
      this.head = node;
    } else {
      // 当前链表不为空
      current = this.head;
      // 从第一项开始循环,找到最后一个,然后添加
      while (current.next !== null) {
        current = current.next;
      }
      // 找到最后一项,进行保存
      current.next = node;
    }
    // 元素添加完成了,需要将count进行修改
    this.count++;
  }

  // 移除链表中的一个元素
  remove(element: any) {
    // 在indexOf方法完成后,remove就水到渠成了
    const index = this.indexOf(element);
    return this.removeAt(index);
  }

  // 查找链表中特定元素的位置索引
  indexOf(element: any) {
    // 当前链表长度为0
    if (this.isEmpty()) {
      return;
    }
    let current = this.head;
    for (let i = 0; i < this.count && current !== null; i++) {
      if (this.equalsFn(element, current.element)) {
        return i;
      }
      current = current.next;
    }
    // 没有找到返回-1
    return -1;
  }

  // 从链表的特定位置移除一个元素
  removeAt(index: number) {
    // 根据传入的index来移除元素
    if (index >= 0 && index < this.count) {
      // 不论移除或添加都需要从第一项开始进行查询
      let current = this.head;
      if (index === 0) {
        // 要移除第一项
        this.head = current.next;
      } else {
        // 获取前一个node
        let prev: Node = this.getElementAt(index - 1);
        current = prev.next;
        // 将前一项跟后一项进行连接,跳过被移除的元素即可实现
        prev.next = current.next;
      }
      // 记得要将count减少
      this.count--;
      // 删除成功后将被移除的元素返回
      return current.element;
    }
    // 边界场景,传入的index不合法
    return undefined;
  }

  // 获取指定元素的方法封装
  getElementAt(index: number) {
    if (index >= 0 && index <= this.count) {
      let node = this.head;
      for (let i = 0; i < index && node !== null; i++) {
        node = node.next;
      }
      return node;
    }
    return undefined;
  }

  // 链表是否为空
  isEmpty() {
    return this.count === 0;
  }

  // 链表中的元素个数
  size() {
    return this.count;
  }
}

双向链表

链表有多种不同的类型,本节介绍双向链表。双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接;而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素。

// 双向链表
// 需要添加到链表中的项,包含element和next,element是当前元素,next是当前元素的下一个元素,与单向链表不同的是,添加了prev用来记录当前元素的前一个元素。所以双向链表相比于单向链表更加灵活
export class DoubleNode extends Node {
  prev: DoubleNode;
  constructor(element) {
    super(element);
    this.prev = null;
  }
}

export class DoubleLinkedList extends LinkedList {
  tail: DoubleNode; // 保存最后一个元素的引用
  constructor(equalsFn = defaultEquals) {
    super(equalsFn);
    this.tail = null;
  }

  // 需要将insert重写,因为涉及到前后
  insert(element: any, index: number): boolean {
    if (index >= 0 && index <= this.count) {
      let node = new DoubleNode(element);
      let current = this.head;
      if (index === 0) {
        if (this.head === null) {
          this.head = node;
          this.tail = node;
        } else {
          node.next = this.head;
          current.prev = node;
          this.head = node;
        }
      } else if (index === this.count) {
        current = this.tail;
        current.next = node;
        node.prev = current;
        this.tail = node;
      } else {
        const previous = this.getElementAt(index - 1);
        current = previous.next;
        previous.next = node;
        current.prev = node;
        node.next = current;
        node.prev = previous;
      }
      this.count++;
      return true;
    }
    return false;
  }

  removeAt(index: number) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        this.head = current.next;
        if (this.count === 1) {
          this.tail = null;
        } else {
          this.head.prev = null;
        }
      } else if (index === this.count - 1) {
        // 删除最后一项
        current = this.tail;
        this.tail = current.prev;
        this.tail.next = null; // 最后一项没有下一项了
      } else {
        // 拿到当前项
        current = this.getElementAt(index);
        const previous = current.prev;
        previous.next = current.next; // 前一项的下一项改为当前项的下一项
        current.next.prev = previous; // 下一项的前一项改为当前项的前一项
        // 经过上面的步骤即可跳过当前项,从而实现移出某一项的功能
      }
      this.count--;
      return current.element;
    }
    return undefined;
  }
}

循环链表/双向循环链表

循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链
表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tai1.next)不是引用
undefined,而是指向第一个元素(head)

双向循环链表有指向head元素的tail.next和指向tail元素的head.prev。

在这里就不用代码来实现了,感兴趣可以自己在上面的基础上实现一下。

有序链表

有序链表是指保持元素有序的链表结构。除了使用排序算法之外,我们还可以将元素插入到
正确的位置来保证链表的有序性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值