链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
主要有以下几种类型:
- 单向链表
- 双向链表
- 循环链表
主要方法:
- int getSize(); //得到链表元素个数
- boolean isEmpty(); //判断链表是否为空
- void add(int index,E e);//链表下标为index中添加元素
- void addFirst(E e); //链表头部添加元素
- void addLast(E e); //链表尾部添加元素
- E get(int index); //获得链表第index位置的元素
- E getFirst(); //获取链表第一个元素
- E getLast(); //获取链表最后一个元素
- void set(int index,E e); //更新链表index位置的元素
- boolean contains(E e); //查找链表中是否存在元素e
- E delete(int index); //删除角标为index的元素
- E deleteFirst(); //删除头结点
- E deleteLast(); //删除最后一个元素
设置头结点
在第一个元素结点进行插入或者删除操作时,使其操作与其他操作一致。带头结点,无论删除或者增加那个结点,代码一致。
单链表具体实现:
单链表是一种顺序结构,有一个头结点,没有值域,为空结点。有一个尾结点,为null。
import javax.xml.ws.soap.AddressingFeature;
public class LinkedList<E> {
/*
* 链表结点类
*/
class Node<E> {
public E e;
public Node next;
/**
* 构造函数
*/
public Node() {
e = null;
next = null;
}
public Node(E e) {
this.e = e;
next = null;
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 覆写toString方法
*/
@Override
public String toString() {
return e.toString();
}
}
// 设置空头节点 利用前驱结点处理后继结点(这样就不需要特殊处理头结点)
private Node dummyHead;
private int size; // 链表元素数量
// 链表构造函数
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
/**
* 返回链表元素数量
* @return
*/
public int getSize() {
return size;
}
/**
* 判断链表是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 在链表index(0-based)位置添加元素
*
* @param e
*/
public void add(int index, E e) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
//计算前驱结点变量
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
// Node node = new Node(e);
// node.next = prev.next;
// prev.next = node;
prev.next = new Node(e, prev.next);
size++;
}
/**
* 在链表头添加元素
*
* @param e
*/
public void addFirst(E e) {
add(0,e);
}
/**
* 在链表末尾添加元素
*
* @param e
*/
public void addLast(E e) {
add(size, e);
}
/**
* 获得链表第index位置的元素
* @param index
* @return
*/
public E get(int index) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
Node currentNode = dummyHead.next;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
return (E) currentNode.e;
}
/**
* 获得链表头结点
* @return
*/
public E getFirst() {
return get(0);
}
/**
* 获取链表最后一个元素
* @return
*/
public E getLast() {
return get(size-1);
}
/**
* 更新链表index位置的元素
* @param index
*/
public void set(int index,E e) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
Node currentNode = dummyHead.next;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
currentNode.e = e;
}
/**
* 查找链表中是否存在元素
* @param e
* @return
*/
public boolean contains(E e) {
Node currentNode = dummyHead.next;
while(currentNode != null) {
if(currentNode.e.equals(e))
return true;
currentNode = currentNode.next;
}
return false;
}
/**
* 从链表中删除index位置的元素 并返回元素
* @param index
* @return
*/
public E remove(int index) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node deleteNode = prev.next;
prev.next = deleteNode.next;
deleteNode.next = null;
size--;
return (E) deleteNode.e;
}
/**
* 删除第一个元素
* @return
*/
public E removeFirst() {
return remove(0);
}
/**
* 删除最后元素
* @return
*/
public E removeLast() {
return remove(size-1);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("linkedList:");
Node currentNode = dummyHead.next;
while(currentNode != null) {
sb.append(currentNode.e+"->");
currentNode = currentNode.next;
}
sb.append("NULL");
return sb.toString();
}
public static void main(String[] args) {
LinkedList<Integer> link = new LinkedList<Integer>();
for (int i = 0; i < 10; i++) {
link.addFirst(i);
System.out.println(link);
}
link.set(0, 100);
System.out.println(link);
link.remove(1);
System.out.println(link);
link.removeFirst();
System.out.println(link);
link.removeLast();
System.out.println(link);
}
}
循环链表的实现
循环链表与单链表区别主要是单链表尾结点指向NULL,而循环链表尾结点指向头结点。只需要修改add(int index,E e)方法如下:
/**
* 在链表index(0-based)位置添加元素
*
* @param e
*/
public void add(int index, E e) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
//计算前驱结点变量
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//如果为第一个元素 ,则让它指向自己
if(size == 0) {
Node newNode = new Node(e);
dummyHead.next = newNode;
newNode.next = newNode;
}
//
prev.next = new Node(e, prev.next);
size++;
}
双向链表
双向链表可以从正反方向遍历。
改变Node结点为以下:
class Node<E>{
private E e;
private Node prev;
private Node next;
public Node() {
this.e = null;
this.prev = null;
this.next = null;
}
public Node(E e) {
this.e = e;
this.prev = null;
this.next = null;
}
public Node(E e,Node prev,Node next) {
this.e = e;
this.prev = prev;
this.next = next;
}
}
双向链表成员变量和构造函数: 设置头结点和尾结点为空,将头结点的后继结点设为尾结点,将尾结点的前驱结点设为头结点。
private Node dummyHead,dummyTail;
private int size;
public ReLinkedList() {
dummyHead = new Node();
dummyTail = new Node();
dummyHead.next = dummyTail;
dummyTail.prev = dummyHead;
size = 0;
}
**添加元素:**找到添加位置的前驱结点,将新结点的前驱结点设为添加位置的前驱结点,后继结点设为添加位置的后置结点。然后将添加位置的后继结点的前驱结点设为新结点,最后将添加位置的后继结点设置为新结点。
/**
* 在链表index(0-based)位置添加元素
*
* @param e
*/
public void add(int index, E e) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
//计算前驱结点变量
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
// 新结点前驱指向prev 后继指向prev.next进行插入
Node node = new Node(e,prev,prev.next);
//将新结点的后继结点的前驱结点设为新结点
prev.next.prev = node;
//将新结点的前驱结点的后继结点设为新结点
prev.next = node;
size++;
}
删除元素:
找到删除结点的前驱结点,将删除结点的前驱结点的后继结点设为删除结点的后继结点,将删除元素的后继结点的前驱结点设为删除结点的前驱结点,最后将删除结点的前驱结点和后继结点设为null。
/**
* 从链表中删除index位置的元素 并返回元素
* @param index
* @return
*/
public E remove(int index) {
if (index > size && index < 0) {
throw new IllegalArgumentException("Add failed:illegal index");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node deleteNode = prev.next;
prev.next = deleteNode.next;
deleteNode.next.prev = prev;
deleteNode.prev = null;
deleteNode.next = null;
size--;
return (E) deleteNode.e;
}
单链表实现栈
package stack;
import java.util.LinkedList;
public class LinkedListStack<E> implements Stack<E> {
private stack.LinkedList<E> list = new stack.LinkedList<E>();
@Override
public int getSize() {
return list.getSize();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void push(E e) {
list.addFirst(e);
}
@Override
public E pop() {
return list.removeFirst();
}
@Override
public E peek() {
return list.getFirst();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("linkedList:");
sb.append(list);
sb.append("NULL");
return sb.toString();
}
public static void main(String[] args) {
LinkedListStack<Integer> listStack = new LinkedListStack<>();
for (int i = 0; i < 10; i++) {
listStack.push(i);
System.out.println(listStack);
}
listStack.pop();
System.out.println(listStack);
}
}
数组实现栈主要是需要动态的创建空间耗时,而链表需要不停地new新的Node耗时。
链表实现队列
public class LinkedListQueue<E> implements Queue<E> {
/*
* 链表结点类
*/
class Node<E> {
public E e;
public Node next;
/**
* 构造函数
*/
public Node() {
e = null;
next = null;
}
public Node(E e) {
this.e = e;
next = null;
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
/**
* 覆写toString方法
*/
@Override
public String toString() {
return e.toString();
}
}
private Node head, tail;
private int size;
public LinkedListQueue() {
head = tail = null;
size = 0;
}
@Override
public void enqueue(E e) {
//不需要头空结点
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("cannot dequeue from an empty queue");
}
Node curNode = head;
head = head.next;
curNode.next = null;
//如果只有一个结点 需要让尾结点为null
if(head == null) {
tail = null;
}
return null;
}
@Override
public E getFront() {
return null;
}
@Override
public int getSize() {
return size;
}
@Override
public Boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("linkedList:");
Node currentNode = head;
while(currentNode != null) {
sb.append(currentNode.e+"->");
currentNode = currentNode.next;
}
sb.append("NULL");
return sb.toString();
}
public static void main(String[] args) {
LinkedListQueue<Integer> link = new LinkedListQueue<Integer>();
for (int i = 0; i < 10; i++) {
link.enqueue(i);
System.out.println(link);
}
link.dequeue();
System.out.println(link);
}
}
总结:
通过学习单向链表,循环链表,双向链表,使我对链表有了深刻的认识,通过将链表应用到栈和队列中,学会了怎样使用链表。将数组和链表进行对比,发现如果要多次进行增删操作的时候,使用链表则节省很多的时间。通过分析,链表增加或删除元素,只需要操作一个结点,不需要大量移动元素,节省了更多的时间。但缺点是浪费了一些空间。如果需要进行多次遍历和搜索,使用数组比较方便,因为数组可以使用索引来查找,原理等同于C语言中的指针。