手写双向链表+反转单向链表

一些总结

  • 因为.next可以移动指针,所以某些for循环条件不需用index
  • 双向链表有双向指针,需要相互指定
  • 要巧用“假设索引为0”来进行微调代码
  • 因为LinkedList的设计是有first和last头尾节点,所以在根据index增删的时候需要对头尾节点先进行判断
  • 因此在LinkeList源码中会事先定义addFirst addLast removeFirst removeLast方法以便调用
  • 因为有first和last头尾节点是否null的问题,所以在设计双向链表时需要考虑判断0个元素(remove报错)、1个元素(remove后将last=null)、是否为头尾节点
  • last.pre.next = last,就是当前的Node对象,Node对象为null,那么上一个Node.next也为null
  • 如果要进行全表置空or删除操作,需要先拿到置空的Node对象赋值给中间变量temp,然后再把Node的三个属性pre next item全=null,再用temp复原,源码说“help GC”,这样多几步操作是为了帮助gc回收垃圾

基本结构

  • 一个Node节点类(pre next item) 一个双向链表(first last length)
  • 初始化LikeLinkedList, first为头节点,不装item
  • 注意length的变化对方法体的影响
  • 长度length=10时,有10个元素,第一个索引是0,最后一个索引是9(同数组)

基本结构:

public class LikeLinkedList {

    private Node first;
    private Node last;

    private int length;
//1,初始化
public LikeLinkedList(){
    first = new Node(null,null,null);
    last = null;
    length = 0;
}
    //方法
}



class Node{

    public T item;

    public Node pre;
    public Node next;

    public Node(T item , Node pre , Node next){
        this.item = item;
        this.pre = pre;
        this.next = next;
    }

}

方法

add尾增

  • 因为last节点要变化,所以把last先存储成preLast

  • 然后再更新新的last = new Node

      //2,add尾增
      public boolean add(T t){
          if(length == 0){
              last = new Node(t,first,null);
              first.next = last;
          }
          else{
              Node preLast = last;
              last = new Node(t, preLast, null);
              preLast.next =last;
              }
    
          length++;
          return true;
      }
    

add索引处增

  • 先判断指针越界,throw异常,索引可取0~length-1

  • 新元素加到索引处,原索引处依次后移

  • 要对“是否是尾增”进行判断

  • 先用for获取原索引处的节点Node

  • 前后Node先保存为中间量

  • new Node(t,pre,next)只能拿到单向指针,指向前后

  • 必须额外补全双向指针

      //3,add索引增
      public boolean add(int index, T t) {
          if (index >= length || index < 0) {
              throw new IndexOutOfBoundsException("指针越界");
          } else {
              if (index < length - 1) {//如果index==length-1,那么需要处理last节点
                  Node theNext = first.next;
                  for (int i = 0; i <= index; i++) {
                      //假设index=1,第一次拿到theNext是0号索引
                      theNext = theNext.next;//拿到后一个节点
                  }
                  Node thePre = theNext.pre;//拿到前一个节点
                  Node newNode = new Node(t, thePre, theNext);//新节点指向前后两节点
                  //补全双向指针
                  theNext.pre = newNode;
                  thePre.next = newNode;
              } else if (index == length - 1) {
                  //调用尾增方法
                  add(t);
              }
          }
          return true;
      }
    

clear置空+源码解读

查看LinkedList源码中的clear发现,需要把每个节点都置空,而不是单单置空首尾节点
源码解释:全部置空gc效率更高,也防止有迭代器能访问到手写双向链表+反转单向链表_第1张图片

//4,clear链表置空
public void clear(){

    //全体置空增加gc回收效率,防止已有的迭代器继续访问
    for(Node x = first ; x != null ; ){
        Node next = x.next;//先拿到下一个节点
        //再把当前节点全部置空
        x.next = null;
        x.pre = null;
        x.item = null;

        x = next;
    }

    length = 0 ;
    first = last = null;//首尾置空
}

removeLast尾删除

  • 用于给其他方法调用

      public void removeLast() {
          if (last == null || length == 0) {
              throw new NoSuchElementException("已经没有元素了");
          } else if (last.pre == first) {//如果只有一个元素时
              first.next = null;
              last = null;
              length = 0;
          } else {//多个元素
              final Node preLast = last.pre;
              preLast.next = null;
              last = preLast;
              length--;
          }
      }
    

remove删指定索引元素

  • 如果是删除尾节点,还需要处理last

      //5,remove删除索引处的元素
      public boolean remove(int index) {
          if (index < 0 || index >= length) {
              throw new IndexOutOfBoundsException("指针越界");
          } else if (index == length - 1) {//删除尾节点
              removeLast();
    
          } else {
              Node curr = first;//获取头节点用于迭代,不是索引0处
              for (int i = 0; i <= index; i++) {//拿到待删除的节点curr
                  curr = curr.next;
              }
              //1 前后节点的对象先拿到,以便删除中间节点后恢复链接
               final Node preNode = curr.pre;
               final Node nextNode = curr.next;
              //2 help gc,所以3次置空
              curr.pre = null;
              curr.next = null;
              curr.item = null;
              //3 恢复链接
              preNode.next = nextNode;
              nextNode.pre = preNode;
          }
              return true;
      }
    

indexOf第一次出现的索引

  • 要巧用“假设索引为0”来进行微调代码

  • 因为.next可以移动指针,所以for循环条件不需用index

      //6,indexOf第一次出现索引
      public int indexOf(T t) {
          //假设t出现的索引是0,推得index初始值应设置为-1
          int index = -1;
    
          if (t == null) {
              for (Node x = first; x != null; x = x.next) {
                  if (x.item == null) {
                      return index;
                  }
                  index++;
              }
          } else {
              for (Node x = first; x != null; x = x.next) {
                  if (x.item.equals(t)) {
                      return index;
                  }
                  index++;
              }
          }
    
          return index;
      }
    

迭代器遍历

  • LikeLinkedList类实现Iterable接口
  • 重写iterator方法,返回迭代器对象
  • 重写Iterator中的hasNext和next方法

如下:

public class LikeLinkedList implements Iterable{

    private Node first;
    private Node last;

    private int length;
    @Override
    public Iterator iterator() {
        return new Iterator() {
            Node n = first;
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }

            @Override
            public Object next() {
                return n=n.next;
            }
        };
    }
	}

测试

LikeLinkedList lll = new LikeLinkedList<>();
lll.add(new Object());
lll.add(new Object());
lll.add(new Object());
lll.add(new Object());
lll.add(new Object());


lll.removeLast();
lll.remove(2);

Iterator iterator = lll.iterator();
while(iterator.hasNext()){
    System.out.println(iterator.next());
}
 
  

链表反转(单向链表)

//7,链表反转
public void reverseLink(){
    Node x = first.next;//获取索引0的节点
    Node nextX = x.next;//索引1处节点

    x.next = null;//索引0变尾节点
    reverse(x,nextX);



}

public void reverse(Node prev , Node curr){//第一次传入索引:0、1
    if(curr.next == null){//处理尾节点
        first.next = curr;
        curr.next = prev;
        return;
    }
    Node next = curr.next;//索引2
    curr.next = prev;//1指0

    reverse(curr,next);//递归,传索引1、2
}

你可能感兴趣的:(链表,数据结构,c++)