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)允许每个节点有更 ...
随机推荐
- PostgreSQ 连接问题 FATAL: no pg_hba.conf entry for host
PostgreSQ数据库为了安全,它不会监听除本地以外的所有连接请求,当用户通过JDBC访问是,会报一些如下的异常: org.postgresql.util.PSQLException: FATAL: ...
- node安装升级npm
安装npm npm上有很多优秀的nodejs包,来解决常见的一些问题,比如用node-mysql,就可以方便通过nodejs链接到mysql,进行数据库的操作 在开发过程往往会需要用到其他的包,使用n ...
- C#:目录
ylbtech-C#:目录 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 6.返回顶部 作者:ylbtech出处:http://ylbtech.cn ...
- docker安装-卸载
docker官网正确安装-卸载 一.查看系统内核 uname -r 3.10.0-229.el7.x86_64 二.Install Docker 1.Install with yum sudo yum ...
- 035--MySQL基本操作
一.数据库的定义及相关名词解释 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库, 每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据. 我们也可以 ...
- 数据库MySQL技术-基础知识
数据库技术: SQL,关系数据库标准 注意: 环境编码: cmd客户端是固定的gbk编码 而php网页中,是该网页文件的编码(现在主流都是utf8). mysql> set names gb ...
- hadoop中的序列化
此文已由作者肖凡授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 最近在学习hadoop,发现hadoop的序列化过程和jdk的序列化有很大的区别,下面就来说说这两者的区别都有 ...
- 20170406-ms
Interval 间隔 revoke v撤销 alert adj 警觉的 n警报
- hdoj1394
题意还告诉我们是0-n-1之间的数,那么我们每次把一个数放到后面去,求一下比他大的,还有比他小的: 比如: 1 3 6 9 0 8 5 7 4 2 逆序数num:22 3 6 9 0 8 5 7 4 ...
- IT兄弟连 Java Web教程 Servlet的生命周期
JavaWeb应用的生命周期由Servlet容器来控制,而Servlet作为JavaWeb应用的最核心的组件,其生命周期也由Servlet容器来控制.Servlet的生命周期可以分为3个阶段:初始化阶 ...