链表数据存储在节点Node中,真正的动态,不需要处理固定容量的问题。
链表的实现
package com.yan.study.algorithm.link;
/**
* 实现自己的链表
* @param <E>
*/
public class LinkedList<E> {
private class Node{
public E e; //存储当前的元素
public Node next; //存储指向的下一个元素
public Node(E e,Node next){
this.e = e;
this.next = next;
}
public Node() {
this(null,null);
}
public Node(E e){
this(e,null);
}
@Override
public String toString(){
return e.toString();
}
}
//一开始的设计,链表只需要一个头即可无限连接下去
// private Node head;
private Node dummyHead;//然后进化成关键在于找到要添加的结点的前一个结点,所以给头结点设置了一个前面的虚拟结点
int size;
public LinkedList(){
// head = null;
dummyHead = new Node(null,null);
size = 0;
}
//获取链表中的个数
public int getSize(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
//在链表头添加元素
public void addFirst(E e){
// //自己构建一个node
// Node node = new Node(e);
// node.next = head;
// //将head指向这个node,也就是让这个node成为头结点
// node = head ; //这是我自己开始写的错误,没有理解引用
// head = node;
//实际以上步骤可以合为一个
// head = new Node(e,head);
// size++;
add(0,e);
}
//在链表index(0-based)位置添加,关键在于找到要添加的结点的前一个结点
public void add(int index,E e){
if(index<0 || index>size){
throw new IllegalArgumentException("index is fault");
}
//关键在于索引和node如何关联上?
// Node node = new Node<>(e);
// node.next = prev.next;
// prev.next = node;//等同于如下一行
Node prev =dummyHead;
for (int i=0;i<index;i++){
prev = prev.next;
}
prev.next = new Node(e,prev.next);
size++;
}
public void addLast(E e){
add(size,e);
}
//在链表index(0-based)位置查找
public E get(int index){
if(index<0 || index>=size){
throw new IllegalArgumentException("index is fault");
}
Node cur = dummyHead.next;
for(int i=0;i<index;i++){
cur = cur.next;
}
return cur.e;
}
//获得链表的第一个元素
public E getFirest(){
return get(0);
}
//获取最后一个
public E getLast(){
return get(size-1);
}
//更新index位置的元素
public void set(int index,E e){
Node cur = dummyHead.next;
for(int i=0;i<index;i++){
cur = cur.next;
}
cur.e=e;
}
//查找链表中是否有元素e
public boolean contains(E e){
Node cur = dummyHead.next;
// for(int i=0;i<size;i++){
// cur = cur.next;
// if(cur.e.equals(e)){
// return true;
// }
// }
// return false;
while(cur!=null) {
if (cur.e.equals(e)) {
return true;
}
}
return false;
}
//删除指定索引位置的元素
public E remove(int index){
if(index<0 || index>=size){
throw new IllegalArgumentException("index is fault");
}
Node prev = dummyHead;
for(int i=0;i<index;i++){
prev = prev.next;
}
Node delNode = prev.next ;
prev.next = delNode.next;
delNode.next =null;
size--;
return delNode.e;
}
//从链表中删除第一个元素
public E removeFirst(){
return remove(0);
}
//从链表中删除最后一个元素
public E removeLast(){
return remove(size-1);
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("");
Node cur = dummyHead.next;
while(cur!=null){
res.append(cur+"->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
链表的复杂度分析
- addLast(e) O(n)
- addFirst(e) O(1)
- add(index,e) O(n)
- removeLast(e) O(n)
- removeFirst(e) O(1)
- remove(index,e) O(n)
- set(index,e) O(n)
- get(index) O(n)
- contains(e) O(n)
综上对比来说,链表的增删改查基本都是O(n),如果只对链表头操作则是O(1).
链表实现栈
package com.yan.study.algorithm.link;
import com.yan.study.algorithm.stack.Stack;
/**
* 用链表实现栈
*/
public class LinkedStack<E> implements Stack<E> {
private LinkedList<E> linkedList;
public LinkedStack(){
linkedList = new LinkedList<>();
}
public void push(E e){
linkedList.addFirst(e);
}
public E pop(){
return linkedList.removeFirst();
}
@Override
public E peek() {
return linkedList.getFirst();
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Stack:top");
res.append(linkedList);
return res.toString();
}
}
链表实现队列
package com.yan.study.algorithm.link;
import com.yan.study.algorithm.queue.Queue;
/**
* 用链表实现队列
*/
public class LinkedQueue<E> implements Queue<E>{
private class Node{
public E e; //存储当前的元素
public Node next; //存储指向的下一个元素
public Node(E e, Node next){
this.e = e;
this.next = next;
}
public Node() {
this(null,null);
}
public Node(E e){
this(e,null);
}
@Override
public String toString(){
return e.toString();
}
}
private Node head,tail;
private int size;
public LinkedQueue(){
head = null;
tail = null;
size = 0;
}
//从链表尾部入
@Override
public void enqueue(E e) {
//如果tail是空,其实就是第一个
if(tail == null){
tail = new Node(e);
head = tail;
}else{
tail.next = new Node(e);
tail = tail.next;
}
size++;
}
@Override
public E dequeue() {
if(isEmpty()){
throw new IllegalArgumentException("can not dequeue from empty Queue");
}
//从头部取
Node ret = head;
head = head.next;
ret.next = null;
//如果只有一个元素
if(head == null){
tail =null;
}
size--;
return ret.e;
}
@Override
public E getFront() {
return head.e;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return 0==size;
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("Queue:front");
Node cur = head.next;
while(cur!=null){
res.append(cur+"->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
}
注意:虽然链表头的操作都是O(1),但是每次却需要new Node 内存操作。因此对于规模较大的数据时,不一定很快。