一、链表
链表是一种动态数组,向比较栈、队列来说,栈、队列的底层还是一个静态数组,靠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");
}
}
结果如下: