单链表
链表设计
接口设计
LinkedList 类
构造方法和头结点
/**
* 头结点
*/
private Node<E> head;
/**
* 构造方法
* @param <E>
*/
private static class Node<E>{
//节点元素
E element;
//下一个节点引用
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
清空链表 clear()
头指针指向空,链表长度置为0
/**
* 清除所有元素
*/
@Override
public void clear() {
size = 0;
head = null;
}
添加节点 add( int index, E e)
要注意边界情况,index = 0时,index = size - 1时
1、 插入位置为头结点后面
2 、 插入位置为头结点之后的位置
/**
* 在index位置插入一个元素
* @param index
* @param e
*/
@Override
public void add(int index, E e) {
//特判往头结点后插入元素的情况
if( index == 0){
Node<E> node = new Node(e, head);
head = node;
}else{
//获取前驱节点
Node<E> pre = get_Node(index - 1);
Node<E> node = new Node(e,pre.next);
pre.next = node;
}
size ++;
}
尾部节点插入 元素的方法add(E element)也是使用add(int index, E e)方法,只需在AbstractList调用该方法并指定index = size即可。这是多态的表现,接口(List)声明的变量 (list)被赋值为该接口实现类(ArrayList)的对象的引用。则调用该接口的方法,就可以调用到接口的实现类的方法。
调用接口List的void add(E i),会调用其实现类AbstracList的void add(E element)
public void add(E element) {
add(size, element);
}
而该方法又会去调用 add(size, element);调用add(size, element)的过程与之类似,最后调用的是LinkedList的add(int index, E e)
public void add(E element) {
add(size, element);
}
删除节点 remove(int index)
注意边界情况,当index == 0时,一般情况是index >= 1
/**
* 按索引删除元素,返回被删除元素的值
*
* @param index
*/
@Override
public E remove(int index) {
Node<E> node = head;
if( index == 0){
head = head.next;
}else{
Node<E> pre = get_Node(index - 1);
node = pre.next;
pre.next = node.next;
}
//size要减1
size --;
return node.element;
}
indexOf(Element e) 返回元素所在节点下标
/**
* 返回元素所在节点下标
*
* @param element
*/
@Override
public int indexOf(E element) {
Node<E> p = head;
//i记录下标
int i = 0;
//遍历链表
while( p != null)
{
if( p.element == element){
return i;
}
p = p.next;
i ++;
}
return ELEMENT_NOT_FOUND ;
}
接口与抽象类
单链表和双链表的接口和抽象类都是一样的,如下
List接口
package DoubleList;
interface List<E> {
/**
* 公共接口,将共有的且每个子类具体实现都不一样的地方抽象出来放在接口处.
* 接口本身就是交由子类实现的,因此其权限本身就是public的,无需写
*/
static final int ELEMENT_NOT_FOUND = - 1;
/**
* 清除所有元素
*/
void clear();
/**
* 返回长度
*/
int size();
/**
* 判断是否为空
*/
boolean isEmpty();
/**
* 判断是否包含该元素
*/
boolean contains(E element);
/**
* 在index位置插入一个元素
*/
void add(int index, E e);
/**
* 按索引获取节点元素值
*
* @param i
*/
E get(int i);
/**
* 设置index位置的元素 返回原来元素
*/
E set(int index, E element);
/**
* 按索引删除元素
*/
E remove(int index);
/**
* 返回给定元素在的位置
*/
int indexOf(E element);
void add(E i);
}
AbstractList
package DoubleList;
abstract class AbstractList<E> implements List<E> {
/**
* 该类实现子类共有的方法
*/
/**
* Constructs a new object.
*/
public AbstractList() {
super();
}
/**
* 元素的数量
*/
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
*/
@Override
public void add(E element) {
add(size, element);
}
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);
}
}
}
SingleList
package SingleList;
public class SingleList<E> extends AbstractList<E> {
/**
* 内部类且声明为私有(只在链表LinkedList内部使用 对外部是不可见的)和静态
*/
/**
* 头结点:是对第一个节点的引用
*/
private Node<E> head;
/**
* 构造方法
* @param <E>
*/
private static class Node<E>{
E element;
Node<E> next;
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
/**
* 清除所有节点
*/
@Override
public void clear() {
//数量置为0 同时头结点也要指向空
size = 0;
head = null;
}
/**
* 在index位置插入一个节点
* @param index
* @param e
*/
@Override
public void add(int index, E e) {
System.out.println("SingleList.LinkedList……");
//先判断索引是否合法
rangeCheckForAdd(index);
//特判往头结点后插入节点的情况
if( index == 0){
//新节点
Node<E> node = new Node(e, head);
head = node;
}else{
//获取前驱节点
Node<E> pre = get_Node(index - 1);
//新节点
Node<E> node = new Node(e,pre.next);
pre.next = node;
}
//长度加1
size ++;
}
/**
* 按索引获取节点值
*
* @param i
*/
@Override
public E get(int i) {
return get_Node(i).element;
}
/**
* 设置index位置的节点,返回原来节点的元素值
* @param index
* @param element
*/
@Override
public E set(int index, E element) {
Node<E> node = get_Node(index);
E val = node.element;
node.element = element;
return val;
}
/**
* 按索引删除节点,返回被删除节点的元素值
*
* @param index
*/
@Override
public E remove(int index) {
Node<E> node = head;
if( index == 0){
head = head.next;
}else{
Node<E> pre = get_Node(index - 1);
node = pre.next;
pre.next = node.next;
}
//size要减1
size --;
return node.element;
}
/**
* 返回元素所在节点下标
*
* @param element
*/
@Override
public int indexOf(E element) {
Node<E> p = head;
//i记录下标
int i = 0;
//遍历链表
while( p != null)
{
if( p.element == element){
return i;
}
p = p.next;
i ++;
}
return List.ELEMENT_NOT_FOUND ;
}
/**
* 按索引获取某个节点
* @param index
* @return
*/
public Node<E> get_Node(int index){
rangeCheck(index);
Node<E> node = head;
for(int i = 0; i < index; i ++) node = node.next;
return node;
}
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = head;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
}
测试:
public class Main {
public static void main(String[] args) {
//父类引用指向子类对象 add方法也要在List接口中声明
//如果是只在LinkedList子类中定义 是无法通过父类引用来调用子类特有的方法的
List<Integer> list = new LinkedList<>();
list.add(20);
list.add(0,10);
list.add(30);
list.add(list.size() , 40);
[10, 20, 30, 40]
list.remove(0);
list.remove(list.size() - 1);
System.out.println(list);
System.out.println( list.get(1) );
System.out.println( list.contains(20) );
}
}
复杂度分析
最好 | 平均 | 最坏 | |
---|---|---|---|
add(int index, Element e) | O(1) | O(n) | O(n) |
remove(int index) | O(1) | O(n) | O(n) |
set(int index, Element e) | O(1) | O(n) | O(n) |
get(int index) | O(1) | O(n) | O(n) |
双向链表
LinkedList类方法及属性
/**
* 头结点(head):是对第一个节点的引用
* 尾结点(tail): 对最后一个节点的引用
*/
private Node<E> head;
private Node<E> tail;
/**
* 构造方法
* @param <E>
*/
private static class Node<E>{
E element;
Node<E> prev; //前驱
Node<E> next; //后继
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
}
clear
/**
* 清除所有节点
*/
@Override
public void clear() {
//数量置为0 同时头结点、尾结点也要指向空
size = 0;
tail = null;
head = null;
}
tail = null;head = null;之后,虽然每个节点存在相互引用,但是根据GC root是否可达,JVM中的GC会自动释放一连串的Node对象。
GC root对象是:
- 被局部变量所指向的对象
Node get_Node(int index)
待查找元素在左半边 从前往后查找。待查找元素在右半边 从后往前查找
/**
* 按索引获取某个节点
* @param index
* @return
*/
public Node<E> get_Node(int index){
rangeCheck(index);
if( index < (size >> 1) ){//待查找元素在左半边 从前往后查找
Node<E> node = head;
for(int i = 0; i < index; i ++) node = node.next;
return node;
}else{//待查找元素在右半边 从后往前查找
Node<E> node = tail;
for(int i = size - 1; i > index; i --) node = node.prev;
return node;
}
}
add(int index, E e)
【思路】
插入的位置可能在
1、尾部插入:涉及到tail尾结点。尾部插入时,也有特殊情况。即 index== size == 0,此时oldTail 为null,前一个节点应该为null,head 和tail都指向该新节点。
2、头部及中间位置插入:涉及到head头结点。头部插入(index==0且size > 0时),头结点head指向该节点。其他情况都是prev.next = current;
/**
* 在index位置插入一个节点
* @param index
* @param e
*/
@Override
public void add(int index, E e) {
//先判断索引是否合法
rangeCheckForAdd(index);
//往尾部添加节点 涉及到尾结点tail
if( index == size){
Node oldTail = tail;
tail = new Node(oldTail,e ,null);
//oldTail可能为null:当index == 0且size == 0时
if( oldTail == null ) head = tail;
else oldTail.next = tail;
}else{
//往[0,size - 1)位置添加节点
//且往0添加时 不是第一个节点 其后已经有节点存在了
Node next = get_Node(index);
Node prev = next.prev;
Node current = new Node(prev, e, next);
next.prev = current;
//插入位置是0 则head = current
if(prev == null) head = current;
//插入位置是除0和尾结点外的位置
else prev.next = current;
}
//长度加1
size ++;
}
E remove(int index)
/**
* 按索引删除节点,返回被删除节点的元素值
*
* @param index
*/
@Override
public E remove(int index) {
//检查索引是否合法
rangeCheck(index);
Node<E> node = get_Node(index);
Node prev = node.prev;
Node next = node.next;
//头结点
if( prev == null) head = next;
else prev.next = next;
//尾结点
if( next == null) tail = prev;
else next.prev = prev;
//size --
size --;
return node.element;
}
DoubleList.LinkedList
package DoubleList;
public class LinkedList<E> extends AbstractList<E> {
/**
* 内部类且声明为私有(只在链表LinkedList内部使用 对外部是不可见的)和静态
*/
/**
* 头结点(head):是对第一个节点的引用
* 尾结点(tail): 对最后一个节点的引用
*/
private Node<E> head;
private Node<E> tail;
/**
* 构造方法
* @param <E>
*/
private static class Node<E>{
E element;
Node<E> prev; //前驱
Node<E> next; //后继
public Node(Node<E> prev, E element, Node<E> next) {
this.prev = prev;
this.element = element;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if( this.prev != null) sb.append( this.prev.element);
else sb.append("null");
sb.append( "__").append( this.element ).append( "__");
if( this.next != null) sb.append( this.next.element);
else sb.append("null");
return sb.toString();
}
}
/**
* 清除所有节点
*/
@Override
public void clear() {
//数量置为0 同时头结点、尾结点也要指向空
size = 0;
tail = null;
head = null;
}
/**
* 在index位置插入一个节点
* @param index
* @param e
*/
@Override
public void add(int index, E e) {
//先判断索引是否合法
rangeCheckForAdd(index);
//往尾部添加节点 涉及到尾结点tail
if( index == size){
Node oldTail = tail;
tail = new Node(oldTail,e ,null);
//oldTail可能为null:当index == 0且size == 0时
if( oldTail == null ) head = tail;
else oldTail.next = tail;
}else{
//往[0,size - 1)位置添加节点
//且往0添加时 不是第一个节点 其后已经有节点存在了
Node next = get_Node(index);
Node prev = next.prev;
Node current = new Node(prev, e, next);
next.prev = current;
//插入位置是0 则head = current
if(prev == null) head = current;
//插入位置是除0和尾结点外的位置
else prev.next = current;
}
//长度加1
size ++;
}
/**
* 按索引获取节点值
*
* @param i
*/
@Override
public E get(int i) {
return get_Node(i).element;
}
/**
* 设置index位置的节点,返回原来节点的元素值
* @param index
* @param element
*/
@Override
public E set(int index, E element) {
Node<E> node = get_Node(index);
E val = node.element;
node.element = element;
return val;
}
/**
* 按索引删除节点,返回被删除节点的元素值
*
* @param index
*/
@Override
public E remove(int index) {
//检查索引是否合法
rangeCheck(index);
Node<E> node = get_Node(index);
Node prev = node.prev;
Node next = node.next;
//头结点
if( prev == null) head = next;
else prev.next = next;
//尾结点
if( next == null) tail = prev;
else next.prev = prev;
//size --
size --;
return node.element;
}
/**
* 返回元素所在节点下标
*
* @param element
*/
@Override
public int indexOf(E element) {
Node<E> p = head;
//i记录下标
int i = 0;
//遍历链表
while( p != null)
{
if( p.element == element){
return i;
}
p = p.next;
i ++;
}
return List.ELEMENT_NOT_FOUND ;
}
/**
* 按索引获取某个节点
* @param index
* @return
*/
public Node<E> get_Node(int index){
rangeCheck(index);
if( index < (size >> 1) ){//带查找元素在左半边 从前往后查找
Node<E> node = head;
for(int i = 0; i < index; i ++) node = node.next;
return node;
}else{
Node<E> node = tail;
for(int i = size - 1; i > index; i --) node = node.prev;
return node;
}
}
public String toString() {
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = head;
for (int i = 0; i < size; i++) {
if (i != 0) {
string.append(", ");
}
string.append(node);
node = node.next;
}
string.append("]");
return string.toString();
}
}
单链表与双链表的比较
双向链表与动态数组
单向循环链表
add
注意只有一个节点时的插入
/**
* 在index位置插入一个节点
* @param index
* @param e
*/
@Override
public void add(int index, E e) {
//先判断索引是否合法
rangeCheckForAdd(index);
//特判往头结点后插入节点的情况
if( index == 0){
//新节点
Node<E> node = new Node(e, head);
//size == 0时,最后一个节点即为 node
//在修改线之前去查找
Node<E> last= size == 0 ? node : get_Node(size - 1);
head = node; //头结点指向新节点
last.next = node;
}else{
//获取前驱节点
Node<E> pre = get_Node(index - 1);
//新节点
Node<E> node = new Node(e,pre.next);
pre.next = node;
}
//长度加1
size ++;
}
remove
/**
* 按索引删除节点,返回被删除节点的元素值
*
* @param index
*/
@Override
public E remove(int index) {
Node<E> node = head;
if( index == 0){
//index == 0 &&size == 1 头结点指向null
if( size == 1) head = null;
else{
Node last = get_Node(size - 1); //最后一个节点
head = head.next;
last.next = head;
}
}else{
Node<E> pre = get_Node(index - 1);
node = pre.next;
pre.next = node.next;
}
//size要减1
size --;
return node.element;
}