自己动手撸一个LinkedList

1. 原理

LinkedList是基于双链表的动态数组,数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。因此,LinkedList善于进行一些插入、删除操作,不利于进行检索操作。LinkedList和ArrayList这两个list在我们代码里会经常用到,因此,小编自定义实现LinkedList的简易版--MyLinkedList。

2. public API

void clear()                     --> 置空
boolean isEmpty()                --> 判空
int size()                       --> 返回链表的长度
AnyType get(int idx)             --> 根据索引检索元素
AnyType set(int idx)             --> 根据索引跟新元素
boolean add(AnyType x)           --> 添加元素x
boolean add(AnyType x,int idx)  --> 根据索引添加元素x
AnyType remove(int idx)          --> 根据索引删除元素x
String toString()                --> 打印链表

3. 图解核心操作

  • 一个节点Node类的数据结构

  • doClear( ) 方法,初始化一个双链表,先定义一个头结点beginMarker,然后定义一个尾结点endMarker,前驱指向头结点,最后头结点的后继指向尾结点。

  • 添加元素,先定义一个被添加元素x的节点,使它前驱指向被插入位置的前一个,后继指向被插入位置的节点,这是第一步,然后将被插入的前一个节点的next指向此节点,被插入位置的节点的prev指向此节点。

    Node<AnyType> newNode = new Node<>(x, p.prev, p);       // ①②
    newNode.prev.next = newNode; // ③
    p.prev = newNode; // ④

    当然,第三步和第四步可以合并:

    Node<AnyType> newNode = new Node<>(x, p.prev, p);       // ①②
    p.prev = p.prev.next = newNode; // ③④

    没想到以上4步全可以合并为:

    p.prev = p.prev.next = new Node<>(x, p.prev, p);        // ①②③④

    精辟!

  • 删除元素,根据索引找到对应的节点p,将p的后继的prev指向p的前驱,将p的前驱的next指向p的后继。

    p.next.prev = p.prev;
    p.prev.next = p.next;
  • 检索节点getNode,LinkedList可以很快很方便地插入和删除元素,但是对于检索元素则就慢了,我们可以将索引分为前半部分和后半部分,如果索引在前半部分,我们就向后的方向遍历该链表;同样的道理,如果索引在后半部分,我们就从终端开始往回走,向前遍历该链表,这样可以提高一下检索速度吧。

    // 从头结点开始向后找
    if (idx < size() / 2) {
    p = beginMarker.next;
    for (int i = 0; i < idx; i++) {
    p = p.next;
    }
    }
    // 从尾节点开始向前找
    else {
    p = endMarker;
       for (int i = size(); i > idx; i--) {
      p = p.prev;
      }
    }

