手写LinkedList(双向链表)

手写LinkedList(双向链表)

系统jdk里的LinkedList是由一个个节点连接起来的,节点就相当于一个对象,里面有数据域和指针域,数据域是存放数据用的,指针域就是指向下一个节点 从而相连接的

  • 这里是一个节点
    在这里插入图片描述

  • 那么链表里是什么样子的呢

  • 当有多个节点时,然后它们的前驱和后继都分别指向对方,那么就行成了一个链表
    在这里插入图片描述

    LinkedList


     好了,下面我们开撸吧!
    
  • 我定义了一个泛型类 然后在里面定义了一个静态对象 里面有2个指针域和一个数据域

public class LinkedList<Y>
  • 2个指针域分别指向它的前面一个节点和后面一个节点(双向链表)
/**
* 节点
* LinkedList都是以一个个节点构成的 单向只有一个指针域(指向某位置) 双向则为两个指针域(分别指向前后)
* @param <Y> 泛型
*/
private static class Node<Y> {
   Y item;
   Node<Y> prev;
   Node<Y> next;

   //构造方法
   public Node(Node<Y> prev, Y item, Node<Y> next) {
       this.prev = prev;
       this.item = item;
       this.next = next;
   }
}
  • 然后一个LinkedList集合里肯定会有size(大小),头部节点和尾部节点
//头节点
private Node<Y> first;
//尾节点
private Node<Y> last;
//大小
int size = 0;
  • 当想要往里面添加内容时,我这里定义了一个公开方法,然后再把尾部添加方法拿出来,因为后面也会用的到
/**
* 添加数据到最后
*
* @param item 值
*/
public void add(Y item) {
   linkedLast(item);
}
  • 尾部判断添加方法 这里我先new了一个Node对象,然后把他的前驱先指向last,再放入内容,后继先为null,把last单独赋值给l(现在l相当于LinkedList的最后一个值),然后把last赋值为new的Node对象,再判断l是不是null,是的话就把first赋值为new的Node对象,否则把刚才l的后继:next指向new的Node 这样就完成了往最后面添加新节点(对了 记得size++)
//往最后面添加数据
private void linkedLast(Y item) {
   Node<Y> newNode = new Node<>(last, item, null);
   //之前的最后一个值
   Node<Y> l = last;
   //把last指向当前插入的值
   last = newNode;

   //里面没有数据
   if (l == null) {
       //当前插入的值就是第一个值
       first = newNode;
   } else {
       //之前的值的后继指向插入的值
       l.next = newNode;
   }
   size++;
}
  • add()方法完成了 那么这里还差add(指定下标,值)方法

  • 这里解释一下:如果插入的下标(index)是最后一个 那么就是直接添加尾部,调用linkedLast(值)尾部添加放了咯

  • 然后其他原因我的先把当前index(下标)的节点Node找出来,方法在再下面!找到了当前index的节点,我就可以把他的pre(前驱)找出来,再根据pre判断,如果为null,就说明当前位置是第一个,把first指向一下,再把刚才用node(index)方法找到的target的前驱指向新节点,这里的target已经相当于新节点的后面一个节点了,不为null就把pre的后继和target的前驱分别指向新节点就ok了(也记得size++)

/**
* 插入指定位置值
*
* @param index 位置
* @param item  值
*/
public void add(int index, Y item) {
   //如果输入位置在最后一个
   if (index == size) {
       linkedLast(item);
   } else {
       //当前插入位置的后继
       Node<Y> target = node(index);
       //当前插入位置的前驱
       Node<Y> pre = target.prev;
       //当前准备插的值
       Node<Y> newNode = new Node<>(pre, item, target);
       //pre为null则插入的位置是0 第一个
       if (pre == null) {
           first = newNode;
           //它后面的前驱指定一下
           target.prev = newNode;
       } else {
           //newNode new出来的时候就已经指定了自己的前驱和后继了 现在只要指定它之前的后继和他之后的前驱就行了
           pre.next = newNode;
           target.prev = newNode;
       }
       size++;
   }
}
  • 这里是按下标查找节点,什么不管它,就进行了一个自定义的判断,下面怎样查找出来的呢,我是先把第一个first赋值为要返回的node,然后根据当前index循环遍历,把node一直指向index位置的节点就行了
