文章目录
一、链表是什么?
链表在物理上是非连续,非顺序的数据结构,由若干节点组成,每一个节点指向下一个节点(有且只有一个后续节点)。单向链表中每一个节点由数据和指向下一节点的指针next组成。
虚拟节点
虚拟节点通常位于链表的头部,作为一个占位符,起到辅助作用。
在链表插入、删除等操作中,虚拟节点可以作为目标位置的前一个节点,使得代码逻辑更加统一。在插入新节点时,不需要单独处理链表为空或只有一个节点的情况,而是统一处理所有位置的插入操作。
对于链表的搜索和遍历,虚拟节点也可以提供便利。它可以作为循环的起点,避免对链表为空的情况做特殊处理。
二、创建链表
1.创建节点
代码如下(示例):
节点中包括存储的数据
以及指向下一节点的指针
/**
* 节点
*/
class Node{
// 存放的数据
int data;
// 下一节点
Node next;
// 构造函数
public Node(int data) {
this.data = data;
this.next = null;
}
}
节点之间指向的示意图
2.链表的遍历
代码如下(示例):
/**
* 遍历链表的长度
* @param head
*/
public static int getListLength(Node head){
int size = 0;
Node temp = head;
// 遍历链表
while (temp != null){
temp = temp.next;
size ++;
}
return size;
}
该处使用“中间"节点temp获取指向首节点的指针,进行遍历。
3.链表的初始化
代码如下(示例):
/**
* 初始化链表
* @param arrays
* @return
*/
public static Node initLink(List<Integer> arrays){
// 首节点
Node head = null;
// 后续节点
Node cur = null;
for (int i = 0; i < arrays.size(); i++) {
// 创建当前节点
Node newNode = new Node(arrays.get(i));
if (i == 0){
head = newNode;
cur = newNode;
}else {
// 后续节点往后插入新节点
cur.next = newNode;
cur = cur.next;
}
}
// 返回首节点
return head;
}
创建了两个节点,head节点用来固定首节点的位置用来返回。cur节点用来在后续新增新节点。有了head节点不用担心cur插入新节点找不到首节点的引用。
4.插入节点
(1)插入首节点:将新节点newNode的next指向首节点head。再将首节点head的指针指向新节点newNode。
(2)插入尾节点:将尾节点cur的next指向新节点newNode,再将cur的指针指向新节点newNode。
(3)插入中间节点:遍历到当前要插入位置的前一个结点lastNode。将lastNode的next赋予新节点newNode的next。再将lastNode的next指向newNode新节点。(顺序一定不能颠倒,颠倒了会丢失原链表的后续节点)
代码如下(示例):
/**
* 插入节点
* @param head 首节点
* @param data 插入数据
* @param index 插入位置
* @return
*/
public static Node insertNode(Node head , int data , int index){
// 创建新节点
Node newNode = new Node(data);
// 判断当前链表是否为空
if (head == null){
return newNode;
}
// 遍历当前链表有多少个元素
int size = getListLength(head);
// 判断元素是否超出链表范围
if (index < 1 || index > size + 1){
System.out.println("超出了当前链表的范围");
return head;
}
// 判断是插入首节点
if (index == 1){
newNode.next = head;
head = newNode;
}else {
// 这里不用判断是否插入尾节点
// 插入中间节点已经包含了这个情况。是去插入节点的前一个在操作。
Node temp = head;
// 获取到前一个节点
while (index > 2){ //找到插入位置的上一节点 等同于 int count = 1; count < index - 1;
temp = temp.next;
index --;
}
newNode.next = temp.next;
temp.next = newNode;
}
return head;
}
使用虚拟节点进行插入
/**
* 使用虚拟节点插入新节点
* @param head 头节点
* @param data 插入数据
* @param index 插入位置
* @return
*/
public static Node insertNodeDummy(Node head, int data,int index){
// 创建虚拟节点
Node dummyNode = new Node(0);
dummyNode.next = head;
// 获取当前链表长度
int size = getListLength(head);
// 判断链表范围界限
if ( index < 1 || index > size + 1){
return head;
}
Node newNode = new Node(data);
Node temp = dummyNode;
// 插入节点
while ( index > 1){
temp = temp.next;
index --;
}
newNode.next = temp.next;
temp.next = newNode;
return dummyNode.next;
}
5.删除节点
(1)删除首节点:将指向头节点head的指针指向head的next节点。此时原来的头节点后续会被jvm回收。
(2)删除尾节点:将指向尾节点cur的指针指向cur的上一节点。将上一节点的next指向null。此时原来的尾节点后续会被jvm回收。
(3)删除中间节点:找到删除节点的上一节点,将上一节点的next指向next节点的next。如图删除节点7。
代码如下(示例):
/**
* 删除链表
* @param head 头节点
* @param index 删除节点的位置
* @return
*/
public static Node delNode(Node head,int index){
Node temp = head;
// 当前链表为空不操作
if (head != null){
// 判断index是否超出链表的范围
int size = 0 ;
while (temp != null){
temp = temp.next;
size ++;
}
if ( index < 1 || index > size){
System.out.println("超出链表的范围!!!");
}else {
// 删除节点
// 删除首节点
if (index == 1){
head = head.next;
}else {
// 删除中间节点,包含了删除尾节点的情况
//定位删除的节点的上一节点
temp = head;
int count = 1;
while ( count < index - 1){ // 找到删除节点的上一节点
temp = temp.next;
count ++;
}
temp.next = temp.next.next;
}
}
}
return head;
}
使用虚拟节点:
/**
* 使用虚拟节点进行删除节点
* @param head
* @param index
* @return
*/
public static Node delNodeDummy(Node head, int index){
// 创造虚拟节点
Node dummyHead = new Node(0);
dummyHead.next = head;
// 链表为空不操作
if (head != null){
// 判断删除节点的位置是否超出链表范围
int size = getListLength(head);
if (index < 1 || index > size){
System.out.println("超出链表范围不操作!!!");
}else {
// 删除中间节点
// 找到删除节点的上一节点
Node temp = dummyHead;
while ( index > 1 ){ // 相当于 int count = 1 ; count < index
temp = temp.next;
index --;
}
// 进行节点删除
temp.next = temp.next.next;
}
}
return dummyHead.next;
}
三、全部代码实现
/**
* 创建链表
*/
public class CreateLink {
public static void main(String[] args) {
int[] ints = {1, 2, 3, 4, 5};
Node node = initLink(ints);
printNode(node);
System.out.println("========节点插入=========");
node = insertNode(node,6,6);
printNode(node);
node = insertNode(node,0,1);
printNode(node);
node = insertNode(node,9,3);
printNode(node);
System.out.println("========使用虚拟节点插入=========");
// 虚拟节点插入
node = insertNodeDummy(node,-1,1);
printNode(node);
node = insertNodeDummy(node,10,10);
printNode(node);
node = insertNodeDummy(node,11,6);
printNode(node);
System.out.println("========删除节点=========");
node = delNode(node, 1);
printNode(node);
node = delNode(node, 10);
printNode(node);
node = delNode(node, 5);
printNode(node);
System.out.println("========使用虚拟节点删除=========");
node = delNodeDummy(node,3);
printNode(node);
node = delNodeDummy(node,1);
printNode(node);
node = delNodeDummy(node,6);
printNode(node);
}
/**
* 插入节点
* @param head 首节点
* @param data 插入数据
* @param index 插入位置
* @return
*/
public static Node insertNode(Node head , int data , int index){
// 创建新节点
Node newNode = new Node(data);
// 判断当前链表是否为空
if (head == null){
return newNode;
}
// 遍历当前链表有多少个元素
int size = getListLength(head);
// 判断元素是否超出链表范围
if (index < 1 || index > size + 1){
System.out.println("超出了当前链表的范围");
return head;
}
// 判断是插入首节点
if (index == 1){
newNode.next = head;
head = newNode;
}else {
// 这里不用判断是否插入尾节点
// 插入中间节点已经包含了这个情况。是去插入节点的前一个在操作。
Node temp = head;
// 获取到前一个节点
while (index > 2){
temp = temp.next;
index --;
}
newNode.next = temp.next;
temp.next = newNode;
}
return head;
}
/**
* 使用虚拟节点插入新节点
* @param head 头节点
* @param data 插入数据
* @param index 插入位置
* @return
*/
public static Node insertNodeDummy(Node head, int data,int index){
// 创建虚拟节点
Node dummyNode = new Node(0);
dummyNode.next = head;
// 获取当前链表长度
int size = getListLength(head);
// 判断链表范围界限
if ( index < 1 || index > size + 1){
return head;
}
Node newNode = new Node(data);
Node temp = dummyNode;
// 插入节点
while (index > 1){
temp = temp.next;
index --;
}
newNode.next = temp.next;
temp.next = newNode;
return dummyNode.next;
}
/**
* 删除链表
* @param head 头节点
* @param index 删除节点的位置
* @return
*/
public static Node delNode(Node head,int index){
Node temp = head;
// 当前链表为空不操作
if (head != null){
// 判断index是否超出链表的范围
int size = 0 ;
while (temp != null){
temp = temp.next;
size ++;
}
if ( index < 1 || index > size){
System.out.println("超出链表的范围!!!");
}else {
// 删除节点
// 删除首节点
if (index == 1){
head = head.next;
}else {
// 删除中间节点,包含了删除尾节点的情况
//定位删除的节点的上一节点
temp = head;
int count = 1;
while ( count < index - 1){ // 找到删除节点的上一节点
temp = temp.next;
count ++;
}
temp.next = temp.next.next;
}
}
}
return head;
}
/**
* 使用虚拟节点进行删除节点
* @param head
* @param index
* @return
*/
public static Node delNodeDummy(Node head, int index){
// 创造虚拟节点
Node dummyHead = new Node(0);
dummyHead.next = head;
// 链表为空不操作
if (head != null){
// 判断删除节点的位置是否超出链表范围
int size = getListLength(head);
if (index < 1 || index > size){
System.out.println("超出链表范围不操作!!!");
}else {
// 删除中间节点
// 找到删除节点的上一节点
Node temp = dummyHead;
while ( index > 1 ){
temp = temp.next;
index --;
}
// 进行节点删除
temp.next = temp.next.next;
}
}
return dummyHead.next;
}
/**
* 初始化链表
* @param arrays
* @return
*/
public static Node initLink(int[] arrays){
// 首节点
Node head = null;
// 后续节点
Node cur = null;
for (int i = 0; i < arrays.length; i++) {
// 创建当前节点
Node newNode = new Node(arrays[i]);
if (i == 0){
head = newNode;
cur = newNode;
}else {
// 后续节点往后插入新节点
cur.next = newNode;
cur = cur.next;
}
}
// 返回首节点
return head;
}
/**
* 遍历链表的长度
* @param head
*/
public static int getListLength(Node head){
int size = 0;
Node temp = head;
// 遍历链表
while (temp != null){
temp = temp.next;
size ++;
}
return size;
}
/**
* 打印链表
* @param head 首节点
*/
public static void printNode(Node head){
Node temp = head;
StringBuilder str = new StringBuilder();
while (temp != null){
str.append(temp.data).append(" --> ");
temp = temp.next;
}
System.out.println(str.toString());
}
/**
* 节点
*/
static class Node{
// 存放的数据
int data;
// 下一节点
Node next;
public Node(int data) {
this.data = data;
this.next = null;
}
}
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了普通单链表的创建,新增节点 以及删除节点。