4. MyLinkedList代码实现

 package com.hx.list;

 /**
* @author: wenhx
* @date: Created in 2019/10/17 16:11
* @description: 用双链表实现MyLinkedList
*/
public class MyLinkedList<AnyType> implements Iterable<AnyType> { private int theSize;
private int modCount = 0;
private Node<AnyType> beginMarker;
private Node<AnyType> endMarker; /**
* 内部类,定义链表的节点
*/
private static class Node<AnyType> { public AnyType data;
public Node<AnyType> prev;
public Node<AnyType> next; public Node(AnyType d, Node<AnyType> p, Node<AnyType> n) {
data = d;
prev = p;
next = n;
}
} /**
* 构造器
*/
public MyLinkedList() {
doClear();
} /**
* 判空
*/
public boolean isEmpty() {
return size() == 0;
} /**
* 清空
*/
public void clear() {
doClear();
} /**
* 返回链表的长度
*/
public int size() {
return theSize;
} /**
* 根据索引检索元素
*/
public AnyType get(int idx) {
return getNode(idx).data;
} /**
* 根据索引跟新元素
*/
public AnyType set(int idx, AnyType newVal) {
Node<AnyType> p = getNode(idx);
AnyType oldVal = p.data;
p.data = newVal;
return oldVal;
} /**
* 添加元素x
*/
public boolean add(AnyType x) {
add(size(), x);
return true;
} /**
* 根据索引添加元素
*/
public void add(int idx, AnyType x) {
addBefore(getNode(idx, 0, size()), x);
} /**
* 根据索引删除元素
*/
public AnyType remove(int idx) {
return remove(getNode(idx));
} /**
* 打印链表
*/
public String toString() {
StringBuilder sb = new StringBuilder("[ "); for (AnyType x : this) {
sb.append(x + " ");
}
sb.append("]"); return new String(sb);
} /**
* 清空链表(实现)
*/
private void doClear() {
beginMarker = new Node<>(null, null, null);
endMarker = new Node<>(null, beginMarker, null);
beginMarker.next = endMarker;
theSize = 0;
modCount++;
} /**
* 根据索引检索节点
*/
private Node<AnyType> getNode(int idx) {
return getNode(idx, 0, size() - 1);
} /**
* 检索节点
*/
private Node<AnyType> getNode(int idx, int lower, int upper) {
Node<AnyType> p; if (idx < lower || idx > upper) {
throw new IndexOutOfBoundsException("getNode index: " + idx + "; size: " + size());
} if (idx < size() / 2) {
p = beginMarker.next;
for (int i = 0; i < idx; i++) {
p = p.next;
}
} else {
p = endMarker;
for (int i = size(); i > idx; i--) {
p = p.prev;
}
} return p;
} /**
* 插入节点
*/
private void addBefore(Node<AnyType> p, AnyType x) {
Node<AnyType> newNode = new Node<>(x, p.prev, p);
newNode.prev.next = newNode;
p.prev = newNode;
theSize++;
modCount++;
} /**
* 删除节点p
*/
private AnyType remove(Node<AnyType> p) {
p.next.prev = p.prev;
p.prev.next = p.next;
theSize--;
modCount++; return p.data;
} /**
* 返回一个迭代器对象,用于遍历链表
*/
public java.util.Iterator<AnyType> iterator() {
return new LinkedListIterator();
} /**
* LinkedListIterator迭代器的实现
*/
private class LinkedListIterator implements java.util.Iterator<AnyType> { private Node<AnyType> current = beginMarker.next;
private int expectedModCount = modCount;
private boolean okToRemove = false; public boolean hasNext() {
return current != endMarker;
} public AnyType next() {
if (modCount != expectedModCount) {
throw new java.util.ConcurrentModificationException();
}
if (!hasNext()) {
throw new java.util.NoSuchElementException();
} AnyType nextItem = current.data;
current = current.next;
okToRemove = true;
return nextItem;
} public void remove() {
if (modCount != expectedModCount) {
throw new java.util.ConcurrentModificationException();
}
if (!okToRemove) {
throw new IllegalStateException();
} MyLinkedList.this.remove(current.prev);
expectedModCount++;
okToRemove = false;
}
} /**
* 主方法:用来测试MyLinkedList
*/
public static void main(String[] args) {
MyLinkedList<Integer> myLinkedList = new MyLinkedList<>(); for (int i = 0; i < 10; i++) {
myLinkedList.add(i);
}
for (int i = 20; i < 30; i++) {
myLinkedList.add(0, i);
} System.out.println(myLinkedList.toString());
System.out.println("----------");
myLinkedList.remove(0);
myLinkedList.remove(myLinkedList.size() - 1);
System.out.println(myLinkedList);
System.out.println("----------");
java.util.Iterator<Integer> itr = myLinkedList.iterator();
while (itr.hasNext()) {
itr.next();
itr.remove();
System.out.println(myLinkedList);
}
}
}

完成,撒花,一个迷你版的LinkedList就写好啦,下次有空再写一个迷你版的ArrayList...

后记:

若有不当之处,可向小编反馈,一起交流学习,共同进步。

个人博客地址:https://www.cnblogs.com/q964024886/

