单项链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域data,另一个是存储下一个结点地址的指针域next。
链表可以是带头节点的,也可以不带头节点,具体看具体的需要。头节点一般默认的data不包含任何数据。
单链表的示意图
物理结构
逻辑结构
应用场景
节点的定义
属性定义:
- no(编号)
- name(名称)
- nickName(外号)
- next(下一个节点)
class HeroNode{
public int no;
public String name;
public String nickName;
public HeroNode next; // next域
public HeroNode(int no,String name,String nickName){
this.name = name;
this.nickName = nickName;
this.no = no;
this.next = null;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
思路
1:实现第一种添加方法
- 首先要先定义一个头节点
- 将新的节点添加到链表中最后一个节点的next指向中
- 如何找到当前链表中的最后一个节点?
- 因为链表中的节点,我们是全部都不能随意变动的,所以我们可以使用一个辅助节点temp,使用temp来不断接受链表的节点遍历,在遍历时依次查看temp的next是否为null,如果为null,则表示这是最后一个节点。
具体的代码实现
public void add(HeroNode heroNode){
// 添加的时候要想办法找到链表的尾节点,让尾节点的next指向heroNode
// 因为head节点不能动,因此我们需要一个辅助节点temp
HeroNode temp = head;
// 遍历链表,找到最后
while(true){
// 找到了最后一个节点
if(temp.next == null){
break;
}
// 如果不是最后一个节点
temp = temp.next;
}
temp.next = heroNode;
}
2: 实现第二种添加方法
- 首先,添加需要考虑到No的问题,所以我们在temp接受遍历的节点的是时候,需要考虑到temp和temp.next这两个节点的No是否包含了添加节点的No。有以下三种情况,如下表述。
- 遍历到最后,temp.next为空的情况下,temp.No小于节点的No,即节点添加到链表的尾部
- 节点的No处于temp.No和temp.next.No之间
- temp.No等于节点的No
- 同时因为需要考虑到No已经存在的问题,我们定义一个布尔变量flag(是否重复),默认为false,当处于4的情况时,修改flag为true。为了方便在后须修改的时候,可以判断flag的值来判断是否存在重复
代码实现
public void addByNo(HeroNode heroNode){
// 辅助节点
HeroNode temp = head;
// 表示添加的编号是否存在, 默认为false
boolean flag = false;
while(true){
// temp已经在链表的最后了
if(temp.next == null){
break;
}
// 即节点添加到temp和temp.next之间
if(temp.next.no > heroNode.no){
break;
}
else if(temp.next.no == heroNode.no){
//heroNode的编号已经存在了
flag = true;
break;
}
temp = temp.next;
}
// 判断flag的值
if(flag){
// 编号已存在
System.out.println("待插入的英雄的编号已经存在了,不能加入");
}else {
// 插入
heroNode.next = temp.next;
temp.next = heroNode;
}
}
3:实现遍历链表
- 判断链表是否为空,即head.next==null?
- 判断是否已经遍历到最后一个节点,temp.next==?
代码实现
public void list(){
// 先判断链表是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}
// 因为头节点不能动,我们需要一个辅助遍历来遍历
HeroNode temp = head.next;
//开始遍历
while(true){
// 先判断是否到链表的最后
if(temp == null){
System.out.println("到末尾了");
break;
}
// 输出节点的信息
System.out.println(temp);
temp = temp.next;
}
}
4:实现修改节点
- 定义一个赋值节点temp
- 定义一个布尔变量flag来判断是否找到该节点
- 我们在遍历链表的时候,只要判断temp.No == 新节点.No?如果相等,则修改flag=true。
- 同时还需要判断temp是否到末尾
代码实现
public void update(HeroNode newHeroNode){
if (head.next == null){
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;
// 表示是否找到该节点
boolean flag = false;
while (true){
if (temp == null){
break;
}
if (temp.no == newHeroNode.no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.nickName = newHeroNode.nickName;
temp.name = newHeroNode.name;
}else {
System.out.println("没有匹配的节点信息");
}
}
5:节点的删除
- temp来接受链表遍历的节点信息
- 判断temp.next.no == 节点.no? 如果true,temp.next = temp.next.next。在这里必须判断temp.next.no和节点.no是否相同。因为如是等到temp.no和节点.no相同的时候,无法做到删除的效果。在删除时,是需要相同节点的前一个节点.next --> 相同节点的后一个节点
代码实现
public void delete(int no){
HeroNode temp = head;
// 是否找到待删除节点
boolean flag = false;
while (true){
if (temp.next == null){
break;
}
if (temp.next.no == no){
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.next = temp.next.next;
}else {
System.out.println("找不到节点信息");
}
}
单链表的面试题
1:求单链表的有效个数,头节点不属于有效节点
- 直接遍历链表,并且设置长度,初始值length为0,每遍历一次就length++
代码实现
public static int getNodesNumber(HeroNode head){
// 有效节点的个数
int length = 0;
// 判断头节点的下一个节点是否为空
if (head.next == null){
return length;
}
// 定义辅助变量
HeroNode temp = head;
while (temp.next != null){
length++;
temp = temp.next;
}
return length;
}
2:查找单链表中的倒数第k个节点(新浪面试题)
思路1
- 获取单链表的总长度length
- 遍历单链表寻找第length-k+1个节点
代码实现
public HeroNode getNodeByIndex(HeroNode head,int index){
// 获取节点的个数
int length = SingleLinkedList.getNodesNumber(head);
if(index > length){
System.out.println("无有效节点");
return null;
}
HeroNode temp = head;
int emp = 0;
while (true){
temp = temp.next;
emp++;
if (emp == length-index+1){
break;
}
}
return temp;
}
思路2
- 设置两个指针front和last
- front先走k-1步
- front和last一起走
- front走到末尾时,返回last
代码实现
public HeroNode getNodeByIndex2(int index){
HeroNode front = head;
HeroNode last = head;
// front 先走 index-1步
while (true){
// 记录front走的步数
int size = 0;
front = front.next;
size ++;
if (front == null){
return null;
}
if(size == index -1){
break;
}
}
// front和last一起走
while (true){
front = front.next;
if(front == null){
return last;
}
last = last.next;
}
}
3:将单链表进行反转(腾讯面试题)
- 创建一个新的头节点
- 遍历原链表
- 将遍历的节点插入到新的头节点前
- 将原节点.next = 新头节点.next
代码实现
public static void reverse(HeroNode head){
// 1: 创建一个新的节点的头部
HeroNode newHead = new HeroNode(0,null,null);
if (head.next == null || head.next.next ==null){
System.out.println("不满足反转的条件");
return;
}
// 定义一个辅助的指针,帮助我们进行遍历
HeroNode temp = head.next;
// 指向当前节点的下一个节点
HeroNode next = null;
// 开始遍历
while (temp != null){
next = temp.next;
temp.next = newHead.next;
newHead.next = temp;
temp = next;
}
head.next = newHead.next;
}
4:将链表进行逆序打印
方法1:
- 先将链表逆序
- 将链表打印
public void reverseList(HeroNode head){
SingleLinkedList.reverse(head);
SingleLinkedList.list();
}
上述的代码调用的方法已在前面的内容已经定义并实现过了的。
方法2:
- 使用栈的方式进行输出
public static void reverseList2(HeroNode head){
if (head.next == null){
System.out.println("空链表");
return;
}
// 创建一个栈
Stack<HeroNode> stack = new Stack<>();
HeroNode temp = head.next;
while (temp != null){
stack.add(temp);
temp = temp.next;
}
// 出栈
while (stack.size() > 0){
System.out.println(stack.pop());
}
}
}