目录
1.定义
2.结构
2.1.类似火车
2.2.分类
2.2.1.按方向分
2.2.2.按是否带头节点分
2.2.3.按是否循环分
3.重点掌握
3.1.无头单向非循环链表
3.1.1.在链表的头部添加节点
3.1.2.在链表的任意位置index处添加节点
3.1.3.在链表的尾部添加节点
3.1.4.查询链表的任意位置index处的节点值
3.1.5.判断链表中是否包含值为data的节点
3.1.6.修改链表中任意位置index处的节点值,返回修改前的节点值
3.1.7.删除链表的头节点
3.1.8.删除链表中任意位置index处节点
3.1.9.删除链表中第一次出现某个值的节点
3.1.10.删除链表中值为data的所有节点
3.1.11.判断链表的index是否合法
3.1.12.toString()方法
3.2.带头单向非循环链表
3.2.1.在链表任意位置index处插入节点
3.2.2.在链表头部添加节点
3.2.3.在链表尾部添加节点
3.2.4.在链表任意位置index处删除节点
3.2.5.删除链表头部节点
3.2.6.删除链表尾部节点
3.2.7.toString()方法
3.3.无头双向链表
3.3.1.在链表的头部添加节点
3.3.2.在链表的尾部添加节点
3.3.3.在链表的任意位置index处添加节点
3.3.4.查询链表的任意位置index处的节点值
3.3.5.修改链表中任意位置index处的节点值
3.3.6.传入一个双向链表节点,将该节点从双向链表中删除
3.3.7.删除链表中第一次出现某个值的节点
3.3.8.删除链表中值为val的所有节点
3.3.9.判断链表的index是否合法
3.3.10.根据index与size的大小关系快速定位指定index位置的节点
3.3.11.toString()方法
1.定义
链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的引用连接次序来实现的。
2.结构
2.1.类似火车
(在Java中一切皆对象)分为车厢类和火车类。
特点:
车厢之间为逻辑先后关系(车厢1在车厢2之前),物理上不连续(中间有钩子相连)。
火车可以根据人员多少来方便地增加/减少车厢。
2.2.分类
2.2.1.按方向分
a.单向链表:只能从头到尾,不能从后向前。
b.双向链表:既能从头到尾,也能从后向前。
2.2.2.按是否带头节点分
a.不带头节点的单链表:
b.带头节点的单链表:
2.2.3.按是否循环分
a.非循环单链表:
b.循环单链表:
3.重点掌握
3.1.无头单向非循环链表
a.结构简单,一般不会单独用来存储数据,更多是作为其他数据结构的子结构,如哈希桶(盛放不同key链表的容器),图的邻接表等。
b.笔试,面试中很多。
3.1.1.在链表的头部添加节点
3.1.2.在链表的任意位置index处添加节点
3.1.3.在链表的尾部添加节点
3.1.4.查询链表的任意位置index处的节点值
3.1.5.判断链表中是否包含值为data的节点
3.1.6.修改链表中任意位置index处的节点值,返回修改前的节点值
3.1.7.删除链表的头节点
3.1.8.删除链表中任意位置index处节点
3.1.9.删除链表中第一次出现某个值的节点
3.1.10.删除链表中值为data的所有节点
3.1.11.判断链表的index是否合法
3.1.12.toString()方法
//车厢类
class Node{
//存储具体元素
int data;
//存储下一个节点的地址
Node next; //next是自定义的Node引用类型,存储Node类型的对象的地址,即车厢地址
//构造方法
public Node(int data){
this.data = data;
}
public Node(int data, Node next){
this.data = data;
this.next = next;
}
}
//火车类 实际上是多个车厢的组合
//单链表只能从前往后遍历
public class SingleLinkedList {
//当前火车中车厢的个数(实际存储的元素的个数)
private int size;
//当前火车的第一个节点
private Node head;
/**
* 在链表的头部添加元素
* @param data
*/
public void addFirst(int data){
//判断当前火车是否为空
//一个节点都没有,要插入的节点就是第一个节点
if(size == 0){
Node node = new Node(data); //引用数据类型,存地址
head = node;
size++;
}else{
//当前火车已经存在节点了
Node node = new Node(data);
node.next = head;
head = node;
size++;
}
}
/**
* 在链表的任意位置index处添加元素
* @param index
* @param data
*/
public void addIndex(int index, int data){
//判断边界条件,注意合法性
if(index < 0 || index > size){
System.err.println("add index illegal!");
return;
}
//若index == 0,此时不需要找前驱节点(无前驱节点),可将此作为边界条件
if(index == 0){
//头插
addFirst(data);
return;
}
//说明此时index合法且在中间位置(包含最后节点)
//此时需要找到待插入位置index的前驱节点(单链表只能从前向后遍历)
Node node = new Node(data);
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//prev引用指向当前插入index的前驱节点(prev引用从头走了index - 1步,此时prev指向index - 1节点)
node.next = prev.next;
prev.next = node;
size++;
}
/**
* 在链表的尾部添加元素
* @param data
*/
public void addLast(int data){
addIndex(size, data);
}
/**
* 查询链表index处的节点数据值
* @param index
* @return
*/
public int get(int index){
if(rangeCheck(index)){
Node node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时node指向待查找节点的索引
int data = node.data;
return data;
}else{
System.err.println("get index illegal!");
return -1;
}
}
/**
* 判断链表中是否包含节点值为data的节点
* @param data
* @return
*/
public boolean contains(int data){
Node node = head;
while(node != null){
if(node.data == data){
System.out.println("找到元素");
return true;
}
node = node.next;
}
System.out.println("没有找到该元素");
return false;
}
/**
* 修改单链表中index位置的元素,返回修改前的元素值
* @param index
* @param data
* @return
*/
public int set(int index, int data){
if(rangeCheck(index)){
Node node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时走到index处
int oldData = node.data;
node.data = data;
return oldData;
}else{
System.err.println("set index illegal!");
return -1;
}
}
/**
* 删除单链表头节点
*/
public void removeFirst(){
//判断边界条件
if(head == null || head.next == null){
//删除完链表为空链表
return;
}
Node node = head;
head = head.next;
node.next = null;
size--;
}
/**
* 删除单链表中任意位置index处的元素
* @param index
*/
public void removeIndex(int index){
if(rangeCheck(index)){
if(index == 0){
//删除头节点
removeFirst();
}else{
//删除中间位置节点
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//此时prev指向待删除节点的前驱节点
//node就是待删除的节点
Node node = prev.next;
//连接前驱节点和后继节点
prev.next = node.next;
//当前节点的next引用置为空,脱钩操作
node.next = null;
size--;
}
}else{
System.err.println("remove index illegal!");
}
}
/**
* 删除单链表中第一次出现某个值的元素
* @param data
*/
public void removeValueOnce(int data){
//先找到待删除元素的节点
//先判断头节点的情况
if(head.data == data){
//此时头节点就是第一个待删除的节点
removeFirst();
}else{
//此时头节点一定不是要删除的节点
//从头节点开始找到待删除元素的节点前驱
Node prev = head;
//此时要看下一个节点的情况
while(prev.next != null){
//找到待删除的节点
if(prev.next.data == data){
//此时prev就是待删除节点的前驱
//node就是待删除的节点
Node node = prev.next;
//将prev跳过当前node
prev.next = node.next;
node.next = null;
size--;
break;
}else{
//此时prev的下一个节点不是待删除节点
//继续向下一个节点寻找
prev = prev.next;
}
}
}
}
/**
* 删除单链表中值为data的所有元素
* @param data
*/
public void removeAllValue(int data){
//判断头节点的情况
//头节点以及之后出现了多个连续的待删除的节点
while(head != null && head.data == data){
Node node = head;
head = head.next;
node.next = null;
size--;
}
//此时head一定不是待删除的节点
//head.data != data;
if(head == null){
//此时链表全是待删除的元素节点,已删除完毕
return;
}else{
//此时头节点已经处理完毕,并且链表也不为空,向下一个节点开始进行判断
Node prev = head;
while(prev.next != null){
if(prev.next.data == data){
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
//当处理完当前删除节点后,要继续判断下一个节点是否也要删,此时prev不能移动
}else{
//此时prev的下一个节点不是要删除的节点,prev继续向后走一个单位
prev = prev.next;
}
}
}
}
public String toString(){
String ret = " ";
//此处有一个临时变量node来存储head的值,保护head,防止遍历完head不见了,这个链表就不能用了
Node node = head;
while(node != null){
ret += node.data + "->";
//继续访问下一个车厢
node = node.next;
}
ret += "NULL";
return ret;
}
/**
* 判断index是否合法
* @param index
* @return
*/
private boolean rangeCheck(int index){
if(index < 0 || index >= size){
return false;
}
return true;
}
//测试
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addFirst(3); //3->NULL
singleLinkedList.addFirst(2); //2->3->NULL
singleLinkedList.addFirst(1); //1->2->3->NULL
singleLinkedList.addLast(7); //1->2->3->7->NULL
singleLinkedList.addIndex(2,2); //1->2->2->3->7->NULL
System.out.println(singleLinkedList); //1->2->2->3->7->NULL
System.out.println(singleLinkedList.get(3)); //3
System.out.println(singleLinkedList.contains(11)); //false
System.out.println(singleLinkedList.set(2, 9)); //2
System.out.println(singleLinkedList); //1->2->9->3->7->NULL
singleLinkedList.removeFirst(); //2->9->3->7->NULL
singleLinkedList.removeIndex(1); //2->3->7->NULL
singleLinkedList.addLast(2); //2->3->7->2->NULL
singleLinkedList.removeAllValue(2); //3->7->NULL
System.out.println(singleLinkedList); //3->7->NULL
}
}
3.2.带头单向非循环链表
3.2.1.在链表任意位置index处插入节点
3.2.2.在链表头部添加节点
3.2.3.在链表尾部添加节点
3.2.4.在链表任意位置index处删除节点
3.2.5.删除链表头部节点
3.2.6.删除链表尾部节点
3.2.7.toString()方法
public class SingleLinkedListWithHead {
//虚拟头节点 在内存中实实在在存在的节点,有一个引用指向
private Node dummyHead = new Node(-1);
//只声明一个Node类的引用,无具体节点,在内存中没有开空间 private Node dummyHead;
//-1表示这个节点的data值无意义,这个节点就是来连接/脱钩其他节点的
//data若不赋值,int默认值为0,有时这个0确实是需要存储的元素,负数还是很少见
//单链表中具体节点的个数(不包括虚拟头节点)
private int size;
/**
* 在任意位置index处插入一个元素
* @param index
* @param data
*/
public void addIndex(int index, int data){
if(index < 0 || index > size){
System.err.println("add index illegal!");
return;
}
//此时index无论为何值,都存在前驱节点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev =prev.next;
}
//prev就指向了待插入的前驱节点
Node node = new Node(data);
node.next = prev.next;
prev.next = node;
size++;
}
/**
* 头插
* @param data
*/
public void addFirst(int data){
addIndex(0, data);
}
/**
* 尾插
* @param data
*/
public void addLast(int data){
addIndex(size, data);
}
public String toString(){
String ret = " ";
Node node = dummyHead.next;
while(node != null){
ret += node.data + "->";
node = node.next;
}
ret += "NULL";
return ret;
}
/**
* 在任意位置删除节点
* @param index
* @return
*/
public int removeIndex(int index){
if(index < 0 || index >= size){
System.err.println("remove index illegal!");
return -1;
}
Node prev = dummyHead;
//先找到待删除节点的前驱
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//prev就指向了待删除节点的前驱节点
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
return node.data;
}
/**
* 删除头节点
* @return
*/
public int removeFirst(){
return removeIndex(0);
}
/**
* 删除尾节点
* @return
*/
public int removeLast(){
return removeIndex(size - 1);
}
}
3.3.无头双向链表
是Java的集合框架中LinkedList底层实现。
3.3.1.在链表的头部添加节点
3.3.2.在链表的尾部添加节点
3.3.3.在链表的任意位置index处添加节点
3.3.4.查询链表的任意位置index处的节点值
3.3.5.修改链表中任意位置index处的节点值
3.3.6.传入一个双向链表节点,将该节点从双向链表中删除
3.3.7.删除链表中第一次出现某个值的节点
3.3.8.删除链表中值为val的所有节点
3.3.9.判断链表的index是否合法
3.3.10.根据index与size的大小关系快速定位指定index位置的节点
3.3.11.toString()方法
/**
* 车厢类
*/
class DoubleNode{
//指向前驱节点
DoubleNode prev;
//存储的具体元素
int val;
//指向后继节点
DoubleNode next;
//构造方法
public DoubleNode(int val){
this.val = val;
}
}
/**
* 基于int的双向链表
*/
public class DoubleLinkedList {
//存储的具体车厢个数
private int size;
//头节点
private DoubleNode first;
//尾节点
private DoubleNode last;
/**
* 头插
* @param val
*/
public void addFirst(int val){
//引入一个局部变量,暂时存储一下头节点的地址
DoubleNode f = first;//目的是为了判断链表是否为空,不为空时,执行first = node;防止first丢失。
//要插入一个节点,首先要new一个节点
DoubleNode node = new DoubleNode(val);
first = node;//因为是头插,那么这行代码不管当前链表是否为空都会执行
//若链表为空,则新建的节点既是头节点又是尾节点
if(f == null){
last = node;
}else{
node.next = f;
f.prev = node;
}
size++;
}
/**
* 尾插
* @param val
*/
public void addLast(int val){
//暂存尾节点地址
DoubleNode l = last;
DoubleNode node = new DoubleNode(val);
last = node;
if(l == null){
//链表为空
first = node;
}else{
node.prev = l;
l.next = node;
}
size++;
}
/**
* 在任意位置index处添加元素
* @param index
* @param val
*/
public void addIndex(int index, int val){
//判断index的合法性
if(index < 0 || index > size){
System.err.println("add index illegal!");
return;
}else if(index == 0){
addFirst(val);
}else if(index == size){
addLast(val);
}else{
//此时0 < index < size
DoubleNode node = node(index);
//指向当前位置的前驱节点
DoubleNode prev = node.prev;
//要插入的新节点
DoubleNode newNode = new DoubleNode(val);
//先处理后半部分引用链
newNode.next = node;
node.prev = newNode;
//再处理前半部分引用链
prev.next = newNode;
newNode.prev = prev;
size++;
}
}
/**
* 根据index索引取得节点值
* @param index
* @return
*/
public int get(int index){
if(rangeCheck(index)){
DoubleNode node = node(index);
return node.val;
}else{
System.err.println("get index illegal!");
return -1;
}
}
/**
* 重置index处的值,由index索引修改值
* @param index
* @param newVal
*/
public void set(int index, int newVal){
if(rangeCheck(index)){
DoubleNode node = node(index);
node.val = newVal;
}else{
System.err.println("set index illegal!");
}
}
/**
* 删除第一个出现的元素
* @param val
*/
public void removeValueOnce(int val){
//只需要从头开始遍历
for (DoubleNode x = first; x != null; x = x.next) {
if(x.val == val){
//此时x节点就是待删除的节点
unlinkNode(x);
return;
}
}
}
/**
* 删除指定的所有元素节点
* @param val
*/
public void removeAllValue(int val){
for (DoubleNode x = first; x != null;) {
if(x.val == val){
//x就是待删除的元素
//先暂存一下next的节点地址
DoubleNode next = x.next;
unlinkNode(x);
x = next;
}else{
x = x.next;
}
}
}
/**
* 传入一个双向链表节点,将该节点从双向链表中删除
* 核心思想:分治,先处理前驱或后继,再处理另一半的情况
* @param node
*/
private void unlinkNode(DoubleNode node) {
//待删除节点的前驱
DoubleNode prev = node.prev;
//待删除节点的后继
DoubleNode next = node.next;
//先处理前半部分引用链
//边界判断
if(prev == null){
first = next;
}else{
//此时前驱节点不为空
prev.next = next;
node.prev = null;
}
//再处理后半部分引用链
if(next == null){
last = prev;
}else{
//此时后继节点不为空
next.prev = prev;
node.next = null;
}
size--;
}
/**
* 判断index是否合法
* @param index
* @return
*/
private boolean rangeCheck(int index){
if(index < 0 || index >= size){
return false;
}
return true;
}
/**
* 根据index与size的大小关系快速定位指定index位置的节点
* @param index
* @return
*/
private DoubleNode node(int index) {
//根据index与size的大小关系来判断到底是从头向后走还是从后向前走
if(index < size >> 1){
//从头向后
DoubleNode node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}else{
//此时index > size >> 1
//从后向前
DoubleNode node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
public String toString(){
String ret = " ";
DoubleNode node = first;
while(node != null){
ret += node.val + "->";
node = node.next;
}
ret += "NULL";
return ret;
}
//测试
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.addLast(2);
doubleLinkedList.addLast(2);
doubleLinkedList.addFirst(3);
System.out.println(doubleLinkedList); //3->2->2->NULL
doubleLinkedList.removeAllValue(2);
System.out.println(doubleLinkedList);//3->NULL
}
}