GitHub地址:https://github.com/wenhaixiong

自己动手撸一个LinkedList的更多相关文章

  1. 微信小程序学习 动手撸一个校园网小程序

    动手撸一个校园网微信小程序 高考完毕,想必广大学子和家长们都在忙着查询各所高校的信息,刚好上手微信小程序,当练手也当为自己的学校做点宣传,便当即撸了一个校园网微信小程序. 效果预览 源码地址:Gith ...

  2. 来吧,自己动手撸一个分布式ID生成器组件

    在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处.但是又由于 ...

  3. 手把手教你撸一个 Webpack Loader

    文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该会很眼熟上面的图.正如它宣传的一样,webpack 能把左侧各种类型的文件(webpa ...

  4. (Android自定义View)来来来,一起再撸一个Material风格loadingView。

    本文同步自博主的个人博客wing的地方酒馆 很久很久以前,撸了一款loadingview(点击这里回顾),当时觉得还不错,现在看看觉得好丑啊!!! 于是想再撸一个,无意间在这里看到一个很不错的效果,于 ...

  5. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  6. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  7. 创建一个LinkedList,然后在其中插入多个值,确保每个值都插入到List中间(偶数中间两个数之一,奇数在正中间)

    这是Thinking in java 中的一道题,下面是我的解决方案: package test; import java.util.LinkedList; import java.util.List ...

  8. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

  9. 动手实现一个vue中的模态对话框组件

    写在前面 对话框是很常用的组件 , 在很多地方都会用到,一般我们可以使用自带的alert来弹出对话框,但是假如是设计 出的图该怎么办呢 ,所以我们需要自己写一个对话框,并且如果有很多地方都用到,那我们 ...

随机推荐

  1. java中public,private,protected和default的区别

    类中的数据成员和成员函数据具有的访问权限包括:public.private.protect.default(包访问权限) 作用域       当前类     同一package 子孙类   其他pac ...

  2. (转载)非常完善的Log4net配置详细说明

    一.前言 在项目中,对项目的日志收集是非常重要的,这里我就说说代码的异常日志收集.收集异常日志,有很多第三方成熟的框架,我这里就介绍一下我常用的Log4net. Log4Net介绍 Log4net 是 ...

  3. 关于git远程被覆盖的问题

    有同事A和B,git远程版本为A0,两个人的本地项目已经跟远程同步.同事A先向git提交了3次,A1.A2.A3.git远程版本为A0.A1.A2.A3.同事B也向git提交了1次B1,但是同事B提交 ...

  4. FreeSql (十二)更新数据时指定列

    var connstr = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;" + "Initia ...

  5. Vue2.x-社交网络程序项目的总结

    最近几天一直在学习Vue的课程,通过这个项目进行进一步的学习Vue方面的知识.掌握如何使用Vue搭建前端,如何请求Node.js写好的后端接口. 一.实现前后端连载 首先在后端的文件中  vue  i ...

  6. 【linux】【maven】maven及maven私服安装

    前言 系统环境:Centos7.jdk1.8 私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的用户使用.当Maven需要下载构件的时候,它从私服请求,如 ...

  7. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

  8. Linux 笔记:核心思想、常用命令以及脚本语法

    总结于 <鸟哥的 Linux 私房菜 - 基础学习篇> ,图片也来自于此. 核心思想 目录树结构(directory tree) Linux 系统是基于目录树结构的,这是它的核心思想.目录 ...

  9. Spark开发常用参数

    Driver spark.driver.cores driver端分配的核数,默认为1,thriftserver是启动thriftserver服务的机器,资源充足的话可以尽量给多. spark.dri ...

  10. 第六届蓝桥杯java b组第一题

    第一题 三角形面积 图中的所有小方格面积都是1. 那么,图中的三角形面积应该是多少呢? 请填写三角形的面积.不要填写任何多余内容或说明性文字. 填空答案 28 没什么好说的 第一题很水 估计就是为了增 ...