数据结构之链表(Linked list)
链表概述
单链表(Singly linked list)
双链表(Doubly linked list)
循环链表(Circular linked list)
链表操作
单链表操作
//E->anyType object
private static class Node<E> {
//数据项
private E data;
//指向next节点的引用
private Node<E> next; //构造
public Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
private Node<E> head = null;
插入
插入链表首部
public void addFirst(E item) {
head = new Node<E>(item, head);
}
插入目标节点的前面
public void insertBefore(E key, E toInsert) {
//若head为null,即链表为空,不存在目标节点 直接返回
if(head == null) return;
//若head指向的链表第一个节点 就是目标节点,即要把新建节点插入链表首部
if(head.data.equals(key)) {
addFirst(toInsert);
return;
}
Node<E> prev = null;
Node<E> curr = head;
//curr定义了 指向当前节点,prev指向当前节点的上个节点。一直顺序查找,若找到目标节点(数据为key),则curr指向目标节点,prev指向了目标节点的上个节点。
while(curr != null && !curr.data.equals(key)) {
prev = curr;
curr = curr.next;
}
//若curr不为空,即找到目标节点,在curr和prev之间插入了新节点(数据为toInsert)。若curr为null,即没找到。
if(curr != null) prev.next = new Node<E>(toInsert, curr);
}
插入某个节点后面
public void insertAfter(E key, E toInsert) {
Node<E> curr = head;
//查找目标节点,找到即curr指向目标节点
while(curr != null && !curr.data.equals(key)) {
curr = curr.next;
}
//curr不为null,即找到目标节点。在之后插入即可。
if(curr != null)
curr.next = new Node<E>(toInsert, curr.next);
}
插入链表尾部
public void addLast(E item) {
if(head == null) {
addFirst(item);
} else {
Node<E> tmp = head;
while(tmp.next != null) tmp = tmp.next;
tmp.next = new Node<E>(item, null);
}
}
删除
public void remove(E key) {
//head为null,即链表为空
if(head == null) throw new RuntimeException("linkedlist is null, cannot delete");
//链表第一个节点即目标节点,改变head指向下个节点就可以了。
if(head.data.equals(key)) {
head = head.next;
return;
}
Node<E> prev = null;
Node<E> curr = head;
//查找,若找到即curr指向目标节点,prev指向目标节点的上个节点
while(curr != null && !curr.data.equals(key)) {
prev = curr;
curr = curr.next;
}
//curr为null,即没找到
if(curr == null) throw new RuntimeException("cannot find your node, cannot delete");
//curr不为null,删除目标节点(即改变prev的next 指向curr的next即可)
prev.next = curr.next;
}
双链表操作
transient int size = 0; /**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;//指向首部(第一个节点) /**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;//指向尾部(最后一个节点) private static class Node<E> {
//数据项
E item;
//指向下个节点
Node<E> next;
//指向上个节点
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
插入到链表首部
/**
* Links e as first element.
*/
private void linkFirst(E e) {
//f指向first指向的节点。即f和first同时指向第一个节点。f在方法中标志 链表插入前的第一个节点
final Node<E> f = first;
//创建新的节点,节点的prev指向null,next指向f(即链表插入前第一个节点)
final Node<E> newNode = new Node<>(null, e, f);
//first指向新节点(链表首部已改变)
first = newNode;
if (f == null)
//若f为null,即插入前链表是空的,插入到新节点既是开始节点也是结束节点,所以last也指向了它
last = newNode;
else
//如果f不为空,则让f(插入前的第一个节点)的prev指向新节点就完成了。
f.prev = newNode;
size++;
modCount++;
}
插入到某个元素前面
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
//这里把succ称为目标节点。succ在这里是已经查找到并存在的,查找过程可参考单链表中相关代码。
//用pred指向 目标节点succ的上个节点。
final Node<E> pred = succ.prev;
//新建节点,prev指向pred 即新建节点的prev指向目标节点的上个节点,next指向目标节点
final Node<E> newNode = new Node<>(pred, e, succ);
//目标节点的prev指向 新建节点
succ.prev = newNode;
if (pred == null)
//若pred为null 是目标节点上个节点为null,即链表插入前只有一个节点,所以first会指向新建节点。
first = newNode;
else
//若pred不为null,目标节点的上个节点的next指向新节点
pred.next = newNode;
size++;
modCount++;
}
插入到链表尾部
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
删除
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If this list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* {@code i} such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns {@code true} if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return {@code true} if this list contained the specified element
*/
public boolean remove(Object o) {
//这里就是区分o是否为null,找到第一个指定element的节点,通过unlink删除。
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
} /**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev; if (prev == null) {
//prev为null 即x是首部(第一个节点),first指向next即可
first = next;
} else {
//prev不为null,x与prev之间关联即断开。prev的next指向next了,x的prev为null
prev.next = next;
x.prev = null;
} //同prev处理类似,断开x与next联系
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
} x.item = null;
size--;
modCount++;
return element;
}
循环链表
private Node<E> head = null;
private Node<E> tail = null; private class Node<E> {
E value;
Node<E> next;
public Node(E value) {
this.value = value;
}
} //往尾部(即tail节点之后)添加节点
public void addNode(E value) {
Node<E> newNode = new Node<>(value);
if (head == null) {
//head为null,即链表为空,head指向新建节点。
head = newNode;
} else {
tail.next = newNode;
}
//tail指向新节点,即新节点相当于链表的尾部
tail = newNode;
//tail.next即新节点next指向head,形成环
tail.next = head;
} //查找,链表中是否包含数据为searchValue的节点
public boolean containsNode(E searchValue) {
Node<E> currentNode = head;
if (head == null) {
return false;
} else {
//以head开始依次向后查找,直到碰到的仍是head停止。找到返回
do {
if (currentNode.value == searchValue) {
return true;
}
currentNode = currentNode.next;
} while (currentNode != head);
return false;
}
} //删除查找到的第一个数据为valueToDelete的节点
public void deleteNode(E valueToDelete) {
Node<E> currentNode = head;
if (head != null) {
if (currentNode.value == valueToDelete) {
//head节点且值为valueToDelete
head = head.next;
tail.next = head;
} else {
//以head开始依次向后查找,直到碰到的仍是head停止。找到删除
do {
Node<E> next = currentNode.next;
if (next.value == valueToDelete) {
currentNode.next = next.next;
break;
}
currentNode = currentNode.next;
} while (currentNode != head);
}
}
}
链表与数组比较
简单做了个表格
|
链表
|
数组
|
|
动态分配:需要时才分配内存
|
固定分配:大小固定,new时即分配所有内存
|
|
分散存储:内存中不连续存储
|
连续存储:内存中连续存储
|
|
总量限制:由于分散存储,受内存总量限制
|
使用限制:由于连续存储,若无合适连续控件即无法完成分配,且容易形成内存碎片
|
|
插入/删除方便:改变节点中指向next的或者prev的引用即可
|
插入/删除代价大:需创建新数组并移动元素
|
|
有内存浪费:节点中需额外存储next或prev的信息
|
无内存浪费:数组元素只存放数据
|
|
顺序访问:在某个节点沿某一方向只能逐一访问
|
随机访问:可以直接计算得到某一元素的地址
|
链表的一些操作复杂度
|
操作
|
链表
|
数组
|
|
访问(访问第N个元素)
|
O(n)
|
O(1)
|
|
插入到首部
|
O(1)
|
O(n)
|
|
插入到尾部
|
有尾部引用tail:O(1)
无尾部引用O(n)
|
O(n)
|
|
插入到中部
|
查找时间+O(1)~=O(n)
|
O(n) |
|
查找
|
O(n)
|
O(n) |
| 删除 |
类似插入,看删除位置或者链表的设计
|
O(n)
|
数据结构之链表(Linked list)的更多相关文章
- 算法与数据结构基础 - 链表(Linked List)
链表基础 链表(Linked List)相比数组(Array),物理存储上非连续.不支持O(1)时间按索引存取:但链表也有其优点,灵活的内存管理.允许在链表任意位置上插入和删除节点.单向链表结构一般如 ...
- 数据结构-单链表(Linked List)
#include <stdio.h> #include <stdlib.h> #define LIST_INIT_SIZE 10 #define LISTINCREMENT 1 ...
- 数据结构与算法 —— 链表linked list(01)
链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...
- Java数据结构之链表(Linked List)
1.链表(Linked List)介绍 链表是有序的列表,但是它在内存存储结构如下: 2.特点: 链表是以节点的方式来存储,是链式存储 每个节点包含 data 域, next 域:指向下一个节点. 链 ...
- Python与数据结构[0] -> 链表/LinkedList[0] -> 单链表与带表头单链表的 Python 实现
单链表 / Linked List 目录 单链表 带表头单链表 链表是一种基本的线性数据结构,在C语言中,这种数据结构通过指针实现,由于存储空间不要求连续性,因此插入和删除操作将变得十分快速.下面将利 ...
- 链表Linked List
链表Linked List 1. 链表 数组是一种顺序表,index与value之间是一种顺序映射,以O(1)O(1)的复杂度访问数据元素.但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后 ...
- 数据结构之链表(LinkedList)(一)
链表(Linked List)介绍 链表是有序的列表,但是它在内存中是存储如下 1)链表是以节点方式存储的,是链式存储 2)每个节点包含data域(value),next域,指向下一个节点 3)各个节 ...
- 学习javascript数据结构(二)——链表
前言 人生总是直向前行走,从不留下什么. 原文地址:学习javascript数据结构(二)--链表 博主博客地址:Damonare的个人博客 正文 链表简介 上一篇博客-学习javascript数据结 ...
- linux内核数据结构之链表
linux内核数据结构之链表 1.前言 最近写代码需用到链表结构,正好公共库有关于链表的.第一眼看时,觉得有点新鲜,和我之前见到的链表结构不一样,只有前驱和后继指针,而没有数据域.后来看代码注释发现该 ...
随机推荐
- PAT 1006 Sign In and Sign Out (25分) 字符串比较
题目 At the beginning of every day, the first person who signs in the computer room will unlock the do ...
- 机器学习算法及代码实现–K邻近算法
机器学习算法及代码实现–K邻近算法 1.K邻近算法 将标注好类别的训练样本映射到X(选取的特征数)维的坐标系之中,同样将测试样本映射到X维的坐标系之中,选取距离该测试样本欧氏距离(两点间距离公式)最近 ...
- Java基础之数据类型
一.数据类型 基本数据类型介绍 byte 1字节 char 2字节 short 2字节 int 4字节 long 8字节 float 4字节 double 8字节 以上有Java中八大基本类型的7种, ...
- python3.x 基础四:目录获取及目录规范
1.获取目录 import os,sys print('程序文件运行相对位置>>',os.path.abspath(__file__)) print('程序文件上级绝对目录>> ...
- Oracle分页查询语句的写法
分页查询是我们在使用数据库系统时经常要使用到的,下文对Oracle数据库系统中的分页查询语句作了详细的介绍,供您参考. AD:2013云计算架构师峰会精彩课程曝光 Oracle分页查询语句使我们最常用 ...
- Spring注入的对象到底是什么类型
开篇 之前,在用spring编码调试的时候,有时候发现被自动注入的对象是原始类的对象,有时候是代理类的对象,那什么时候注入的原始类对象呢,有什么时候注入的是代理类的对象呢?心里就留下了这个疑问.后来再 ...
- Keyboard Shortcuts Reference
Sublime Text 3快捷键 Ctrl + Shift + P 打开命令面板 Ctrl + P 搜索项目中的文件 Ctrl + G 跳到第几行 Ctrl + W 关闭当前打开文件 Ctrl + ...
- Shone.Math开源系列2 — 实数类型(含分数和无理数)的实现
Shone.Math开源系列2 实数类型(含分数和无理数)的实现 作者:Shone 声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp. 摘要: ...
- PLC可编程控制器的结构和工作原理
PLC的可编程控制器由的功能结构由cpu中央处理器,存储器和输入输出借口三部分组成 CPU Cpu的功能是完成plc所有的的控制和监视, Cpu中央处理去由控制器,寄存器,运算器.通过数据总线,地址总 ...
- 前端内网穿透,localtunnel你值得拥有!
一个前端在调试本地页面时,总会有些稀奇古怪的需求,比如产品立刻要看你的页面效果,而此时有没有上线环境折腾给他看,那此时通过内网穿透的方式,实时把你的项目生成一个在线链接丢给他,让他去找那一像素的bug ...