//返回指定位置的值
private Node<Y> node(int index) {
   //如果输入下标不符合要求就返回null
   if (index < 0 || index >= size) {
       try {
           throw new Exception("yang say the index is wrong! Do you have any comments?");
       } catch (Exception e) {
           e.printStackTrace();
       }
       return null;
   }
   //一遍遍遍历 当前传入第几个就返回当前第几个,可以折中查找 如果index小于一半 就从前往后找
   if (index < size >> 1) {
       Node<Y> node = first;
       for (int i = 0; i < index; i++) {
           node = node.next;
       }
       return node;
   } else {
       Node<Y> node = last;
       for (int i = size - 1; i > index; i--) {
           node = node.prev;
       }
       return node;
   }
}
  • 好了,add(值)和add(指定位置,值)方法都写完了,现在增加还差一个addAll(LinkedList对象)方法 如下:

  • 这里很简单,就把传进来的LinkedList集合循环遍历,调用自己的尾部添加方法一个个添加

/**
* 添加一个linkedList对象进来
*
* @param linkedList
*/
public void addAll(LinkedList<Y> linkedList) {
   Y item;
   for (int i = 0; i < linkedList.size; i++) {
       item = linkedList.get(i);
       //一个个添加
       linkedLast(item);
   }
}
  • 增删改查,现在到删了

  • 这边只要把头结点和尾节点分别指向nul,size = 0 就行了 那么被抛弃的中间那些节点会怎么办呢,别担心,它们会在gc的下一个周期里被回收

/**
* 删除所有值
*/
public void clear() {
   first = null;
   last = null;
   size = 0;
}
  • 这里是删除指定位置的值 我也把方法单独拿出来了
/**
* 删除某位置值
*  * @param index 位置
*/
public void remove(int index) {
   Node<Y> target = node(index);
   unlinkNode(target);
}
  • 根据item删除 下面注释很清楚,先分别找出传入item的前驱(pre)和后继(next),然后会有3种情况,下面有介绍,这里我把3种情况分别执行的代码放这里:

1.当前item是第一个:

              first = item.next;       //头结点的后继指向自己的后面一个

              next.prev = item,prev;   //下一个的前驱指向自己的前面一个

2.当前item是中间的:

              pre.next = item.next;   //前面的后继指向自己的后继

              next.prev = item.prev;  //后面的前驱指向自己的前驱

3.当前item是最后一个:

              pre.next = item.next;   //前面的后继指向自己的后继(null)

              last = item.prev;       //尾节点指向自己的前驱
  • 注:其实这里的删除方法和前面讲的gc回收一个道理,当没有指针指向他们的时候 他们就会被回收(删除一个数据后记得size–)
//移除某值
private void unlinkNode(Node<Y> item) {
   //分别定义出当前值的前驱和后继
   Node<Y> pre = item.prev;
   Node<Y> next = item.next;

   //删除分3种情况 一种删除的当前值是第一个 一种是中间 一种是最后一个
   //第一个
   if (pre == null) {
       first = item.next;
   } else {
       //让前一个的后继指向自己的后继 然后自己就被跳过了
       pre.next = item.next;
   }
   //最后一个
   if (next == null) {
       last = item.prev;
   } else {
       //让后一个的前驱指向自己的前驱 然后自己就被跳过了(配合上面的一行代码,相当于删除)
       next.prev = item.prev;
   }
   size--;
}
  • 到改了 改也是一样的,先根据下标得到当前的节点,然后把节点的item变一下就行了
/**
* 修改指定位置值
*
* @param index 要修改的位置
* @param item  修改的值
*/
public void update(int index, Y item) {
   Node<Y> node = node(index);
   node.item = item;
}
  • 查 根据下标
/**
* 查询指定位置数据
*
* @param index 下标
* @return 值
*/
public Y get(int index) {
   return node(index).item;
}
  • 查 根据内容返回下标,重复的话会返回最前面的一个
/**
* 查询某值的下标
*
* @param item 值
* @return 下标
*/
public int find(Y item) {
   Node<Y> node = first;
   for (int i = 0; i < size; i++) {
       if (node.item == item) {
           return i;
       }
       node = node.next;
   }
   return -1;
}
  • 然后我把头节点(first)和尾节点(last)私有化了 然后提供了一个公开方法获取他们的item
/**
* 返回第一个
*
* @return 值
*/
public Y getFirst() {
   return first.item;
}

/**
* 返回最后一个
*
* @return 值
*/
public Y getLast() {
   return last.item;
}
  • 好了 到这里就完成了自定义linkedList的简单增删改查

  • 希望不足的地方大家多多指点 谢谢大家!

猜你喜欢

转载自blog.csdn.net/qq_42921539/article/details/84672896