数据结构 -- 链表(LinkedList)
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
二、链表种类
1.1)单向链表:
element:用来存放元素
next:用来指向下一个节点元素
通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。
1.2)单向循环链表
element、next 跟前面一样
在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环
1.3)双向链表
element:存放元素
pre:用来指向前一个元素
next:指向后一个元素
双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。
1.4)双向循环链表
element、pre、next 跟前面的一样
第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。
时间复杂度
(1)添加操作:O(n)
① addLast(e): O(n) ②addFirst(e): O(1) ③add(index, e): O(n/2)=O(n)
(2)删除操作:O(n)
① removeLast(e): O(n) ②removeFirst(e): O(1) ③remove(index, e): O(n/2)=O(n)
(3)查找操作:O(n)
① get(index): O(n) ②contains(e): O(n)
(4)修改操作:O(n)
① set(index,e): O(n)
三、代码实现
单链表的实现
图解如下:
插入
删除
代码
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(E e){
this(e,null);
}
public Node(){
this(null,null);
} @Override
public String toString(){
return e.toString();
}
} private Node dummyHead; //虚拟头节点,位于第一位置的前一个位置
private int size;//链表元素个数 public LinkedList(){
dummyHead = new Node();
size = 0;
} public int getSize(){
return size;
}
//判断是否为空
public boolean isEmpty(){
return size == 0;
} public void add(int index, E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node prev = dummyHead;
for (int i=0; i<index; i++){
prev = prev.next;
}
prev.next = new Node(e,prev.next);
size ++;
}
//在链表 表头添加新的元素e
public void addFirst(E e){
add(0,e);
}
//在链表 尾部添加新的元素e
public void addList(E e){
add(size,e);
} //获得链表的第index位置的元素. 在链表中不是一个常用的操作,练习用
public E get(int index){
if(index < 0 || index >= size){
throw new IllegalArgumentException("index Illegal");
} Node cur = dummyHead.next;
for (int i =0; i<index; i++){
cur = cur.next;
}
return cur.e;
} public E getLast(){
return get(size - 1);
}
public E getFirst(){
return get(0);
} public void set(int index,E e){
if(index < 0 || index >= size){
throw new IllegalArgumentException("index Illegal");
} 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;
while (cur != null){
if (cur.e.equals(e))
return true;
cur = cur.next;
}
return false;
}
// 从链表中删除index(0-based)位置的元素, 返回删除的元素
// 在链表中不是一个常用的操作,练习用:)
public E remove(int index){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Remove failed. Index is illegal.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++){
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
//删除retNode节点,则它的下一个节点指引就不需要了,设置null,让GC干活
retNode.next = null;
size --;
return retNode.e;
} public E removeFirst(){
return remove(0);
} public E removeLast(){
return remove(size - 1);
} public void removeElement(E e){
Node prev = dummyHead;
while (prev != null){
if (prev.next.e.equals(e)){
break;
}
prev = prev.next;
} if (prev.next != null){
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size --;
}
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
Node cur = dummyHead.next;
while( cur != null){
stringBuilder.append(cur + "- >");
cur = cur.next;
}
stringBuilder.append("null");
return stringBuilder.toString();
}
}
测试代码
public class LinkedListTest{
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
for (int i=0; i< 10; i++){
linkedList.add(i,i*10);
System.out.println(linkedList);
}
linkedList.remove(1);
System.out.println(linkedList);
linkedList.removeElement(50);
System.out.println(linkedList);
}
}
//测试结果
0- >null
0- >10- >null
0- >10- >20- >null
0- >10- >20- >30- >null
0- >10- >20- >30- >40- >null
0- >10- >20- >30- >40- >50- >null
0- >10- >20- >30- >40- >50- >60- >null
0- >10- >20- >30- >40- >50- >60- >70- >null
0- >10- >20- >30- >40- >50- >60- >70- >80- >null
0- >10- >20- >30- >40- >50- >60- >70- >80- >90- >null
0- >20- >30- >40- >50- >60- >70- >80- >90- >null //删除第二个位置的10
0- >20- >30- >40- >60- >70- >80- >90- >null //删除指定元素50
链表实现队列
import com.wj.queue.Queue; //Queue连接:https://www.cnblogs.com/FondWang/p/11808221.html
public class LinkedListQueue<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 LinkedListQueue(){
head = null;
tail = null;
size = 0;
} @Override
public int getSize() {
return size;
} @Override
public boolean isEmpty() {
return size == 0;
} //入队
@Override
public void enqueue(Object o) {
if (tail == null){
tail = new Node((E) o);
head = tail;
}else {
tail.next = new Node((E) o);
tail = tail.next;
}
size++;
}
//出队
@Override
public E dequeue() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
Node retNode = head;
head = head.next;
retNode.next = null;
if (head == null){
tail = null;
}
size--;
return retNode.e;
}
//返回队首元素
@Override
public E getFront() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return head.e;
} @Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Queue: front ");
Node cur = head;
while (cur != null){
stringBuilder.append(cur + "- >");
cur = cur.next;
}
stringBuilder.append("NULL tail");
return stringBuilder.toString();
}
}
测试类
public class LinkedListQueueTest{
public static void main(String[] args) {
LinkedListQueue linkedListQueue = new LinkedListQueue();
for (int i=0; i<10; i++){
linkedListQueue.enqueue(i); //入队
System.out.println(linkedListQueue);
if (i % 3 ==0){
linkedListQueue.dequeue(); //符合条件,出队
}
}
}
}
//测试结果
Queue: front 0- >NULL tail
Queue: front 1- >NULL tail
Queue: front 1- >2- >NULL tail
Queue: front 1- >2- >3- >NULL tail
Queue: front 2- >3- >4- >NULL tail
Queue: front 2- >3- >4- >5- >NULL tail
Queue: front 2- >3- >4- >5- >6- >NULL tail
Queue: front 3- >4- >5- >6- >7- >NULL tail
Queue: front 3- >4- >5- >6- >7- >8- >NULL tail
Queue: front 3- >4- >5- >6- >7- >8- >9- >NULL tail
链表实现栈
import com.wj.stack.Stack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
public class LinkedListStack<E> implements Stack<E> { private LinkedList<E> linkedList; public LinkedListStack(){
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
} @Override
public boolean isEmpty() {
return linkedList.isEmpty();
} //压入
public void push(Object o) {
linkedList.addFirst((E) o);
}
//移除
@Override
public E pop() {
return linkedList.removeFirst();
}
//返回栈首元素
@Override
public E peek() {
return linkedList.getFirst();
} public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Stack: top ");
stringBuilder.append(linkedList);
return stringBuilder.toString();
}
}
测试类
public class LinkedListStackTest{
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack();
for (int i =0; i< 10; i++){
stack.push(i); //压入
System.out.println(stack);
} System.out.println("=============");
stack.pop(); //移除
System.out.println(stack);
}
}
//测试结果
Stack: top 0- >null
Stack: top 1- >0- >null
Stack: top 2- >1- >0- >null
Stack: top 3- >2- >1- >0- >null
Stack: top 4- >3- >2- >1- >0- >null
Stack: top 5- >4- >3- >2- >1- >0- >null
Stack: top 6- >5- >4- >3- >2- >1- >0- >null
Stack: top 7- >6- >5- >4- >3- >2- >1- >0- >null
Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null
Stack: top 9- >8- >7- >6- >5- >4- >3- >2- >1- >0- >null //将第一个移除
=============
Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null
四、效率对比
栈的对比
import com.wj.stack.ArrayStack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
import com.wj.stack.Stack;
import java.util.Random;
public class StackEfficiency {
private static double testStack(Stack<Integer> stack, int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for (int i=0; i < opCount; i ++){
stack.push(random.nextInt(Integer.MAX_VALUE));
}
for (int i=0; i < opCount; i ++){
stack.pop();
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 100000;
ArrayStack<Integer> arrayStack = new ArrayStack();
double time1 = testStack(arrayStack,opCount);
System.out.println("ArrayStack - time1: " + time1); LinkedListStack<Integer> linkedListStack = new LinkedListStack();
double time2 = testStack(linkedListStack,opCount);
System.out.println("LinkedListStack - time2: " + time2); }
}
//测试结果,添加和删除
ArrayStack - time1: 7.071363002
LinkedListStack - time2: 0.006395025
//说明数组的增删 效率要低于 链表。
//对于增删数组的时间复杂度是O(n),而链表是O(1)
队列的对比
import com.wj.queue.ArrayQueue;//详情:https://www.cnblogs.com/FondWang/p/11808221.html
import com.wj.queue.LoopQueue;
import com.wj.queue.Queue;
import java.util.Random;
public class MainQueue {
private static double testStack(Queue<Integer> queue, int opCount){
long startTime = System.nanoTime();
Random random = new Random();
for (int i=0; i < opCount; i ++){
queue.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i=0; i < opCount; i ++){
queue.dequeue();
} long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int opCount = 1000000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue();
double time1 = testStack(arrayQueue,opCount);
System.out.println("ArrayStack - time1: " + time1); LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue();
double time2 = testStack(linkedListQueue,opCount);
System.out.println("LinkedListStack - time2: " + time2); LoopQueue<Integer> loopQueue = new LoopQueue();
double time3 = testStack(loopQueue,opCount);
System.out.println("LoopQueue - time3: " + time3);
}
}
//测试结果
ArrayQueue - time1: 368.014648575
LinkedListQueue - time2: 0.395344446
LoopQueue - time3: 0.045756219
//对于增删操作,数组(O(n))的时间复杂度高于链表队列和循环队列,所以数组最慢。
//链表和队列。队列增删只在队首和队尾,时间复杂度低于链表,所以循环队列要高于链表队列。
数据结构 -- 链表(LinkedList)的更多相关文章
- 模板 - 数据结构 - 链表/LinkedList
一个只供删除的双向链表,为了简单不再引入head节点,而且也不进行next的套娃操作.空间使用略微多了一些,但是无伤大雅. struct LinkedList { static const int M ...
- 数据结构之链表(LinkedList)(三)
数据结构之链表(LinkedList)(二) 环形链表 顾名思义 环形列表是一个首尾相连的环形链表 示意图 循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活. 看一 ...
- 数据结构之链表(LinkedList)(二)
数据结构之链表(LinkedList)(一) 双链表 上一篇讲述了单链表是通过next 指向下一个节点,那么双链表就是指不止可以顺序指向下一个节点,还可以通过prior域逆序指向上一个节点 示意图: ...
- 《数据结构与算法分析——C语言描述》ADT实现(NO.00) : 链表(Linked-List)
开始学习数据结构,使用的教材是机械工业出版社的<数据结构与算法分析——C语言描述>,计划将书中的ADT用C语言实现一遍,记录于此.下面是第一个最简单的结构——链表. 链表(Linked-L ...
- Python—数据结构——链表
数据结构——链表 一.简介 链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构.由一系列节点组成的元素集合.每个节点包含两部分,数据域item和指向下一个节 ...
- (js描述的)数据结构[链表](4)
(js描述的)数据结构 [链表](4) 一.基本结构 二.想比于数组,链表的一些优点 1.内存空间不是必须连续的,可以充分利用计算机的内存,事项灵活的内存动态管理. 2.链表不必再创建时就确定大小,并 ...
- 数据结构和算法(Golang实现)(12)常见数据结构-链表
链表 讲数据结构就离不开讲链表.因为数据结构是用来组织数据的,如何将一个数据关联到另外一个数据呢?链表可以将数据和数据之间关联起来,从一个数据指向另外一个数据. 一.链表 定义: 链表由一个个数据节点 ...
- Redis数据结构—链表与字典的结构
目录 Redis数据结构-链表与字典的结构 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 Redis字典的使用 Re ...
- Redis数据结构—链表与字典
目录 Redis数据结构-链表与字典 链表 Redis链表节点的结构 Redis链表的表示 Redis链表用在哪 字典 Redis字典结构总览 Redis字典结构分解 哈希算法 解决键冲突 rehas ...
- 链表LinkedList、堆栈Stack、集合Set
链表LinkedList LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但它在 List 中间执行插入和删除操作时比 ArrayList 更高效.然而,它在随机访问 ...
随机推荐
- mysql5.7版本以上下载安装
1.mysql官网下载地址:https://downloads.mysql.com/archives/community/ 2.下载完成后解压,解压后如图: 3.放置位置,把解压好的文件夹放在自己喜欢 ...
- 在Spring中读取properties文件
1.配置文件(*.properties)往往通过以下方式注册在Spring IOC中. <!-- JDBC配置 --> <context:property-placeholder l ...
- centos7使用yum提示有事物未完成的解决办法:
错误提示: There are unfinished transactions remaining. You might consider running yum-complete-transacti ...
- GO 跟C++/C差异
规范的语法(不需要符号表来解析) 垃圾回收(独有) 无头文件 明确的依赖 无循环依赖 常量只能是数字 int和int32是两种类型 字母大小写设置可见性(letter case sets visibi ...
- pc电源cpu插座和显卡插座
cpu插座是8口的,一般4+4 显卡插座是6口的,也有8口的用6+2 6+2的中2有一个小边,组合成8口也不能插入cpu插座.
- 完全基于卷积神经网络的seq2seq
本文参考文献: Gehring J, Auli M, Grangier D, et al. Convolutional Sequence to Sequence Learning[J]. arXiv ...
- GDPR全文翻译(一)
General Data Protection Regulation <一般数据保护法案>全文翻译(一) 编者按 2016年4月14日,欧洲议会投票通过了商讨四年的<一般数据保护法案 ...
- angular 中的[ngClass]、[ngStyle]
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> </ ...
- 003-结构型-01-适配器模式(Adapter)
一.概述 将一个类的接口转换成客户期望的另一个接口.适配器模式让那些接口不兼容的类可以一起工作. 1.1.适用场景 已经存在的类,它的方法和需求不匹配时(方法结果相同或相似) 不是软件设计阶段考虑的设 ...
- 丢失libiconv-2.dll解决办法以及无法定位输入点libiconv-2.dll到动态链接库
摘自https://blog.csdn.net/mengxiangjia_linxi/article/details/78147348 丢失libiconv-2.dll解决办法以及无法定位输入点lib ...