02-java实现单链表
02-手撸链表
本篇是恋上数据结构第一季个人总结
借鉴https://juejin.im/post/6844904001478066183#heading-0
本人git https://github.com/bigeyes-debug/Algorithm
目录
一丶链表定义
链表是一种链式存储的线性表, 所有节点的内存地址不一定是连续的。
二丶链表设计
-
创建LinkedList类,用来管理链表数据,其中的size属性记录存储数据的数量,first属性引用链表的第0个节点。
-
创建私有类Node,其中的element属性用于存储节点,next属性用于指向链表中的下一个节点。
public class SIngleLinkedList<E> extends
AbstractList<E>{
private Node<E> first;
private static class Node<E>{
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
接口
// 节点的数量
int size();
// 是否为空
boolean isEmpty();
// 是否包含某个节点
boolean contains(E element);
// 添加节点到最后面
void add(E element);
// 返回index位置对应的节点
E get(int index);
// 设置index位置的节点
E set(int index, E element);
// 往index位置添加节点
void add(int index, E element);
// 删除index位置对应的节点
E remove(int index);
// 查看节点的位置
int indexOf(E element);
// 清除所有节点
void clear();
与动态数组的接口类似,我们可以抽取接口,然后实现,后面我们会提到
三丶链表的实现
3.1 构造方法
- 链表的创建与动态数组不同,动态数组在构造时需要传入一个空间属性,来决定这个数组的容量。但链表节点是在添加时才创建的,内存地址不一定是连续的。所以链表不需要在单独设计构造方法,使用默认构造方法即可。
3.2 添加节点
- 添加节点时候只需要到将最后节点的next指针指向新添加的节点
- 当指定位置添加节点的时候,需要插入位置的前驱,
- 然后用前驱的next指针指向新添加的节点,并且该结点next指向原来的结点
- 需要特别注意的地方是当前插入位置是0时,因为他没没有前驱,需要做特殊处理
代码如下
@Override
public void add(int index, E element) {
//检查越界 和动态数组类似
rangeCheck(index);
//特殊处理
if(index ==0) {
first=new Node<>(element, first);
}else {
// 获得前驱
Node <E>prev=node(index-1);
//上图的①②
prev.next=new Node<>(element, prev.next);
}
// TODO Auto-generated method stub
size++;
}
public void add(E element) {
// 节点添加到size位置, 即添加到最后面
add(size, element);
}
3.3 删除节点
- 删除指定位置的节点,需要找到前驱
- 用前驱next指向该位置节点的后继
- 由于头结点没有前驱,所以我们仍需要对头结点的删除做特殊处理,只需要将头结点指向头结点的后继
//这里我们用到了一个node函数,node是根据索引获取该节点的节点
public E remove(int index) {
rangeCheck(index);//检查越界
Node <E> node =first;
// TODO Auto-generated method stub
if(index==0) {
first=first.next;
}else {
//或得前驱
Node <E>prev=node(index -1);
//index位置的节点
node =prev.next;
//前驱的后继是index位置的后继
prev.next=prev.next.next;
}
size--;
return node.element;
}
private Node<E> node(int index){
rangeCheck(index);
Node<E> node=first;
for(int i=0;i<index;i++) {
node=node.next;
}
return node;
}
3.4 修改节点
修改节点首先根据节点的索引找到节点,然后修改节点的元素,最后返回原来节点的元素的值
public E set(int index, E element) {
Node <E> node=node(index);
E old=node.element;
node.element=(E) element;
return old;
// TODO Auto-generated method stub
}
3.5查找节点
3.5.1 根据下标查找
找到对应的节点, 取出元素即可。
public E get(int index) {
// TODO Auto-generated method stub
return node(index).element;
}
3.5.2 根据元素值查找
- 查找指定元素的索引,需要遍历所有节点,找到节点对应的元素与执行元素相等即可。
- 如果需要支持节点element为null,则需要分两种情况处理。
一定要分开处理,,如果传入的element为null,调用equals方法会报错, 至于为什么用equlas方法,而不用==,自行百度Java基础
public int indexOf(E element) {
if(element == null) {
Node <E> node= first;
for(int i=0;i<size;i++) {
if(node.element==null) {
return i;
}
node=node.next;
}
}else {
Node <E> node= first;
for(int i=0;i<size;i++) {
if(element.equals(node.element)) {
return i;
}
node=node.next;
}
}
return ELEMENT_NOT_FOUND;
}
3.6 获取链表元素的个数
public int size() {
return size;
}
3.7 链表是否为空
public boolean isEmpty() {
return size == 0;
}
3.7 元素是否存在
public boolean contains(E element) {
return indexOf(element) != ELEMENT_ON_FOUND;
}
3.8 打印链表中存储的数据
@Override
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size = ").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(",");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
四丶链表的复杂度
五丶代码优化
通过编写代码发现,链表和动态数组的接口一样,部分代码共用,他俩都属于线性表
我们可以优化代码,具体如下
实现list接口
package com.bigeyes;
public interface List<E> {
static final int ELEMENT_NOT_FOUND = -1;
/**
* 清除所有元素
*/
void clear();
/**
* 元素的数量
* @return
*/
int size();
/**
* 是否为空
* @return
*/
boolean isEmpty();
/**
* 是否包含某个元素
* @param element
* @return
*/
boolean contains(E element);
/**
* 添加元素到尾部
* @param element
*/
void add(E element);
/**
* 获取index位置的元素
* @param index
* @return
*/
E get(int index);
/**
* 设置index位置的元素
* @param index
* @param element
* @return 原来的元素ֵ
*/
E set(int index, E element);
/**
* 在index位置插入一个元素
* @param index
* @param element
*/
void add(int index, E element);
/**
* 删除index位置的元素
* @param index
* @return
*/
E remove(int index);
/**
* 查看元素的索引
* @param element
* @return
*/
int indexOf(E element);
}
定义AbstractList抽象类,实现list接口,并且共用代码在这实现
package com.bigeyes;
public abstract class AbstractList<E> implements
List<E> {
/**
* 元素的数量
*/
protected int size;
/**
* 元素的数量
* @return
*/
public int size() {
return size;
}
/**
* 是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 是否包含某个元素
* @param element
* @return
*/
public boolean contains(E element) {
return indexOf(element) !=
ELEMENT_NOT_FOUND;
}
/**
* 添加元素到尾部
* @param element
*/
public void add(E element) {
add(size, element);
// System.out.println(siArrayList.javaze);
}
protected void outOfBounds(int index) {
throw new
IndexOutOfBoundsException("Index:" + index + ",
Size:" + size);
}
protected void rangeCheck(int index) {
if (index < 0 || index >= size) {
outOfBounds(index);
}
}
protected void rangeCheckForAdd(int index) {
if (index < 0 || index > size) {
outOfBounds(index);
}
}
}