数据结构学习篇——单链表的实现

链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
结构:链表的结构不要求相邻元素地址连续,而需要元素的前驱和后继的地址,这样能通过头节点一直找到尾节点。
数据结构学习篇——单链表的实现_第1张图片

/*节点*/
public class ListNode<T> {
  T data;
  ListNode<T> pre;
  ListNode<T> next;

  public ListNode(T data) {
    this.data = data;
  }

  public T getData() {
    return data;
  }

  public ListNode<T> getPre() {
    return pre;
  }

  public void setNext(ListNode<T> next) {
    this.next = next;
  }

  public void setPre(ListNode<T> pre) {
    this.pre = pre;
  }

  public ListNode<T> getNext() {
    return next;
  }
}

实现一个单链表

实现一个单链表,其中关键点有4个

  • 单链表可以根据头节点找到下一个地址一直找到尾节点,所以需要关注的变量有3个,头节点、尾节点、元素的个数
  • 插入元素是往尾部插入要考虑到头节点为空的情况以及尾节点的变化
  • 删除元素需要依次从头开始遍历,遍历的关键方法是p = p.next,但是也不能忘了给pre节点赋值。删除的思想是把上一个元素的next节点替换为要删除元素的下一个节点。除此之外也要考虑到头节点为空的情况以及尾节点的变化
public class SingleLinkedList implements MyList {
  private ListNode first;
  private ListNode last;
  private int size;

  @Override
  public void add(Object element) {
    if (first == null) {
      first = new ListNode(element);
      last = first;
    } else {
      last.next = new ListNode(element);
      last = last.next;
    }
    size++;
  }

  @Override
  public void delete(Object element) {
    ListNode p = first;
    ListNode pre = null;
    while (p != null){
      if (Objects.equals(p.data,element)){
        if (p == first){
          first = first.next;
          if (first == null){
            last = null; // 如果链表变为空,更新 last 指针
          }
        }else {
          pre.next = p.next;
          if (p == last){
            last = pre; // 如果删除的是最后一个节点,更新 last 指针
          }
        }
        size --;
        break;// 注意这里
      }
      pre = p;
      p = p.next;
    }
  }

  @Override
  public void delete(int index) {
    if (index < 0 || index >= size) {
      return;// 索引无效,啥也不干
    }
    ListNode p = first;
    ListNode pre = null;
    int i = 0;
    while (p != null){
      if (i == index){
        if (p == first){
          first = first.next;
          if (first == null){
            last = null; // 如果链表变为空,更新 last 指针
          }
        }else {
          pre.next = p.next;
          if (p == last){
            last = pre; // 如果删除的是最后一个节点,更新 last 指针
          }
        }
        size --;
        break;// 注意这里
      }
      pre = p;
      p = p.next;
      i++;
    }
  }

  @Override
  public void update(int index, Object newElement) {
    if (index < 0 || index >= size) {
      return;//啥也不干
    }
    int i = 0;//指针指向的节点的索引
    ListNode p = first;

    while (p != null) {
      if (i == index) {
        p.data = newElement;
      }
      p = p.next;
      i++;
    }
  }

  @Override
  public boolean contains(Object target) {
    ListNode p = first;
    while (p != null) {
      if (p.data.equals(target)) {
        return true;
      }
      p = p.next;
    }
    return false;
  }

  @Override
  public Object at(int index) {
    if (index < 0 || index >= size) {
      return null;
    }
    int i = 0;//指针指向的节点的索引
    ListNode p = first;

    while (p != null) {
      if (i == index) {
        return p.data;
      }
      p = p.next;
      i++;
    }
    return null;
  }

  @Override
  public int indexOf(Object element) {
    int i = 0;//指针指向的节点的索引
    ListNode p = first;

    while (p != null) {
      if (p.data.equals(element)) {
        return i;
      }
      p = p.next;
      i++;
    }
    return -1;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("[");
    ListNode p = first;
    while (p != null) {
      sb.append(p.data);
      if (p.next != null)
        sb.append(",");
      p = p.next;
    }
    sb.append("]");
    return sb.toString();
  }

  @Override
  public boolean hasNext() {
    return false;
  }

  @Override
  public Object next() {
    return null;
  }
}

单链表与数组列表的对比

  • 从空间上来说,链表除了需要存储元素之外,每个节点还需要记录下一个节点的地址;而数组只需要记录元素即可,因为数组的地址是连续的。所以链表消耗的空间会比数组稍微多一点。
  • 从速度上来说,这里需要分为不同的操作:
    <1>新增:链表只需要改变指针pre -> next是 O ( 1 ) O(1) O(1)的操作,而对于数组来说可能会涉及到扩容(重新生成一个数组,拷贝等)操作,是 O ( n ) O(n) O(n)
    <2>删除:如果传的是元素,链表查找需要消耗一定时间,找到后只需要改变指针和新增一样,复杂度 O ( n ) O(n) O(n)。对数组来说删除之后需要把当前元素后面的元素往前挪一位,来保证地址的连续,复杂度 O ( n ) + O ( n − k ) O(n)+O(n-k) O(n)+O(nk)。如果是传的索引,如果索引比较靠前,链表只要循环查找比较少的次数就可以修改前后元素指针,复杂度 O ( k ) O(k) O(k)。而索引比较靠后,数组删除元素后只需要挪动较少的元素,复杂度 O ( n − k ) O(n-k) O(nk)
    <3>修改:如果传的是元素两者差不多,因为都需要从头开始遍历,复杂度 O ( k ) O(k) O(k)。如果是传的索引,那数组可以根据索引定位会更快 O ( 1 ) O(1) O(1)
    <4>查询:如果是传的索引,数组有索引查找会更快。如果传的是元素两者一样。

你可能感兴趣的:(数据结构,学习)