在实现之前,我们需要知道有关链表的知识,有兴趣的朋友可以去查阅一下资料。
我会一步一步带着自己和大家一起实现,仅供参考。
链表分为:单向链表,双向链表和循环链表
如下图所示,就是一个双向链表图
需求:我们将实现的功能主要有四个:
1)add(String element) 添加数据
2) get(int index) 根据索引获取数据
3) remove(int index) 根据索引删除数据
4)add(String element, int index) 根据索引插入元素
public class LinkedList {
/*
* 永远指向链表的首节点
*/
private Node firstNode;
/*
* 永远指向链表的尾节点
*/
private Node lastNode;
/*
* 存放节点元素实际个数
*/
private int size;
public int size() {
return size;
}
/**
* 添加数据(在链表末尾追加)
* @param element 添加的数据
*/
public void add(String element) {
// 1.如果链表不存在的情况
if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
// 1.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 1.2把node设置为链表的首节点和尾节点
firstNode = node;
lastNode = node;
// 1.3更新size实际存放元素的个数
size++;
}
// 2.链表已经存在的情况
else {
// 2.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 2.2让lastNode的next指向新添加的节点
lastNode.next = node;
// 2.3让node的prev指向lastNode
node.prev = lastNode;
// 2.4跟新链表的尾节点
lastNode = node;
// 2.5更新size实际存放元素的个数
size++;
}
}
/*
* 链表的节点类
*/
private class Node {
/*
* 指向上一个节点
*/
private Node prev;
/*
* 节点存储的数据
*/
private String data;
/*
* 指向下一个节点
*/
private Node next;
// 无参构造方法
public Node() {}
// 有参构造方法
public Node(Node prev, String data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
主方法测试类
public class Test01 {
public static void main(String[] args) {
LinkedList list1 = new LinkedList();
list1.add("aa");
list1.add("bb");
list1.add("cc");
list1.add("dd");
// 遍历数组
}
}
说明:由于还未添加展示功能,可通过Debug模式来看里面的元素是否添加进去了
public class LinkedList {
/*
* 永远指向链表的首节点
*/
private Node firstNode;
/*
* 永远指向链表的尾节点
*/
private Node lastNode;
/*
* 存放节点元素实际个数
*/
private int size;
public int size() {
return size;
}
/**
* 添加数据(在链表末尾追加)
* @param element 添加的数据
*/
public void add(String element) {
// 1.如果链表不存在的情况
if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
// 1.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 1.2把node设置为链表的首节点和尾节点
firstNode = node;
lastNode = node;
// 1.3更新size实际存放元素的个数
size++;
}
// 2.链表已经存在的情况
else {
// 2.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 2.2让lastNode的next指向新添加的节点
lastNode.next = node;
// 2.3让node的prev指向lastNode
node.prev = lastNode;
// 2.4跟新链表的尾节点
lastNode = node;
// 2.5更新size实际存放元素的个数
size++;
}
}
/**
* 根据索引获取对应的元素
* @param index 索引
* @return 返回索引对应的节点
*/
public String get(int index) {
// 判断索引是否越界
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界异常");
}
// 2.1如果索引小于size/2,证明查找的元素在上半区
if(index < size>>1) {
// 从前往后找
Node node = firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
// 返回索引对应的节点
return node.data;
}
// 2.2如果索引大于等于size/2,证明查找的元素在下半区
else {
// 从后往前找
Node node = lastNode;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
// 返回索引对应的节点
return node.data;
}
}
private class Node {
/*
* 指向上一个节点
*/
private Node prev;
/*
* 节点存储的数据
*/
private String data;
/*
* 指向下一个节点
*/
private Node next;
// 无参构造方法
public Node() {}
// 有参构造方法
public Node(Node prev, String data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
主方法测试数据
public class Test01 {
public static void main(String[] args) {
LinkedList list1 = new LinkedList();
list1.add("aa");
list1.add("bb");
list1.add("cc");
list1.add("dd");
// 遍历数组
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
}
}
循环遍历输出:
aa
bb
cc
dd
因为我们后面还需要使用“根据索引获取对应的元素”,因此在下一个功能中我把它抽成方法
public class LinkedList {
/**
* 永远指向链表的首节点
*/
private Node firstNode;
/**
* 永远指向链表的尾节点
*/
private Node lastNode;
/**
* 用于保存实际存放元素的个数
*/
private int size;
/**
* 获取链表实际存放元素的个数
*/
public int size() {
return size;
}
/**
* 添加数据(在链表末尾追加)
* @param element 添加的数据
*/
public void add(String element) {
// 1.如果链表不存在的情况
if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
// 1.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 1.2把node设置为链表的首节点和尾节点
firstNode = node;
lastNode = node;
// 1.3更新size实际存放元素的个数
size++;
}
// 2.链表已经存在的情况
else {
// 2.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 2.2让lastNode的next指向新添加的节点
lastNode.next = node;
// 2.3让node的prev指向lastNode
node.prev = lastNode;
// 2.4跟新链表的尾节点
lastNode = node;
// 2.5更新size实际存放元素的个数
size++;
}
}
/**
* 根据索引获取元素值
* @param index 索引值
* @return 索引对应的元素值
*/
public String get(int index) {
// 1.判断索引是否合法
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界异常:" + index);
}
// 2.根据索引获取节点
Node node = node(index);
// 3.返回节点对应数据
return node.data;
}
/**
* 根据索引删除元素
* @param index 索引值
*/
public void remove(int index) {
// 1.判断索引是否合法
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界异常:" + index);
}
// 2.获取index对应的节点对象
Node node = node(index);
// 3.判断node是否为首节点
if(node == firstNode)
{
// 3.1把node的next节点设置为首节点
firstNode = node.next;
// 3.2把node的next节点设置为尾节点
firstNode.prev = null;
}
// 4.判断node是否为尾节点
else if (node == lastNode) {
// 4.1 把node的pre设置为尾节点
lastNode = node.prev;
// 4.2 把lastNode的next设置为空
lastNode.next = null;
}
// 5.执行到此处,证明node既不是首节点,也不是尾节点
else {
// 5.1 获取node节点的上一个节点和下一个节点
Node prevNode = node.prev;
Node nextNode = node.next;
// 5.2.更改prevNode和nextNode的指向关系
prevNode.next = nextNode;
nextNode.prev = prevNode;
}
// 6.更新size实际存放元素的个数
size--;
}
/**
* 根据索引获取对应的节点
* @param index 索引
* @return 返回索引对应的节点
*/
private Node node(int index) {
// 2.1如果索引小于size/2,证明查找的元素在上半区
if (index < size >> 1) {
// 从前往后找
Node node = firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
// 返回索引对应的节点
return node;
}
// 2.2如果索引大于等于size/2,证明查找的元素在下半区
else {
// 从后往前找
Node node = lastNode;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
// 返回索引对应的节点
return node;
}
}
/**
* 链表的节点类(只能存储字符串)
*/
private class Node {
/**
* 指向上一个节点
*/
private Node prev;
/**
* 节点存储的数据
*/
private String data;
/**
* 指向下一个节点
*/
private Node next;
/**
* 无参构造方法
*/
public Node() {}
/**
* 有参构造方法
* @param prev
* @param data
* @param next
*/
public Node(Node prev, String data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
主方法,测试数据
public class Test01 {
public static void main(String[] args) {
LinkedList list = new LinkedList();
// 添加元素
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
list.add("ff");
// 删除元素
list.remove(5);
// 遍历元素
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
输出:
aa
bb
cc
dd
ee
/**
* add(String element) 添加数据
* get(int index) 根据索引获取数据
* remove(int index) 根据索引删除数据
* add(String element, int index) 根据索引插入元素
*/
public class LinkedList {
/**
* 永远指向链表的首节点
*/
private Node firstNode;
/**
* 永远指向链表的尾节点
*/
private Node lastNode;
/**
* 用于保存实际存放元素的个数
*/
private int size;
/**
* 获取链表实际存放元素的个数
*/
public int size() {
return size;
}
/**
* 添加数据(在链表末尾追加)
* @param element 添加的数据
*/
public void add(String element) {
// 1.如果链表不存在的情况
if(null == firstNode) { // 当firstNode不存在,则证明链表不存在
// 1.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 1.2把node设置为链表的首节点和尾节点
firstNode = node;
lastNode = node;
// 1.3更新size实际存放元素的个数
size++;
}
// 2.链表已经存在的情况
else {
// 2.1把添加的元素封装为节点对象
Node node = new Node(null, element, null);
// 2.2让lastNode的next指向新添加的节点
lastNode.next = node;
// 2.3让node的prev指向lastNode
node.prev = lastNode;
// 2.4跟新链表的尾节点
lastNode = node;
// 2.5更新size实际存放元素的个数
size++;
}
}
/**
* 根据索引获取元素值
* @param index 索引值
* @return 索引对应的元素值
*/
public String get(int index) {
// 1.判断索引是否合法
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界异常:" + index);
}
// 2.根据索引获取节点
Node node = node(index);
// 3.返回节点对应数据
return node.data;
}
/**
* 根据索引删除元素
* @param index 索引值
*/
public void remove(int index) {
// 1.判断索引是否合法
if(index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界异常:" + index);
}
// 2.获取index对应的节点对象
Node node = node(index);
// 3.判断node是否为首节点
if(node == firstNode)
{
// 3.1把node的next节点设置为首节点
firstNode = node.next;
// 3.2把node的next节点设置为尾节点
firstNode.prev = null;
}
// 4.判断node是否为尾节点
else if (node == lastNode) {
// 4.1 把node的pre设置为尾节点
lastNode = node.prev;
// 4.2 把lastNode的next设置为空
lastNode.next = null;
}
// 5.执行到此处,证明node既不是首节点,也不是尾节点
else {
// 5.1 获取node节点的上一个节点和下一个节点
Node prevNode = node.prev;
Node nextNode = node.next;
// 5.2.更改prevNode和nextNode的指向关系
prevNode.next = nextNode;
nextNode.prev = prevNode;
}
// 6.更新size实际存放元素的个数
size--;
}
/**
* 根据索引插入元素
* @param element 插入的元素
* @param index 插入位置的索引
*/
public void add(String element, int index) {
// 1.判断索引是否合法
if(index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界异常:" + index);
}
// 2.判断插入的元素是否在链表的末尾
if(index == size) {
add(element);
}
// 3.判断插入的元素是否在链表的开头
else if(index == 0){
// 3.1把需要插入的元素包装为一个节点
Node node = new Node(null, element, null);
// 3.2更改firstNode和node之间的连线
firstNode.prev = node;
node.next = firstNode;
// 3.3更新链表的首节点
firstNode = node;
// 3.4更新size实际存放元素的个数
size++;
}
// 4.把index插入指定的位置(排除末尾和开头)
else {
// 4.1获取index对应的元素节点
Node nextNode = node(index);
// 4.2获取index对用的上一个元素节点
Node prevNode = nextNode.prev;
// 4.3把需要插入的元素包装为一个节点
Node newNode = new Node(null, element, null);
// 4.4更改newNode和prevNode之间的连线
newNode.prev = prevNode;
prevNode.next = newNode;
// 4.5更改newNode和nextNode之间的连线
nextNode.prev = newNode;
newNode.next = nextNode;
// 4.6更新size实际存放元素的个数
size++;
}
}
/**
* 根据索引获取对应的节点
* @param index 索引
* @return 返回索引对应的节点
*/
private Node node(int index) {
// 2.1如果索引小于size/2,证明查找的元素在上半区
if (index < size >> 1) {
// 从前往后找
Node node = firstNode;
for (int i = 0; i < index; i++) {
node = node.next;
}
// 返回索引对应的节点
return node;
}
// 2.2如果索引大于等于size/2,证明查找的元素在下半区
else {
// 从后往前找
Node node = lastNode;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
// 返回索引对应的节点
return node;
}
}
/**
* 链表的节点类(只能存储字符串)
*/
private class Node {
/**
* 指向上一个节点
*/
private Node prev;
/**
* 节点存储的数据
*/
private String data;
/**
* 指向下一个节点
*/
private Node next;
/**
* 无参构造方法
*/
public Node() {}
/**
* 有参构造方法
* @param prev
* @param data
* @param next
*/
public Node(Node prev, String data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
主方法,测试数据
public class Test01 {
public static void main(String[] args) {
LinkedList list = new LinkedList();
// 添加元素
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
list.add("ff");
// 删除元素
list.remove(5);
// 插入元素
// list.add("00", 0);
// list.add("00", list.size());
list.add("00", 2);
// 遍历元素
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
输出:
aa
bb
00
cc
dd
ee
大功告成,所有功能已经实现,可对着内存图理解记忆