Java数据结构和算法(四)--链表
日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要
插入索引后面的所以元素全部后移一位。
而本文会详细讲解链表,可以解决数组的部分问题,相比数组的大小不可更改,链表更加灵活,在学习LinkedList源码对链表有了一个大致的
了解。
ArrayList和LinkedList源码请参考:
Java集合(四)--基于JDK1.8的ArrayList源码解读
本文我们会学习:单链表、双端链表、有序链表、双向链表和有迭代器的链表,并且会讲解一下抽象数据类型(ADT)的思想,如何用 ADT 描述
栈和队列,如何用链表代替数组来实现栈和队列。
链节点:
在链表中,每个元素都被包含在链节点Link中。一个链节点是某个类的对象,这个类可以叫做Link。每个Link对象都包含对下一个Link引用的
字段(通常叫next)。但是链表本身有个字段指向对第一个Link的引用。

代码示例:
public class Link {
private Object data;
private Link next;
}
单链表:
单链表的机构比较简单,每个Node包含data和next(指向下个Node),最后一个Node的next指向null
图例:

代码实现:
public class SingleLinkList<E> {
private int size; //链表长度大小
private Node head; //头结点
public SingleLinkList() {
size = 0;
head = null;
}
//添加元素到head
public void addFirst(E data) {
Node newNode = new Node(data);
if (size == 0) {
head = newNode;
} else {
newNode.next = head;
head = newNode;
}
size++;
}
//删除头结点
public E deleteFirst() {
final E data = (E)head.data;
head = head.next;
size--;
return data;
}
//查询某个元素是否存在
public E find(E object) {
Node current = head;
int tempSize = size;
while (tempSize > 0) {
if (object == current.data) {
return (E)current.data;
} else {
current = current.next;
}
tempSize--;
}
return null;
}
//删除链表中某个元素
public boolean delete(E object) {
if (null != object) {
Node current = head;
Node previous = head;
while (!object.equals(current.data)) {
if (current.next == null) {
return false;
} else {
previous = current;
current = current.next;
}
}
if (current == head) {
head = current.next;
} else {
previous.next = current.next;
}
size--;
}
return true;
}
//遍历打印链表
public void displayList() {
Node current = head;
int tempSize = size;
if (tempSize == 0) {
System.out.print("[]");
} else {
System.out.print("[");
while (current != null) {
if (current.next == null) {
System.out.print(current.data);;
} else {
System.out.print(current.data + "-->");;
}
current = current.next;
}
System.out.println("]");
}
}
public boolean isEmpty() {
return size == 0;
}
private static class Node<E>{
E data;
Node<E> next;
Node(E data) {
this.data = data;
}
}
}
public static void main(String[] args) {
SingleLinkList<Integer> singleList = new SingleLinkList<Integer>();
singleList.addFirst(22); //添加节点
singleList.addFirst(44);
singleList.addFirst(66);
singleList.addFirst(88);
singleList.displayList(); //打印链表结构
singleList.delete(44); //删除某个节点
singleList.displayList();
System.out.println(singleList.find(66)); //查询某个节点
}
打印结果:
[88-->66-->44-->22]
[88-->66-->22]
66
双端链表:
双端链表和单向链表很相似,但是增加了一个新特性:就是对最后一个节点的引用,最后一个节点定义为tail
PS:双端链表不是双向链表,只能单向遍历,只是可以在双端添加/删除数据
图例:

