数据结构与算法(Dart)之链表(三)

554 阅读4分钟

顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。

链表是一种在存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现。

链表是由一系列的结点组成,结点可以在运行时动态生成。每个结点包含两部分:数据域与指针域。数据域存储数据元素,指针域存储下一结点的指针。

链表与顺序表的对比

链表失去了序列的随机读取优点,同时链表增加了指针域,空间开销也较大,但它对存储空间的使用要相对灵活。

操作链表顺序表
访问元素O(n)O(1)
在头部插入/删除O(1)O(n)
在尾部插入/删除O(1)O(1)
在中间插入/删除O(1)O(n)

注意虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作:

  • 链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是 O(1)。
  • 顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。

单向链表

单向链表也叫单链表,是链表中最简单的形式。它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。

截屏2024-01-22 上午10.23.59.png

head 保存首地址,item 存储数据,next 指向下一结点地址。

列如:有一堆数据[1,2,3,5,6,7,8,9,11,13],要在11和13之间插入12, 如果用数组,需要将13之后的数据都往后退一位,然后再插入12,这样非常麻烦,但是如果用链表,我就直接在11和13之间插入12就行。

自定义单链表的操作:

  • isEmpty():链表是否为空。
  • length():链表长度。
  • travel():遍历整个链表。
  • add(item):链表头部添加元素。
  • append(item):链表尾部添加元素。
  • insert(index, item):指定位置添加元素。
  • remove(item):删除元素。
  • find(item):元素是否存在。

定义节点

节点的数据结构为数据元素(item)与 指针(next)

class Node {
  /// 单链表的结点
  var item;
  Node? next;
  Node({this.item, this.next});
}

定义链表

链表需要具有首地址指针head。

class SingleLinkList {
  /// 单链表

  /// 指向头节点
  Node? _head;

  /// 链表是否为空
  bool isEmpty() {
    return _head == null;
  }

  /// 链表长度
  int length() {
    /// 初始指针指向_head
    Node? cur = _head;
    int count = 0;
    while (cur != null) {
      count += 1;

      /// 指针下移
      cur = cur.next;
    }

    return count;
  }

  /// 遍历链表
  void travel() {
    Node? cur = _head;
    while (cur != null) {
      print('cur.item: ${cur.item}');
      cur = cur.next;
    }
  }

  /// 链表头部添加元素
  void add(item) {
    final Node node = Node(item: item);

    /// 新节点指针指向头节点
    node.next = _head;

    /// 头节点重新指向该节点
    _head = node;
  }

  /// 链表尾部添加元素
  void append(item) {
    final Node node = Node(item: item);

    /// 先判断链表是否为空, 若是空链表, 则将head指向新节点
    if (isEmpty()) {
      _head = node;
    } else {
      /// 若不为空, 则将尾部节点的next节点指向新节点
      Node? cur = _head;
      while (cur?.next != null) {
        cur = cur?.next;
      }
      cur?.next = node;
    }
  }

  /// 指定位置插入元素
  insert(int index, item) {
    /// index <=0, 插入头部
    if (index <= 0) {
      add(item);
    } else if (index > (length() - 1)) {
      /// 指定位置超过尾部, 尾部插入
      append(item);
    } else {
      /// 指定位置插入

      final Node node = Node(item: item);

      Node? cur = _head;

      /// 循环到需要插入的位置
      for (var _ in List.generate(index - 1, (i) => i)) {
        cur = cur?.next;
      }

      node.next = cur?.next;
      cur?.next = node;
    }
  }

  /// 移除节点
  remove(item) {
    /// 删除节点
    Node? cur = _head;
    Node? pre;
    while (cur != null) {
      if (cur.item == item) {
        /// 头结点就是被删除的节点
        if (pre == null) {
          /// 将头指针指向头结点的后一个节点
          _head = cur.next;
        } else {
          /// 将删除位置前一个节点的next指向删除位置的后一个节点
          pre.next = cur.next;
        }
      } else {
        /// 继续按链表后移节点
        pre = cur;
        cur = cur.next;
      }
    }
  }

  /// 查看节点是否存在,返回布尔值
  bool find(item) {
    Node? cur = _head;

    while (cur != null) {
      if (cur.item == item) {
        return true;
      }
      cur = cur.next;
    }
    return false;
  }
}

