1.基础知识
1.1线性结构比较
1.2 链表
- 数据存储在”节点”中
- 优点:真正的动态,不需要处理固定容量的问题
- 缺点:丧失了随机访问的能力。
1.3数组和链表的对比
2.添加元素
package com.antfin.datastructure.linkedlist;
public class LinkedList <E> {
private class Node{
private E e;
private Node next;
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 LinkedList(){
head=null;
size=0;
}
//获取链表中元素个数
public int getSize(){
return size;
}
//返回链表是否为空
public boolean isEmpty(){
return size==0;
}
//在链表头添加新的元素e
public void addFirst(E e){
head=new Node(e,head);
size++;
}
//在链表的index(0-based)位置添加新的元素e
//在链表中不是一个常用的操作,练习用
public void add(int index,E e){
if (index<0||index>size)
throw new IllegalArgumentException("Add failed. Illegal 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++;
}
}
public void addLast(E e){
add(size,e);
}
}
3.虚拟头结点(DummyHead)
使用虚拟节点可以使添加头结点和添加非头结点的逻辑保持一致。
package com.antfin.datastructure.linkedlist;
public class LinkedList <E> {
private class Node{
private E e;
private Node next;
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 LinkedList(){
dummyHead=new Node();
size=0;
}
//获取链表中元素个数
public int getSize(){
return size;
}
//返回链表是否为空
public boolean isEmpty(){
return size==0;
}
//在链表头添加新的元素e
public void addFirst(E e){
add(0,e);
}
//在链表的index(0-based)位置添加新的元素e
//在链表中不是一个常用的操作,练习用
public void add(int index,E e){
if (index<0||index>size)
throw new IllegalArgumentException("Add failed. Illegal index.");
Node pre=dummyHead;
for (int i=0;i<index;i++){
pre=pre.next;
}
pre.next=new Node(e,pre.next);
size++;
}
public void addLast(E e){
add(size,e);
}
}
4.包含、查询和修改
// 获得链表的第index(0-based)个位置的元素
// 在链表中不是一个常用的操作,练习用:)
public E get(int index){
if (index<0||index>size-1)
throw new IllegalArgumentException("Get failed.Illegal index");
Node target=dummyHead;
for (int i=0;i<=index;i++)
target=target.next;
return target.e;
}
public E getFirst(){
return get(0);
}
public E getLast(){
return get(size-1);
}
// 修改链表的第index(0-based)个位置的元素为e
// 在链表中不是一个常用的操作,练习用:)
public void set(int index,E e){
if (index<0||index>size-1)
throw new IllegalArgumentException("Set failed.Illegal index");
Node target=dummyHead;
for (int i=0;i<=index;i++)
target=target.next;
target.e=e;
}
// 查找链表中是否有元素e
public boolean contain(E e){
Node target=dummyHead.next;
while (target!=null){
if (target.e.equals(e))
return true;
target=target.next;
}
return false;
}
5.删除元素
// 从链表中删除index(0-based)位置的元素, 返回删除的元素
// 在链表中不是一个常用的操作,练习用:)
public E remove(int index){
if (index <0||index>size-1)
throw new IllegalArgumentException("Delete failed.index illegal");
Node pre=dummyHead;
for (int i=0;i<index;i++){
pre=pre.next;
}
Node retNode=pre.next;
pre.next=retNode.next;
retNode.next=null;//之前没有考虑到这一步的操作,这一步的目的在于便于垃圾回收
size--;
return retNode.e;
}
public E removeLast(){
return remove(size-1);
}
public E removeFirst(){
return remove(0);
}
6.时间复杂度
7.完整代码
package com.antfin.datastructure.linkedlist;
public class LinkedList <E> {
private class Node{
private E e;
private Node next;
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 LinkedList(){
dummyHead=new Node();
size=0;
}
//获取链表中元素个数
public int getSize(){
return size;
}
//返回链表是否为空
public boolean isEmpty(){
return size==0;
}
//在链表头添加新的元素e
public void addFirst(E e){
add(0,e);
}
//在链表的index(0-based)位置添加新的元素e
//在链表中不是一个常用的操作,练习用
public void add(int index,E e){
if (index<0||index>size)
throw new IllegalArgumentException("Add failed. Illegal index.");
Node pre=dummyHead;
for (int i=0;i<index;i++){
pre=pre.next;
}
pre.next=new Node(e,pre.next);
size++;
}
public void addLast(E e){
add(size,e);
}
// 获得链表的第index(0-based)个位置的元素
// 在链表中不是一个常用的操作,练习用:)
public E get(int index){
if (index<0||index>size-1)
throw new IllegalArgumentException("Get failed.Illegal index");
Node target=dummyHead;
for (int i=0;i<=index;i++)
target=target.next;
return target.e;
}
public E getFirst(){
return get(0);
}
public E getLast(){
return get(size-1);
}
// 修改链表的第index(0-based)个位置的元素为e
// 在链表中不是一个常用的操作,练习用:)
public void set(int index,E e){
if (index<0||index>size-1)
throw new IllegalArgumentException("Set failed.Illegal index");
Node target=dummyHead;
for (int i=0;i<=index;i++)
target=target.next;
target.e=e;
}
// 查找链表中是否有元素e
public boolean contain(E e){
Node target=dummyHead.next;
while (target!=null){
if (target.e.equals(e))
return true;
target=target.next;
}
return false;
}
// 从链表中删除index(0-based)位置的元素, 返回删除的元素
// 在链表中不是一个常用的操作,练习用:)
public E remove(int index){
if (index <0||index>size-1)
throw new IllegalArgumentException("Delete failed.index illegal");
Node pre=dummyHead;
for (int i=0;i<index;i++){
pre=pre.next;
}
Node retNode=pre.next;
pre.next=retNode.next;
retNode.next=null;//之前没有考虑到这一步的操作,这一步的目的在于便于垃圾回收
size--;
return retNode.e;
}
public E removeLast(){
return remove(size-1);
}
public E removeFirst(){
return remove(0);
}
@Override
public String toString() {
StringBuilder res=new StringBuilder();
for (Node tar=dummyHead.next;tar!=null;tar=tar.next){
res.append(tar+"-->");
}
res.append("NULL");
return res.toString();
}
}
8.链表应用1:栈
package com.antfin.datastructure.linkedlist;
public class LinkedListStack <E> implements Stack<E>{
private LinkedList<E> list;
public LinkedListStack(){
list=new LinkedList<>();
}
@Override
public int getSize(){
return list.getSize();
}
@Override
public boolean isEmpty(){
return list.getSize()==0;
}
@Override
public void push(E e){
list.addFirst(e);
}
@Override
public E pop(){
return list.removeFirst();
}
@Override
public E peak(){
return list.getFirst();
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("Stack :top");
res.append(list);
return res.toString();
}
}
与ArrayStack比较
- 时间复杂度在同一级别
- ArrayStack耗时在于开辟新的空间
- LinkedListStack耗时在于new操作
9.链表应用2:队列 (使用尾指针)
两端插入元素都很容易,但是tail端删除元素不容易,所以head为队首,tail端为队尾。
package com.antfin.datastructure.queue;
public class LinkedListQueue<E> implements queue<E> {
private class Node{
private E e;
private Node next;
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 LinkedListQueue(){
head=null;
tail=null;
size=0;
}
@Override
public int getSize(){
return size;
}
@Override
public boolean isEmpty(){
return size==0;
}
@Override
public void enqueue(E e){
if (tail==null){
tail=new Node(e);
//tail为空,那么head应该也为空
head=tail;
}else {
tail.next=new Node(e);
tail=tail.next;
}
size++;
}
@Override
public E dequeue(){
if (isEmpty())
throw new IllegalArgumentException("queue is empty,cannot dequeue");
Node retNode=head;
head=head.next;
retNode.next=null;
if (head==null)
tail=null;
size--;
return retNode.e;
}
@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("Queue is empty.");
return head.e;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue: front ");
Node cur = head;
while(cur != null) {
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL tail");
return res.toString();
}
public static void main(String[] args){
LinkedListQueue<Integer> queue = new LinkedListQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}