数据结构----链表(单向链表)(java实现代码)

一、链表

链表是一种动态数组,向比较栈、队列来说,栈、队列的底层还是一个静态数组,靠resize解决固定容量问题。而链表是真正的动态数据结构,也是最简单的动态数据结构。学习链表更深入的理解了指针,更深入理解递归,链表也辅助组成其他数据结构
1.什么是链表(单向链表)
数据存储在“结点Node”中,Node中包含数值和下一个结点的指向,链表的优点:真正的动态,不需要处理固定容量问题;缺点:丧失了随机访问的能力,即不能像数组那样随机访问,必须从头遍历。

设立一个head,head即指向链表的第一个结点
链表实现的部分功能代码:

package com.lxr.LinkList0403;

public class Linklist<E> {
	
//创建node结点对象
	private class Node {
		Node next;
		E e;
		
		public Node(E e,Node next) {
			this.e=e;
			this.next=next;
		}
		
		public Node(E e) {
			this(e, null);
		}
		
		public Node() {
			this(null, null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}
	
	private Node head;
	private int size;
	
	public Linklist() {
		head = null;
		size=0;
	}

	public Linklist(E[] e) {
		//传入一个数组将数组变成链表(**)
		this();
		if(e!=null) {
			head=new Node(e[0]);
			size=1;
			for(int i=1;i<e.length;i++) {
				addLast(e[i]);
			}
		}
	}
	
	//获取链表中的元素个数
	public int getSize() {
		return size;
	}
	
	//返回链表是否为空
	public boolean isEmpty() {
		return size==0;
	}
	
	//在链表头添加新元素e
	public void addfirst(E e) {
//		Node node=new Node(e);
//		node.next=head;
//		head=node;
		//可以这样写:
		head=new Node(e,head);
		size++;
	}
	
	//在链表的index(0-based)位置添加元素e
	//在链表中不是一个常用操作,练习用
	public void add(E e,int index) {
		
		if(index<0||index>size)
			throw new IllegalArgumentException("添加失败,index越界");
		
		if(index==0)
			addfirst(e);
		else {
			Node pre=head;
			for(int i=0;i<index-1;i++)
				pre=pre.next;
			
			pre.next=new Node(e, pre.next);
			size++;
		}
	}

	//在链表尾添加新元素e
	public void addLast(E e) {
		add(e, size);
	}
}

2.设立虚拟头结点
上面的代码是设立一个head,head即指向链表的第一个结点。这样如果对第一个元素进行操作时就需要特殊处理。所以使用一个技巧,为链表设立虚拟头结点dummyhead,结点值为空并指向第一个结点,即设立一个空结点dummyhead,dummyhead的next指向第一个结点,这样对于第一个元素的操作就不需要单独拿出来进行处理。
在这里插入图片描述

链表删除就是待删除元素的上一个元素的next指向待删除元素的下一个元素,再令待删除元素的next为空。
代码实现如下:

package com.lxr.LinkList0403;

import java.awt.image.SinglePixelPackedSampleModel;

public class Linklist<E> {
	
	
//创建node结点对象
	private class Node {
		Node next;
		E e;
		
		public Node(E e,Node next) {
			this.e=e;
			this.next=next;
		}
		
		public Node(E e) {
			this(e, null);
		}
		
		public Node() {
			this(null, null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}
	
	private Node dummyhead;
	private int size;
	
	public Linklist() {
		dummyhead =new Node(null,null);
		size=0;
	}

	public Linklist(E[] e) {
		//传入一个数组将数组变成链表(**)
		this();
		if(e!=null) {
			for(int i=0;i<e.length;i++) {
				addLast(e[i]);
			}
		}
	}
	
	//获取链表中的元素个数
	public int getSize() {
		return size;
	}
	
	//返回链表是否为空
	public boolean isEmpty() {
		return size==0;
	}
	
	
	
	//在链表的index(0-based)位置添加元素e。O(n/2)=O(n)
	//在链表中不是一个常用操作,练习用
	public void add(E e,int index) {
		
		if(index<0||index>size)
			throw new IllegalArgumentException("添加失败,index越界");
		
		
		Node pre=dummyhead;
		//pre算是index=-1的元素,所以遍历结果是插入索引的前一个位置
		for(int i=0;i<index;i++)
			pre=pre.next;
			
		pre.next=new Node(e, pre.next);
		size++;
	}

	//在链表头添加新元素e。O(1)
	public void addfirst(E e) {
		add(e, 0);
	}
		
	//在链表尾添加新元素e。O(n)
	public void addLast(E e) {
		add(e, size);
	}
	
	//链表遍历找到index的元素。O(n)
	//在链表中不是一个常用操作,练习用
	public E getE(int index) {
		if(index<0||index>size)
			throw new IllegalArgumentException("添加失败,index越界");

		Node cur=dummyhead.next;
		for(int i=0;i<index;i++) 
			cur=cur.next;
		
		return cur.e;
	}
	
	//获取第一个元素的值
	public E getFirst() {
		return getE(0);
	}
	
	//获取最后个元素的值
	public E getLast() {
		return getE(size-1);
	}
	
	//修改链表index的元素为e。O(n)
	//在链表中不是一个常用操作,练习用
	public void set(E e,int index) {
		if(index<0||index>size)
			throw new IllegalArgumentException("添加失败,index越界");
		
		Node cur=dummyhead.next;
		for(int i=0;i<index;i++) 
			cur=cur.next;
		
		cur.e=e;
	}
	
	//查找链表中是否存在元素e。O(n)
	public boolean contains(E e) {
		Node cur=dummyhead.next;
		for(int i=0;i<size;i++) {
			if(cur.e.equals(e))
				return true;
			cur=cur.next;
		}
		
		return false;
	}
	
	//删除链表index的元素,返回其值.O(n/2)=O(n)
	//在链表中不是一个常用操作,练习用
	public E remove(int index) {
		if(index<0||index>size)
			throw new IllegalArgumentException("添加失败,index越界");
		
		Node cur=dummyhead;
		for(int i=0;i<index;i++) 
			cur=cur.next;
		Node retNode =cur.next;
		cur.next=retNode.next;
		retNode.next=null;
		size--;
		
		return retNode.e;
	}
	
	//删除第一个元素。O(1)
	public E removeFirst() {
		return remove(0);
	}
	
	//删除最后一个元素。O(n)
	public E removeLast() {
		return remove(size-1);
	}
	
	@Override
		public String toString() {
			StringBuilder stringBuilder =new StringBuilder();
			
			for(Node cur=dummyhead.next;cur!=null;cur=cur.next) 
				stringBuilder.append(cur+"->");
			stringBuilder.append("NULL");
			
			return stringBuilder.toString();
		}
}

测试代码:

package com.lxr.LinkList0403;

public class Main {

public static void main(String[] args) {
	// TODO Auto-generated method stub
//
//		//测试将数组转换成链表
//		Integer []a=new Integer[10];
//		for(int i=0;i<10;i++) {
//			a[i]=(Integer) i;
//			
//		}
//		Linklist<Integer> iLinklist=new Linklist<>(a);
//		System.out.println(iLinklist);
	
	//测试增改查
	Linklist<Integer> iLinklist=new Linklist<>();
	for(int i=0;i<10;i++) {
		iLinklist.addLast(i);	
		System.out.println(iLinklist);
	}
	
	iLinklist.set(666, 6);
	System.out.println(iLinklist);

	iLinklist.remove(6);
	System.out.println(iLinklist);
}

}

测试结果:
在这里插入图片描述
如果只对链表头进行操作,只查链表头元素,时间复杂度为O(1),比较有优势,链表是一种动态的数据结构

二、链表的应用

1.用链表实现栈
因为对于链表的头元素进行访问,添加,删除是其时间复杂度都为O(n),所以可以将表的第一个元素看做栈顶元素,就可以用链表实现一个栈。接下来就用刚刚创建的链表实现一个栈LinkLIstStack。

沿用之前栈的接口方法:

public interface Stack<E> {

	int getSize();//复杂度o(1)
	boolean isEmpty();//复杂度o(1)
	void push(E e);//复杂度o(1),均摊
	E pop();//复杂度o(1),均摊
	E peek();//复杂度o(1)
}

LinkLIstStack.java代码:

package com.lxr.LinkList0403;

public class LinkLIstStack<E> implements Stack<E>{

	private Linklist<E> linklist;
	
	public LinkLIstStack() {
		// TODO Auto-generated constructor stub
		linklist=new Linklist<>();
	}
	
	@Override
	public int getSize() {
		// TODO Auto-generated method stub
		return linklist.getSize();
	}

	@Override
	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return linklist.isEmpty();
	}

	@Override
	public void push(E e) {
		// TODO Auto-generated method stub
		linklist.addfirst(e);
	}

	@Override
	public E pop() {
		// TODO Auto-generated method stub
		return linklist.removeFirst();
	}

