LinkedList——链表
相比较于ArrayList的方法,除了Add(),get(),remove(),size()等,LinkedList多了addLast(),addFirst(),getFirst
1、一般将数据结构分为两大类:线性数据结构和非线性数据结构。线性数据结构有线性表、栈、队列、串、数组和文件;非线性数据结构有树和图。
线性表的逻辑结构是n个数据元素的有限序列: (a1, a2,a3,…an),n为线性表的长度(n≥0),n=0的表称为空表。数据元素呈线性关系。必存在唯一的称为“第一个”的数据元素;必存在唯一的称为“最后一个”的数据元素;除第一个元素外,每个元素都有且只有一个前驱元素;除最后一个元素外,每个元素都有且只有一个后继元素。所有数据元素在同一个线性表中必须是相同的数据类型。
线性表按其存储结构可分为顺序表和链表。用顺序存储结构存储的线性表称为顺序表;用链式存储结构存储的线性表称为链表。将线性表中的数据元素依次存放在某个存储区域中,所形成的表称为顺序表。一维数组就是用顺序方式存储的线性表。ArrayList就相当于顺序表。LinkedList就相当于链表。
2、单向链表
链表的操作
package com.wwy.linked;
public class TestLinked1 {
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
Node node1 = new Node("hehe");
Node node2 = new Node("haha");
Node node3 = new Node("xixi");
Node node4 = new Node("heihei");
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = null;
System.out.println(node1.next.data);
node1.next = node4;
node4.next = node2;
System.out.println(node1.next.data);
node4.next = null;
System.out.println(node1.next.data);
}
public static class Node
{
Node next;
String data;
public Node(String s)
{
data = s;
}
}
}
就上面的例子,
3、循环链表
单向循环链表删除一个节点的例子 (节点顺序是:1--》2--》3--》4删除2节点的代码是这样的)
注意:就单向链表而言,删除一个节点必须知道该节点的前一个节点,否则无法删除(至少我现在是这样认为的)
package com.wwy.linked;
public class TestLinked2 {
/**
* @param args
*/
public static void main(String[] args)
{
// TODO Auto-generated method stub
Node node1 = new Node("hehe");
Node node2 = new Node("haha");
Node node3 = new Node("xixi");
Node node4 = new Node("heihei");
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node1;
System.out.println(node1.next.data);
//删除2 注意:只能是循环链表才可以这样删除
Node temp = node2;
while (temp.next != node2) {
System.out.println("13" + temp.next.data + "||" + node2.data);
//通过循环找到2节点的前一个节点
temp = temp.next;
}
//这个时候temp是node2的前一个节点也就是node1
//把node1指向node3,相当于断开了1节点与2节点的链接
temp.next = node2.next;
//在断开2节点与3节点的链接
node2.next = null;
System.out.println(node1.next.data);
}
public static class Node
{
Node next;
String data;
public Node(String s)
{
data = s;
}
}
}
双向循环列表
下面模拟双向循环链表
package com.wwy.linked;
public class BidirectionalLinked {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//1-->2-->3-->1
Node node1 = new Node("hehe");
Node node2 = new Node("haha");
Node node3 = new Node("xixi");
node1.next = node2;
node1.previous = node3;
node2.previous = node1;
node2.next = node3;
node3.previous = node2;
System.out.println(node1.previous.data );
//插入Node4 1-->4-->2-->3-->1
Node node4 = new Node("heihei");
//插入到第二个位置
node1.next = node4;
node4.next = node2;
node4.previous = node1;
node2.previous = node4;
System.out.println(node4.next.data + "|||" +node4.previous.data );
//删除node2 1-->4-->3-->1
node4.next = node3;
node3.previous = node4;
node2.next = node2.previous = null;
System.out.println(node4.next.data);
}
public static class Node
{
Node previous;
Node next;
String data;
public Node(String s)
{
data = s;
}
}
}
4、LinkedList源代码分析:LinkedList底层就是使用了双向循环链表实现
- //先定义两个成员变量
- private transient Entry<E> header = new Entry<E>(null, null, null);
- private transient int size = 0;
- //默认构造函数
- public LinkedList() {
- header.next = header.previous = header;
- }
//先定义两个成员变量
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
//默认构造函数
public LinkedList() {
header.next = header.previous = header;
}
看一下Entry:
- private static class Entry<E> {
- E element;
- Entry<E> next;
- Entry<E> previous;
- Entry(E element, Entry<E> next, Entry<E> previous) {
- this.element = element;
- this.next = next;
- this.previous = previous;
- }
- }
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}
它的定义就像Node,有一个存放数据的成员变量element,还各有一个指向前驱和后继Entry的指针previous和next。
关于add()方法,参数是一个Object对象,作为Entry的element值
- public boolean add(E e) {
- addBefore(e, header);
- return true;
- }
- private Entry<E> addBefore(E e, Entry<E> entry) {
- Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
- newEntry.previous.next = newEntry;
- newEntry.next.previous = newEntry;
- size++;
- modCount++;
- return newEntry;
- }
public boolean add(E e) {
addBefore(e, header);
return true;
}
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
add()方法默认调用addBefore()方法插入一个新的节点,addBefore看其字面意思,就是在指定的节点之前插入,而其参数默认为e和header,e是要保存的数据,header是链表的头节点,按照上面的图,头节点的前驱就是链表的最后一个节点,所以插入默认是插入到链表最后。addBefore方法先构建一个新的Entry节点,根据其参数,这个新节点的element=e,next=entry,(也就是header),previous=entry.previous,(也就是header.previous)。然后修改原来最后一个节点的后继指向新节点:newEntry.previous.next = newEntry;然后在修改原来的头节点的前驱指向新节点:newEntry.next.previous = newEntry;
5、关于ArrayList与LinkedList的比较分析
1)ArrayList底层采用数组实现,LinkedList底层采用双向链表实现。
2)当执行插入或删除操作时,采用LinkedList比较好。
3)当执行搜索操作时,采用ArrayList比较好。
6、list的删除操作只删除找到匹配的第一个节点,如linkedlist.add("aaa");linkedlist.add("aaa");删除时,remove("aaa")只删除第一个找到的aaa节点。
在get()方法取第n个元素时,调用了entry()方法,这个方法使用了一个优化性能的算法,先判断n是否大于链表元素个数的二分之一,如果不大于,说明距离头节点比较近,从头开始先后遍历寻找,否则说明距离末节点比较近,从尾部开始向前遍历寻找。
7、当向ArrayList添加一个对象时,实际上就是将该对象放置到了ArrayList底层所维护的数组当中;当向LinkedList中添加一个对象时,实际上LinkedList内部会生成一个Entry对象,该对象的结构为:
Entry
{
Entry previous;
Object element;
Entry next;
}
其中的Object类型的元素element就是我们向LinkedList中所添加的元素,然后Entry又构造好了向前与向后的引用previous、next,最后将生成的这个Entry对象加入到了链表当中。换句话说,LinkedList中所维护的是一个个的Entry对象。
最后附上自己根据《数据结构与算法分析_Java语言描述中文第二版_Weiss+M.A》中讲链表的时候模拟LinkedList的小程序,自己在Iterator()和getNode(int idx)这两个方法不懂,尤其是getNode获取节点的判断条件为什么那样判定?希望看的大神解释一下
package com.wwy.list.test;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* 模拟双向循环链表
* @author wWX161568
*
* @param <AnyType>
*/
public class MyLinkedList<AnyType> implements Iterable<AnyType>
{
//链表的大小
private int theSize;
private int modCount = 0;
//开始节点
private Node<AnyType> beginMarker;
//链表的尾节点
private Node<AnyType> endMarker;
/**
* 节点类
* 节点的模型:(previous|data|next)才是一个节点
* @author wWX161568
*
* @param <AnyType>
*/
private static class Node<AnyType>
{
public AnyType data;
//节点的模型:(previous|data|next)才是一个节点,prev指向前一个节点(前驱)next指向后一节点(后继)
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()
{
clear();
}
/**
* 返回链表的大小
* @return
*/
public int size()
{
return theSize;
}
/**
* 判断链表是否为空
* @return
*/
@SuppressWarnings("unused")
private boolean isEmpty()
{
return size() == 0;
}
/**
* 讲元素添加到链表的尾部
* @param x
* @return
*/
public boolean add(AnyType x)
{
add(size(),x);
return true;
}
/**
* 讲元素添加到指定的位置
*/
public void add(int idx,AnyType x)
{
addBefore(getNode(idx),x);
}
/**
* 获取指定位置的元素
* @param idx 指定位置下标
* @return
*/
public AnyType get(int idx)
{
//根据下表先找到相应的节点,然后取出元素
return getNode(idx).data;
}
/**
* 替换指定位置的元素,并返回原来的元素
* @param idx指定位置下标
* @param newVal 要替换的元素
* @return 被替换的元素
*/
public AnyType set(int idx,AnyType newVal)
{
//获得指定位置的相应节点
Node<AnyType> p = getNode(idx);
//取得原来的(旧的)节点元素值
AnyType oldVal = p.data;
//用新的替换
p.data = newVal;
return oldVal;
}
/**
* 移除指定位置的元素
* @param idx
* @return
*/
public AnyType remove(int idx)
{
return remove(getNode(idx));
}
/**
* 插入元素,并指定他的前驱与后继
* @param p 要插入元素对应的节点
* @param x 要插入的元素
*/
private void addBefore(Node<AnyType> p,AnyType x)
{
//创建新的节点
Node<AnyType> newNode = new Node<AnyType>(x, p.prev, p);
//给新创建的节点建立起节点间的链接,也就是前驱与后继
newNode.prev.next = newNode;
p.prev = newNode;
//链表长度加1
theSize++;
modCount++;
}
/**
* 根据节点,移除该节点出的元素,并处理前后节点间的联系
* @param p
* @return
*/
private AnyType remove(Node<AnyType> p)
{
//取出要移除节点的元素
AnyType result = p.data;
//处理该节点的链接,也就是把该节点的前一个节点与后一个节点链接起来
p.next.prev = p.prev;
p.prev.next = p.next;
//并删除该节点的链接
p.next = p.prev = null;
//把该节点的元素值设为null
p.data = null;
//修改链表长度
theSize--;
modCount++;
return result;
}
@Override
public Iterator<AnyType> iterator() {
// TODO Auto-generated method stub
return new LinkedListIterator();
}
private class LinkedListIterator implements java.util.Iterator<AnyType>
{
private Node<AnyType> current = beginMarker.next;
private int expectedModCount = modCount;
private boolean okToRemove = false;
@Override
public boolean hasNext() {
// TODO Auto-generated method stub
return current != endMarker;
}
@Override
public AnyType next() {
// TODO Auto-generated method stub
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
if(!hasNext())
throw new NoSuchElementException();
AnyType nextItem = current.data;
current = current.next;
okToRemove = true;
return nextItem;
}
@Override
public void remove() {
// TODO Auto-generated method stub
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
if(!okToRemove)
throw new IllegalStateException();
MyLinkedList.this.remove(current.prev);
okToRemove = false;
expectedModCount++;
}
}
/**
* 清空链表
*/
private void clear() {
// TODO Auto-generated method stub
//得到开始节点
beginMarker = new Node<AnyType>(null,null,null);
//得到尾节点
endMarker = new Node<AnyType>(null, beginMarker, null);
//使开始节点的下一个节点指向尾节点,从而使他们中间没有节点,相应的也就是空链表
beginMarker.next = endMarker;
//链表的大小赋值为0
theSize = 0;
modCount ++;
}
/**
* 根据指定位置,获取他对应的节点
* @param idx
* @return
*/
private Node<AnyType> getNode(int idx)
{
Node<AnyType> p;
if(idx < 0 || idx > size())
throw new IndexOutOfBoundsException();
//不懂为什么这么判断
if(idx < size() / 2 ) //index < (size >> 1);
{
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;
}
}