目录
最近刚刚学完单链表,想趁热打铁总结一下。 首先,单链表的每个结点在内存中的存储相互是没有联系的,每个结点是靠着结点内存储的结点类型的引用'next'找到下一个结点的,所以可以形成一个单链表。 我们要明确的是,单链表之所以可以成为链表,是因为它的'next'。 想象一下,你在一张白纸上随机画出了若干个点,这些点在白纸上随机分布,每个点只有一条线可以连接下一个点,当然,这条线也可以什么也不连接(连接null),并且,这条线是单向的,无法逆行,即若沿着该线从A点走到B点,无法原路返回。 好了,现在单链表的框架已经有了,让我们把这些思想抽象为代码吧:
class Node{
//该类就是抽象出来的单链表的躯体
public int data;//这个结点里面存放的数据
public Node next;//可以存储Node类型地址的引用,相当于上述的绳子
Node(){}
Node(int data){
this.data=data;
}
Node(Node next){
this.next=next;
}
Node(int data,Node next){
this.data=data;
this.next=next;
}
}
public class Test {
//主类,我们对单链表的操作全部在该类中完成
public static Node head;//存储单链表的头结点
public static int size;//为了方便期间,我们增加一个size属性表示单链表的有效元素
}
下面,我将编写单链表的增加与删除两个方面
单链表的增添
单链表的增添主要分为头插入法,尾插入法,与根据指定下标插入法
头插入法
头插入法,顾名思义就是每次将结点从表头插入,注意每次插入要改变head,代码如下:
public static void headAdd(int data){
//该方法是单链表的头插入法,传入要插入的数据,可以将其按头插入法插入单链表中
//由于该方法是定义在主类中,而主类就是单链表的类,故无需返回值,也无需将head传入该方法中
if(head==null){//表示该单链表没有元素
Node node=new Node(data,null);
size++;
head=node;
return ;
}
Node node=new Node(data);
//下面两行代码是头插法的核心思想,可以画图体验一下
node.next=head;
head=node;
size++;
}
尾插入法
也好理解,就是在单链表的最后一个元素后插入元素,代码如下:
public static void footAdd(int data){
//传入一个数据data,新建一个结点将数据存入,并将该结点存储在单链表的尾部
//尾插的关键在于找到最后一个结点
//这里不能直接用head,不然该方法走完后链表就丢了,没有head就无法访问链表
Node copyHead=head;
while(copyHead.next!=null){
copyHead=copyHead.next;
}
Node node=new Node(data);
copyHead.next=node;
node.next=null;
size++;
}
根据指定下标插入节点
要在指定位置插入节点,核心思想是要找到改位置节点的前驱,并将节点插入,代码如下:
public static void indexAdd(int index,int data){
//先判断index的合法性
//index==0代表头插入,index==size代表尾插入
if(index<0||index>size){
System.err.println("输入下标有误!!");
return;
}
if(index==0){
headAdd(data);
return;
}
if(index==size){
footAdd(data);
return;
}
Node copyHead=head;
while(index-1!=0){//由于要找前驱,故为index-1(少找一位)
copyHead=copyHead.next;
index--;
}
//这两行代码是关键
Node node=new Node(data,copyHead.next);
copyHead.next=node;
size++;
}
单链表的删除
单链表的删除我主要会写根据下标删除与删除单链表中所有与输入数据相同的数据
根据下标删除结点
很简单也很基础的单链表操作,代码如下:
public static void indexDelete(int index){
//核心是找到该下标的前驱
if(index<0||index>size-1){//注意这里是size-1
System.err.println("输入下标有误!!");
return ;
}
if(index==0){
head=head.next;
size--;
return ;
}else{
Node copyHead=head;
//找前驱
while(index-1!=0){
copyHead=copyHead.next;
index--;
}
copyHead.next=copyHead.next.next;
size--;
}
}
删除该单链表中所有值与传入数据相同的节点
该删除的难点在于连续删除节点,我分别将创建虚拟头结点与不创建虚拟头结点的代码都编写一,代码如下:
//不创建虚拟头结点删除
public static void deleteAll1(int data){
//若头部就是要删除的结点,此时除了删除,还要修改head
while(head!=null&&head.data==data){
head=head.next;
size--;
}
if(head==null){
return ;
}else{
//此时删除不需要改变头,并且头部也不是要删除的结点,故可以从copyHead.next处开始判断,而且这样写便于删除
Node copyHead=head;
//下面的是难点
while(copyHead.next!=null){
if(copyHead.next!=null&©Head.next.data==data){
copyHead.next=copyHead.next.next;
size--;
}else if(copyHead.next!=null){
copyHead=copyHead.next;
}else{
break;
}
}
}
}
//创建虚拟头结点删除
public static void deleteAll2(int data){
Node dyHead=new Node(head);//注意,我们要创建的是结点,不是引用
Node copyHead=dyHead;
while(copyHead.next!=null){
if(copyHead.next!=null&©Head.next.data==data){
copyHead.next=copyHead.next.next;
size--;
}else if(copyHead.next!=null){
copyHead=copyHead.next;
}else{
break;
}
}
head=dyHead.next;
}
可以看出来,创建虚拟头结点比较方便
当你掌握了上面这些代码后,你就差不多有了初步操作单链表的能力了(单链表的简单查找与修改可以自己编写练练),对了,这里吧单链表的打印也一并给你们,方便查看链表
public static String print(){
Node copyHead=head;
String ret="";
while(copyHead!=null){
ret+=copyHead.data+"->";
copyHead=copyHead.next;
}
ret+="null";
return ret;
}
所有的代码放在下面
class Node{
//该类就是抽象出来的单链表的躯体
public int data;//这个结点里面存放的数据
public Node next;//可以存储Node类型地址的引用,相当于上述的绳子
Node(){}
Node(int data){
this.data=data;
}
Node(Node next){
this.next=next;
}
Node(int data,Node next){
this.data=data;
this.next=next;
}
}
public class Test {
//主类,我们对单链表的操作全部在该类中完成
public static Node head;//存储单链表的头结点
public static int size;//为了方便期间,我们增加一个size属性表示单链表的有效元素
public static void headAdd(int data){
//该方法是单链表的头插入法,传入要插入的数据,可以将其按头插入法插入单链表中
//由于该方法是定义在主类中,而主类就是单链表的类,故无需返回值,也无需将head传入该方法中
if(head==null){//表示该单链表没有元素
Node node=new Node(data,null);
size++;
head=node;
return ;
}
Node node=new Node(data);
//下面两行代码是头插法的核心思想,可以画图体验一下
node.next=head;
head=node;
size++;
}
public static void footAdd(int data){
//传入一个数据data,新建一个结点将数据存入,并将该结点存储在单链表的尾部
//尾插的关键在于找到最后一个结点
//这里不能直接用head,不然该方法走完后链表就丢了,没有head就无法访问链表
Node copyHead=head;
while(copyHead.next!=null){
copyHead=copyHead.next;
}
Node node=new Node(data);
copyHead.next=node;
node.next=null;
size++;
}
public static void indexAdd(int index,int data){
//先判断index的合法性
//index==0代表头插入,index==size代表尾插入
if(index<0||index>size){
System.err.println("输入下标有误!!");
return;
}
if(index==0){
headAdd(data);
return;
}
if(index==size){
footAdd(data);
return;
}
Node copyHead=head;
while(index-1!=0){//由于要找前驱,故为index-1(少找一位)
copyHead=copyHead.next;
index--;
}
//这两行代码是关键
Node node=new Node(data,copyHead.next);
copyHead.next=node;
size++;
}
public static void indexDelete(int index){
//核心是找到该下标的前驱
if(index<0||index>size-1){//注意这里是size-1
System.err.println("输入下标有误!!");
return ;
}
if(index==0){
head=head.next;
size--;
return ;
}else{
Node copyHead=head;
//找前驱
while(index-1!=0){
copyHead=copyHead.next;
index--;
}
copyHead.next=copyHead.next.next;
size--;
}
}
//不创建虚拟头结点删除
public static void deleteAll1(int data){
//若头部就是要删除的结点,此时除了删除,还要修改head
while(head!=null&&head.data==data){
head=head.next;
size--;
}
if(head==null){
return ;
}else{
//此时删除不需要改变头,并且头部也不是要删除的结点,故可以从copyHead.next处开始判断,而且这样写便于删除
Node copyHead=head;
//下面的是难点
while(copyHead.next!=null){
if(copyHead.next!=null&©Head.next.data==data){
copyHead.next=copyHead.next.next;
size--;
}else if(copyHead.next!=null){
copyHead=copyHead.next;
}else{
break;
}
}
}
}
//创建虚拟头结点删除
public static void deleteAll2(int data){
Node dyHead=new Node(head);//注意,我们要创建的是结点,不是引用
Node copyHead=dyHead;
while(copyHead.next!=null){
if(copyHead.next!=null&©Head.next.data==data){
copyHead.next=copyHead.next.next;
size--;
}else if(copyHead.next!=null){
copyHead=copyHead.next;
}else{
break;
}
}
head=dyHead.next;
}
public static String print(){
Node copyHead=head;
String ret="";
while(copyHead!=null){
ret+=copyHead.data+"->";
copyHead=copyHead.next;
}
ret+="null";
return ret;
}
public static void main(String[] args) {
headAdd(1);
headAdd(2);
headAdd(2);
footAdd(2);
footAdd(2);
footAdd(2);
System.out.println(print());
deleteAll1(2);
System.out.println(print());
}
}
进一步理解单链表
记住我开头所举的例子,单链表之所以是单链表,本质上是因为next
做完下面这道题相信你的理解会更深入,答案我粘在下面
面试题 02.04. 分割链表 - 力扣(LeetCode) (leetcode-cn.com)
public ListNode partition(ListNode head, int x) {
//创建两个虚拟头结点,便于拆分链表
ListNode smallHead=new ListNode(-1);
ListNode bigHead=new ListNode(-1);
//创建两个指针,用于操纵新创建的链表
ListNode bigTial=bigHead;
ListNode smallTiai=smallHead;
//遍历原链表
while(head!=null){
//该值比给定值小
if(head.val<x){
//插入small
smallTiai.next=head;
smallTiai=head;
}else{
//比给定值大或等于
//插入big
bigTial.next=head;
bigTial=head;
}
head=head.next;
}
//清理该链表尾部
bigTial.next=null;
//small无需清理,直接链接
smallTiai.next=bigHead.next;
return smallHead.next;
}