	@Override
	public E peek() {
		// TODO Auto-generated method stub
		return linklist.getFirst();
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		StringBuilder stringBuilder =new StringBuilder();
		stringBuilder.append("Stack: top");
		stringBuilder.append(linklist);
		return stringBuilder.toString();
	}
}

2.用链表实现队列
链表对于头操作时比较方便是因为头结点有一个head指向,所以在表尾引入tail来记录最后一个结点的位置。因为删除元素时链表需要知道前一个元素,在表头操作,而引入了tail来标记最后一个元素方便在表尾插入元素,所以在tail端插入元素,在head端删除元素。

因为都是在链表的head和tail完成,所以就不使用虚拟的头结点了,当没有dummyHead,要注意链表为空的情况(也可以使用dummyHead)
在这里插入图片描述
代码如下:

package com.lxr.Queue0402;
/**
 * 用链表实现队列
 * @author lixingrui
 *
 * @param <E>
 */
public class LinkListQueue<E> implements Queue<E>{

	//创建node结点对象
	private class Node {
		Node next;
		E e;
			
		public Node(E e,Node next) {
			this.e=e;
			this.next=next;
		}
			
		public Node(E e) {
			this(e, null);
		}
			
		public Node() {
			this(null, null);
		}
			
		@Override
		public String toString() {
	 	    return e.toString();
		}
	}
		
	private Node head,tail;
	private int size;
	
	public LinkListQueue() {
		// TODO Auto-generated constructor stub
		head=null;
		tail=null;
		size=0;
	}
		
	@Override
	public int getSize() {
		// TODO Auto-generated method stub
		return size;
	}

	@Override
	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return size==0;
	}

	@Override
	public void enqueue(E e) {
		// TODO Auto-generated method stub
		if(tail==null) {
			tail=new Node(e);
			head=tail;
		}else {
			tail.next=new Node(e);
			tail=tail.next;
		}
		size++;
	}

	@Override
	public E dequeue() {
		// TODO Auto-generated method stub
		if(isEmpty())
			throw new IllegalArgumentException("队列为空");
		Node reNode=head;
		head=head.next;
		reNode.next=null;
		//如果head为空时,说明队列中仅有的元素已经移除,即head和tail同时指向的那个节点移除了,让tail也变成NULL
		if(head==null)
			tail=null;
		return reNode.e;
	}

	@Override
	public E getFrount() {
		// TODO Auto-generated method stub
		if(isEmpty())
			throw new IllegalArgumentException("队列为空");
		return head.e;
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		StringBuilder reStringBuilder =new StringBuilder();
		reStringBuilder.append("Queue: front");
		
		Node cur =head;
		while (cur!=null) {
			reStringBuilder.append(cur+"->");
			cur=cur.next;
		}
		reStringBuilder.append("NULL tail");
		
		return reStringBuilder.toString();
	}
	
	public static void main(String[] args) {
		//测试
		LinkListQueue<Integer> linkListQueue=new LinkListQueue<>();
		for(int i=0;i<20;i++) {
			linkListQueue.enqueue(i);
			if(i%3==2) {
				linkListQueue.dequeue();
			}
			System.out.println(linkListQueue);
		}
	}

}

对数组队列,循环队列和链表队列进行测试

package com.lxr.Queue0402;

import java.util.Random;

public class Main {

	public static double test(Queue<Integer> q,int num) {
		//开始时间计时
		long startTime=System.nanoTime();
		
		//进行num次入队和出队的操作
		Random random=new Random();
		for(int i=0;i<num;i++)
			q.enqueue(random.nextInt(Integer.MAX_VALUE));
		for(int i=0;i<num;i++)
			q.dequeue();
		
		//结束时间计时
		long endTime=System.nanoTime();
		
		return (endTime-startTime)/1000000000.0;
	}
	
	public static void main(String[] args) {
		int num=100000;
		
		ArrayQueue<Integer> arrayQueue=new ArrayQueue<Integer>();
		double time1=test(arrayQueue, num);
		System.out.println("ArrayQueue,time: "+time1+" s");
		
		LoopQueue<Integer> loopQueue=new LoopQueue<Integer>();
		double time2=test(loopQueue, num);
		System.out.println("LoopQueue,time: "+time2+" s");
		
		LinkListQueue<Integer> linkListQueue =new LinkListQueue<>();
		double time3 =test(linkListQueue, num);
		System.out.println("linkListQueue,time: "+time3+" s");
	}

}

结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值