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)允许每个节点有更 ...
随机推荐
- JAVA Synchronized (二)
一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...
- css3 appearance 改变元素外观
h5 input标签的默认样式去除: 去掉date类型<input>框的叉叉: ::-webkit-clear-button { -webkit-appearance: none; } 去 ...
- nodejs supvisor模块
在测试nodejs程序的时候,每次都需要在控制台编译,非常的麻烦.supervisor是一款无需重复手动编译,自动后台监听文件变化来自动编译,并且不需要在项目内require,使用非常的方便. 使用方 ...
- 用C++调用C的库函数(转载)
转自:http://linhs.blog.51cto.com/370259/140927 C++调用C的库函数时,如果头文件定义得不恰当,可能会出现明明某函数在obj文件中存在,但是却发生链接失败的情 ...
- Eclipse中快速重写(Override)基类方法的技巧(转载)
转自:http://blog.csdn.net/guolin_blog/article/details/11952435 在Android开发过程中会引用大量的标准库,还要通过Override基类函数 ...
- DB Link 去除域名
1.查看global_name的设置 SQL> show parameters global_name; NAME TYPE ...
- E20180410-hm
preface n. 序言,引语; 开端,前奏; [宗] (弥撒的) 序诵,序祷; vi. 作序; 作为…的序言,作为…的开端; 给…作序; 开始,导致; continue vi. 持 ...
- hdoj1540 【线段树的表示】
大牛blog 这题的题解写给自己看-- 总结(瞎扯一点): 之前只会思考,len,sum,然后GG,如果只是sum和len的去用的话,就是在mid的时候会GG.然后这次也是参考大牛的写法,其实还是蛮简 ...
- Java class不分32位和64位
1.32位JDK编译的java class在32位系统和64位系统下都可以运行,64位系统兼容32位程序,可以理解.2.无论是Linux还是Windows平台下的JDK编译的java class在Li ...
- LeetCode.896-单调数组(Monotonic Array)
这是悦乐书的第345次更新,第369篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第210题(顺位题号是896).如果数组单调递增或单调递减,则数组是单调的.如果对于所有 ...