集合链表解析

本文详细介绍了LinkedList数据结构,对比ArrayList,强调了LinkedList作为链表的特性,包括添加、删除和搜索操作。内容涵盖线性表的分类、单向链表、循环链表以及LinkedList的源码分析,特别指出在特定操作下LinkedList相对于ArrayList的优势。
摘要由CSDN通过智能技术生成

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底层就是使用了双向循环链表实现

  1. //先定义两个成员变量   
  2. private transient Entry<E> header = new Entry<E>(nullnullnull);  
  3. private transient int size = 0;  
  4. //默认构造函数   
  5. public LinkedList() {  
  6.         header.next = header.previous = header;  
  7.     }  
//先定义两个成员变量
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
//默认构造函数
public LinkedList() {
        header.next = header.previous = header;
    }

看一下Entry:

  1. private static class Entry<E> {  
  2.     E element;  
  3.     Entry<E> next;  
  4.     Entry<E> previous;  
  5.   
  6.     Entry(E element, Entry<E> next, Entry<E> previous) {  
  7.         this.element = element;  
  8.         this.next = next;  
  9.         this.previous = previous;  
  10.     }  
  11.     }  
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值

  1.  public boolean add(E e) {  
  2.     addBefore(e, header);  
  3.         return true;  
  4.     }  
  5.   
  6. private Entry<E> addBefore(E e, Entry<E> entry) {  
  7.     Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);  
  8.     newEntry.previous.next = newEntry;  
  9.     newEntry.next.previous = newEntry;  
  10.     size++;  
  11.     modCount++;  
  12.     return newEntry;  
  13.     }  
 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;
	}
	


}


 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值