双向链表

双向链表比单向链表更加复杂,它每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个链接指向下一个节点,当此节点为最后一个节点时,指向空值。

截屏2024-01-22 下午1.40.13.png

head 保存首地址,item 存储数据,next 指向下一结点地址,prev 指向上一结点地址。

class BilateralLinkList {
  /// 双向链表

  /// 指向头节点
  Node? _head;

  /// 链表是否为空
  bool isEmpty() {
    return _head == null;
  }

  /// 链表长度
  int length() {
    /// 初始指针指向_head
    Node? cur = _head;
    int count = 0;
    while (cur != null) {
      count += 1;

      /// 指针下移
      cur = cur.next;
    }

    return count;
  }

  /// 遍历链表
  void travel() {
    Node? cur = _head;
    while (cur != null) {
      print('cur.item: ${cur.item}');
      cur = cur.next;
    }
  }
}

链表头部添加元素

链表头部添加元素。只需要两步即可。

  void add(item) {
    final Node node = Node(item: item);

    if (isEmpty()) {
      /// 头节点重新指向该节点
      _head = node;
    } else {
      /// 新节点的next指针指向头节点
      node.next = _head;

      /// 原头部 prev 指向 新节点
      _head?.prev = node;

      /// 头节点重新指向该节点
      _head = node;
    }
  }

截屏2024-01-22 下午2.15.52.png

链表尾部添加元素

  void append(item) {
    final Node node = Node(item: item);

    /// 先判断链表是否为空, 若是空链表, 则将head指向新节点
    if (isEmpty()) {
      _head = node;
    } else {
      /// 若不为空, 则将尾部节点的next节点指向新节点
      Node? cur = _head;
      while (cur?.next != null) {
        cur = cur?.next;
      }

      /// 新节点上一级指针指向旧尾部
      node.prev = cur;

      /// 旧尾部指向新结点
      cur?.next = node;
    }
  }

截屏2024-01-22 下午2.20.30.png

指定位置插入元素

insert(int index, item) {
    /// index <=0, 插入头部
    if (index <= 0) {
      add(item);
    } else if (index > (length() - 1)) {
      /// 指定位置超过尾部, 尾部插入
      append(item);
    } else {
      /// 指定位置插入

      final Node node = Node(item: item);

      Node? cur = _head;

      /// 循环到需要插入的位置
      for (var _ in List.generate(index - 1, (i) => i)) {
        cur = cur?.next;
      }

      ///  新节点的向下指针指向当前节点
      node.next = cur;

      /// 新节点的向上指针指向当前节点的上一节点
      node.prev = cur?.prev;

      /// 当前上一节点的向下指针指向node
      cur?.prev?.next = node;

      /// 当前节点的向上指针指向新节点
      cur?.prev = node;
    }
  }

截屏2024-01-22 下午2.25.19.png

移除节点

remove(item) {
    /// 删除节点

    if (isEmpty()) {
      return;
    }

    Node? cur = _head;

    /// 如果头节点就是被删除的节点
    if (cur?.item == item) {
      if (cur?.next == null) {
        /// 如果链表只有这一个节点
        _head = null;
      } else {
        /// 将第二个节点的prev指向null
        cur?.next?.prev = null;
        _head = cur?.next;
      }
    } else {
      while (cur != null) {
        if (cur.item == item) {
          /// 将cur的前一个节点的next指向cur的下一个节点
          cur.prev?.next = cur.next;

          /// 将cur的下一个节点的next指向cur的前一个节点
          cur.next?.prev = cur.prev;
          break;
        } else {
          /// 继续按链表后移节点
          cur = cur.next;
        }
      }
    }
  }

删除表头结点

截屏2024-01-22 下午2.40.17.png

删除表中结点

截屏2024-01-22 下午2.41.03.png

删除表尾结点

截屏2024-01-22 下午2.41.36.png

查看节点是否存在

  bool find(item) {
    Node? cur = _head;

    while (cur != null) {
      if (cur.item == item) {
        return true;
      }
      cur = cur.next;
    }
    return false;
  }

参考资料

Python 数据结构与算法详解

双向链表的操作