代码实现:
public class DoubleLinkList<E> {
private int size; //链表长度大小
private Node head; //头结点
private Node tail; //尾结点
public DoubleLinkList() {
size = 0;
head = null;
tail = null;
}
//添加元素到head
public void addFirst(E data) {
Node newNode = new Node(data);
if (size == 0) {
head = newNode;
tail = newNode;
} else {
newNode.next = head;
head = newNode;
}
size++;
}
//添加元素到tail
public void addLast(E data) {
Node newNode = new Node(data);
if (size == 0) {
head = newNode;
tail = newNode;
} else {
tail.next = newNode;
tail = newNode;
}
size++;
}
//删除头结点
public E deleteFirst() {
if (isEmpty()) {
return null;
}
final E data = (E)head.data;
if (head.next == null) {
tail = null;
}
head = head.next;
size--;
return data;
}
//删除尾结点
public E deleteLast() {
if (isEmpty()) {
return null;
}
final E data = (E)tail.data;
if (head.next == null) {
head = null;
}
int tempSize = size;
Node current = head;
Node previous = head;
while (tempSize > 0) {
if (current.next == null) {
previous.next = null;
break;
}
previous = current;
current = current.next;
tempSize--;
}
tail = previous;
size--;
return data;
}
//查询某个元素是否存在
public E find(E object) {
Node current = head;
int tempSize = size;
while (tempSize > 0) {
if (object == current.data) {
return (E)current.data;
} else {
current = current.next;
}
tempSize--;
}
return null;
}
//删除链表中某个元素
public boolean delete(E object) {
if (null != object) {
Node current = head;
Node previous = head;
while (!object.equals(current.data)) {
if (current.next == null) {
return false;
} else {
previous = current;
current = current.next;
}
}
if (current == head) {
head = current.next;
}else {
previous.next = current.next;
}
size--;
}
return true;
}
//遍历打印链表
public void displayList() {
Node current = head;
int tempSize = size;
if (tempSize == 0) {
System.out.print("[]");
} else {
System.out.print("[");
while (current != null) {
if (current == tail) {
System.out.print(current.data);
break;
} else {
System.out.print(current.data + "-->");
}
current = current.next;
}
System.out.println("]");
}
}
public boolean isEmpty() {
return size == 0;
}
private static class Node<E>{
E data;
Node<E> next;
Node(E data) {
this.data = data;
}
}
}
public static void main(String[] args) {
DoubleLinkList<Integer> doubleLinkList = new DoubleLinkList<Integer>();
doubleLinkList.addFirst(22); //添加节点
doubleLinkList.addFirst(44);
doubleLinkList.addLast(66);
doubleLinkList.addLast(88);
doubleLinkList.addFirst(101);
doubleLinkList.displayList(); //打印链表结构
doubleLinkList.delete(44); //删除某个节点
doubleLinkList.displayList();
doubleLinkList.deleteLast(); //删除尾节点
doubleLinkList.displayList();
doubleLinkList.deleteFirst(); //删除头结点
doubleLinkList.displayList();
System.out.println(doubleLinkList.find(66)); //查询某个节点
}
输出结果:
[101-->44-->22-->66-->88]
[101-->22-->66-->88]
[101-->22-->66]
[22-->66]
66
链表的效率:
表头插入和删除的速度很快,时间复杂度O(1)
平均下来,定点插入、删除、查询都需要搜索链表中一半的节点,需要O(N)次比较,相比而言,数组执行这些操作也需要O(N)次比较,但是
链表不需要移动数据,只需要改变前后引用,而数组只能整体复制,效率会好很多
链表的另一个优点体现在内存使用上,需要多少内存就使用多少内存,而数组一开始的内存空间都是确定的
有序链表:
对于某些应用来说,在链表中保持数据的有序很很有用的。有序链表中,数据都是按照关键值有序排列的。
在大多数使用有序数组的场景也可以使用有序链表,在插入速度方面有很大优势
有序链表和一般单向链表只是添加方法有区别,其余方法都是相同的
代码示例:
public class SortedLinkList {
private int size; //链表长度大小
private Node head; //头结点
public SortedLinkList() {
size = 0;
head = null;
}
//添加元素
public void add(int data) {
Node newNode = new Node(data);
Node previoue = null;
Node current = head;
while (current != null && data > current.data) {
previoue = current;
current = current.next;
}
if (previoue == null) {
head = newNode;
head.next = current;
} else {
previoue.next = newNode;
newNode.next = current;
}
size++;
}
//删除头结点
public int deleteFirst() {
final int data = head.data;
head = head.next;
size--;
return data;
}
//查询某个元素是否存在
public int find(int object) {
Node current = head;
int tempSize = size;
while (tempSize > 0) {
if (object == current.data) {
return current.data;
} else {
current = current.next;
}
tempSize--;
}
return -1;
}
//删除链表中某个元素
public boolean delete(int object) {
Node current = head;
Node previous = head;
while (object != current.data) {
if (current.next == null) {
return false;
} else {
previous = current;
current = current.next;
}
}
if (current == head) {
head = current.next;
} else {
previous.next = current.next;
}
size--;
return true;
}
//遍历打印链表
public void displayList() {
Node current = head;
int tempSize = size;
if (tempSize == 0) {
System.out.print("[]");
} else {
System.out.print("[");
while (current != null) {
if (current.next == null) {
System.out.print(current.data);;
} else {
System.out.print(current.data + "-->");;
}
current = current.next;
}
System.out.println("]");
}
}
public boolean isEmpty() {
return size == 0;
}
private static class Node{
int data;
Node next;
Node(int data) {
this.data = data;
}
}
}
public static void main(String[] args) {
SortedLinkList sortedLinkList = new SortedLinkList();
sortedLinkList.add(5); //添加节点
sortedLinkList.add(1);
sortedLinkList.add(8);
sortedLinkList.add(2);
sortedLinkList.add(101);
sortedLinkList.displayList(); //打印链表结构
sortedLinkList.delete(8); //删除某个节点
sortedLinkList.displayList();
sortedLinkList.deleteFirst(); //删除头结点
sortedLinkList.displayList();
System.out.println(sortedLinkList.find(101)); //查询某个节点
}
输出结果:
[1-->2-->5-->8-->101]
[1-->2-->5-->101]
[2-->5-->101]
101
双向链表:
双向链表就是为了解决单向链表只能单向遍历而效率慢的问题,因为只能current=current.next进行遍历,只能获得下一个节点,而不能
获取上一个节点
在学习LinkedList源码的时候,我们已经详细了解过双向链表了,Java集合(五)--LinkedList源码解读
图例:

代码示例:
public class DoubleLinkedList<E> {
private int size; //链表长度大小
private Node head; //头结点
private Node tail; //尾结点
public DoubleLinkedList() {
size = 0;
head = null;
tail = null;
}
//添加元素到head
public void addFirst(E data) {
Node newNode = new Node(data);
if (size == 0) {
tail = newNode;
} else {
head.previous = newNode;
newNode.next = head;
}
head = newNode;
size++;
}
//添加元素到tail
public void addLast(E data) {
Node newNode = new Node(data);
if (size == 0) {
head = newNode;
} else {
tail.next = newNode;
newNode.previous = tail;
}
tail = newNode;
size++;
}
//删除头结点
public E deleteFirst() {
if (isEmpty()) {
return null;
}
final E data = (E)head.data;
if (head.next == null) {
tail = null;
}
head = head.next;
head.previous = null;
size--;
return data;
}
//删除尾结点
public E deleteLast() {
if (isEmpty()) {
return null;
}
final E data = (E)tail.data;
if (head.next == null) {
head = null;
}
tail = tail.previous;
tail.next = null;
size--;
return data;
}
//查询某个元素是否存在
/*public E find(E object) {
if (object == null) {
for (Node<E> x = head; x != null; x = x.next) {
if (x.data == null) {
return x.data;
}
}
} else {
for (Node<E> x = head; x != null; x = x.next) {
if (object.equals(x.data)) {
return x.data;
}
}
}
return null;
}*/
//查询某个索引下标的数据
public E find(int index) {
return (E)node(index).data;
}
//删除链表中某个元素
public boolean delete(E object) {
if (object == null) {
for (Node<E> x = head; x != null; x = x.next) {
if (x.data == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = head; x != null; x = x.next) {
if (object.equals(x.data)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
final E element = x.data;
final Node<E> next = x.next;
final Node<E> prev = x.previous;
if (prev == null) {
head = next;
} else {
prev.next = next;
x.previous = null;
}
if (next == null) {
tail = prev;
} else {
next.previous = prev;
x.next = null;
}
x.data = null;
size--;
return element;
}
Node node(int index) {
if (index < (size >> 1)) {
Node x = head;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = tail;
for (int i = size - 1; i > index; i--)
x = x.previous;
return x;
}
}
//遍历打印链表
public void displayList() {
Node current = head;
int tempSize = size;
if (tempSize == 0) {
System.out.print("[]");
} else {
System.out.print("[");
while (current != null) {
if (current == tail) {
System.out.print(current.data);
break;
} else {
System.out.print(current.data + "-->");
}
current = current.next;
}
System.out.println("]");
}
}
public boolean isEmpty() {
return size == 0;
}
private static class Node<E>{
E data;
Node<E> previous;
Node<E> next;
Node(E data) {
this.data = data;
}
}
}
public static void main(String[] args) {
DoubleLinkedList<Integer> doubleLinkList = new DoubleLinkedList<Integer>();
doubleLinkList.addFirst(22); //添加节点
doubleLinkList.addFirst(44);
doubleLinkList.addLast(66);
doubleLinkList.addLast(88);
doubleLinkList.addFirst(101);
doubleLinkList.displayList(); //打印链表结构
doubleLinkList.delete(44); //删除某个节点
doubleLinkList.displayList();
doubleLinkList.deleteLast(); //删除尾节点
doubleLinkList.displayList();
doubleLinkList.deleteFirst(); //删除头结点
doubleLinkList.displayList();
System.out.println(doubleLinkList.find(66)); //查询某个节点
System.out.println(doubleLinkList.find(33)); //查询某个节点
}
输出结果:
[101-->44-->22-->66-->88]
[101-->22-->66-->88]
[101-->22-->66]
[22-->66]
66
拓展1、用链表实现:
public class MyStackByLinkedList<E> {
private SingleLinkList linkList;
public MyStackByLinkedList() {
linkList = new SingleLinkList();
}
public void push(E e) {
linkList.addFirst(e);
}
public E pop(){
E e = (E)linkList.deleteFirst();
return e;
}
}
拓展2:用双端链表实现队列
public class MyQueueByLinkedList<E> {
private DoubleLinkedList linkedList;
public MyQueueByLinkedList() {
linkedList = new DoubleLinkedList();
}
public void add(E e) {
linkedList.addFirst(e);
}
//移除数据
public E remove(){
return (E)linkedList.deleteFirst();
}
}
内容参考:<Java数据结构和算法>
Java数据结构和算法(四)--链表的更多相关文章
- Java数据结构和算法之链表
三.链表 链结点 在链表中,每个数据项都被包含在‘点“中,一个点是某个类的对象,这个类可认叫做LINK.因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点.每个LINK对象中 ...
- Java数据结构和算法(四)——栈
stack,中文翻译为堆栈,事实上指的是栈,heap,堆. 这里讲的是数据结构的栈,不是内存分配里面的堆和栈. 栈是先进后出的数据的结构,好比你碟子一个一个堆起来.最后放的那个是堆在最上面的. 队列就 ...
- Java数据结构和算法(六)--二叉树
什么是树? 上面图例就是一个树,用圆代表节点,连接圆的直线代表边.树的顶端总有一个节点,通过它连接第二层的节点,然后第二层连向更下一层的节点,以此递推 ,所以树的顶端小,底部大.和现实中的树是相反的, ...
- Java数据结构和算法(五)——队列
队列.queue,就是现实生活中的排队. 1.简单队列: public class Queqe { private int array[]; private int front; private in ...
- Java数据结构和算法(一)线性结构之单链表
Java数据结构和算法(一)线性结构之单链表 prev current next -------------- -------------- -------------- | value | next ...
- Java数据结构和算法(四)赫夫曼树
Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...
- Java数据结构和算法(十四)——堆
在Java数据结构和算法(五)——队列中我们介绍了优先级队列,优先级队列是一种抽象数据类型(ADT),它提供了删除最大(或最小)关键字值的数据项的方法,插入数据项的方法,优先级队列可以用有序数组来实现 ...
- Java数据结构和算法 - 二叉树
前言 数据结构可划分为线性结构.树型结构和图型结构三大类.前面几篇讨论了数组.栈和队列.链表都是线性结构.树型结构中每个结点只允许有一个直接前驱结点,但允许有一个以上直接后驱结点.树型结构有树和二叉树 ...
- Java数据结构和算法 - 什么是2-3-4树
Q1: 什么是2-3-4树? A1: 在介绍2-3-4树之前,我们先说明二叉树和多叉树的概念. 二叉树:每个节点有一个数据项,最多有两个子节点. 多叉树:(multiway tree)允许每个节点有更 ...
随机推荐
- this关键字使用
原文地址:https://www.cnblogs.com/alsf/p/5515996.html 一,表示类中属性 1,没有使用this的情况 class Person{ // 定义Person类 p ...
- 从1到N的整数中1出现的次数
/* * 1-n整数中1出现的次数.cpp * * Created on: 2018年5月1日 * Author: soyo */ #include<iostream> using nam ...
- 通过链接将JSP页面中一变量传到另一JSP界面中
A.jsp 发送 <a herf="B.jsp?name=<%=name%>">传递到B页面</a> B.jsp 接收 <%String ...
- Ubuntu16.04安装Python3.6 和pip
root账户,不是root账户,命令前加sudo 安装: 1.add-apt-repository ppa:jonathonf/python-3.6 2.apt-get update 3.apt-ge ...
- RxJava入门之路(一)
RxJava接触过蛮长时间了,但是让我说个所以然来还是说不出来,归根结底还是还是理解不够深刻,趁着年底这个时候争取写个系列出来给自己的学习做个记录 注意区分RxJava1.0和2.0的区别,以下默认是 ...
- vector中插入pair
我们知道map和multimap的作用,这两种数据类型在存储数据时,会根据pair<>的first成员进行排序,不同的时前者将不会插入对first成员重复的结构,后者可以.那如果我们只想存 ...
- C++经典面试题全集 50~100道 都附带有参考答案
51. 引用与指针有什么区别? 答 .1) 引用必须被初始化,指针不必. 2) 引用初始化以后不能被改变,指针可以改变所指的对象. 3) 不存在指向空值的引用,但是存在指向空值的指针. 52. 描 ...
- CentOS 7.6 最小安装 ifconfig command not found 及 yum 不可用的解决办法
问题描述 下载的是 CentOS 7.6 Everything 版本,在 VMware 12 上安装时软件选择“最小安装”后,输入 ifconfig 命令提示: bash ifconfig comma ...
- 如何在VS2010的VC++ 基于对话框的MFC程序中添加菜单
方法1:亲测 成功 转载自https://social.msdn.microsoft.com/Forums/vstudio/zh-CN/48338f6b-e5d9-4c0c-8b17-05ca3ef ...
- Bank Hacking CodeForces - 796C
题目 题意: 一条笨狗要去黑银行,银行有n个,它们之间用n-1条边连接.可以选择任意一个银行开始黑,但是后面每一次黑的银行都要求与已经黑过的银行直接相连.每个银行初始有一个防御值,每一个银行被黑后,与 ...