一、单链表结构:链表是由N个Node节点构成,每个Node节点包括data域及next域,由Next域指向下一个Node节点对象;所以,链表这一结构特性决定了链表再内存中的数据不是连续存储的,数据分布在内存中的多个内存块中,下图为链表在内存结构中的示意图:
注:关于双向链表节点解析及常见面试题分析见博客:https://blog.csdn.net/weixin_54513300/article/details/121321081
二、链表分类:带头结点链表/与不带头结点链表,下图为带头结点链表结构示意图:
注意:头结点不存储具体的数据,仅仅表示单链表的头结点;
三、测试案例:创建链表,并向链表中存储水浒英雄
1)创建HeroNode节点类,用以存储每个英雄及别名:
//定义一个HeroNode,每个HeroNode为一个对象
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next;//指向下一个节点
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
2)定义单链表存储结构,并定义add方法及遍历方法:
//定义单链表存储数据
class SingleLinkedList{
//初始化头结点
private HeroNode head = new HeroNode(0,"","");
//定义向链表中添加Node节点的方法
//思路:找到链表中的最后一个节点(node.next=NULL)后,令node.next=new HeroNode
public void add(HeroNode heroNode){
HeroNode temp = head;
while (true){
//找到链表最后一个节点后终止循环
if (temp.next==null){
break;
}
temp = temp.next;
}
//循环结束后temp为最后一个节点,此时temp.next=heroNode即可
temp.next=heroNode;
}
//定义方法遍历链表
public void list(){
if (head.next==null){
System.out.println("链表为空");
return;
}
HeroNode temp= head.next;
while (true){
if (temp == null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
3)Demo测试
public static void main(String[] args) {
SingleLinkedList list = new SingleLinkedList();
HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
HeroNode node2 = new HeroNode(1, "吴用", "智多星");
HeroNode node3 = new HeroNode(1, "林冲", "豹子头");
list.add(node1);
list.add(node2);
list.add(node3);
//输出链表中的存储数据
list.list();
}
三、单链表常见面试题
1)求单链表中有效节点个数:
//定义一个方法获取节点数量
public static int getNodeNum(HeroNode head){
if (head.next==null){
return 0;
}
int length = 0;
HeroNode temp = head.next;
while (temp!=null){
length++;
temp=temp.next;
}
return length;
}
//demo测试:
public static void main(String[] args) {
SingleLinkedList list = new SingleLinkedList();
HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
HeroNode node2 = new HeroNode(1, "吴用", "智多星");
HeroNode node3 = new HeroNode(1, "林冲", "豹子头");
list.add(node1);
list.add(node2);
list.add(node3);
//输出链表中的存储数据
list.list();
int nodeNum = list.getNodeNum(list.getHead());
System.out.println("该链表节点数量:"+nodeNum);
}
2)查找单链表中倒数第K个节点:
在第一道题基础上就很简单了,直接说说思路:先得到链表总的节点数量Num,然后获取该链表的第Num-k节点就可以了;
3)单链表的反转:
public static void reversetList(HeroNode head) {
//如果当前链表为空,或者只有一个节点,无需反转,直接返回
if(head.next == null || head.next.next == null) {
return ;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;// 指向当前节点[cur]的下一个节点
HeroNode reverseHead = new HeroNode(0, "", "");
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
//动脑筋
while(cur != null) {
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; //将cur 连接到新的链表上
cur = next;//让cur后移
}
//将head.next 指向 reverseHead.next , 实现单链表的反转
head.next = reverseHead.next;
}
4)从尾到头打印单链表(反向遍历):
//反向遍历单链表
public static void revergeList(SingleLinkedList list,int num){
HeroNode head = list.getHead();
if (head.next==null){
System.out.println("链表为空");
return;
}
for (int i = num-1; i >= 0 ; i--) {
HeroNode node = list.getNode(list, i);
System.out.println(node);
}
}
//定义一个方法获取第K个Node节点
public static HeroNode getNode(SingleLinkedList list,int K){
HeroNode head = list.getHead();
if (head.next==null){
return null;
}
int num = 0;
HeroNode temp = head.next;
while (temp !=null){
if (num == K){
return temp;
}
if (num==getNodeNum(head)){
break;
}
temp=temp.next;
num++;
}
return null;
}
测试结果: