文章目录
- 1. 单链表
- 1. 定义
- 2. 思路分析
- 3. 实现
- 3.1 自定义结点类
- 3.2 单链表类
- 3.3 单链表的实现方法
- 3.3.1 添加
- 3.3.2 修改
- 3.3.3 删除
- 3.3. 打印
- 4. 常见面试题
- 4.1 求单链表中有效结点的个数
- 4.2 查找单链表中的倒数第k个结点
- 4.3 单链表的反转
- 4.4 从尾到头打印单链表
- 4.5 合并两个有序单链表
1. 单链表
1. 定义
链表(Linked List):是有序的列表。链表分带头结点的链表和没有头结点的链表,根据实际的需求来确定。链表是以结点的方式来存储,即链式存储。链表的各个结点不一定连续。每个结点包含数据域(data)和指针域(next)。
单链表(Single Linked List):指针域中只有一个指针指向当前结点的下一结点。
2. 思路分析
- 定义,定义一个自定义类,里面包含了所需要的数据域和指针域
- 创建,创建一个head头结点,其作用就是表示单链表的头
- 添加
- 无条件添加,每添加一个结点,直接加到链表的最后
- 有条件添加,例如按照id顺序添加,需要先遍历链表找到待插入的位置,再将新结点插入在正确的位置,如果id重复则添加失败
- 修改,遍历整个链表,找到待修改的结点,修改结点的数据
- 删除
- 首先遍历整个链表,找到待删除结点的前一个结点
- temp.next = temp.next.next
- 被删除的结点,如果没有其他引用指向,将会自动被垃圾回收机制回收。
3. 实现
3.1 自定义结点类
包含数据域(data)和指针域(next)。
//自定义学生结点
class StudentNode {
public int id; //数据域
public String name;
public String school;
public StudentNode next; //指针域,指向下一个结点
public StudentNode(int number, String name, String school) {
this.id = number;
this.name = name;
this.school = school;
}
@Override
public String toString() {
return "StudentNode{" +
"id=" + id +
", name='" + name + '\'' +
", school='" + school + '\'' +
'}';
}
}
3.2 单链表类
//创建单链表类
class SingleLinkedList{
private StudentNode head = new StudentNode(0,"",""); //初始化头结点,头结点不需要存放具体的数据
//返回头结点
public StudentNode getHead() {
return head;
}
//无条件添加结点,直接添加到尾部
public void add(StudentNode stuNode){}
//有条件添加结点,根据id顺序添加
public void addById(StudentNode stuNode){}
//修改结点的信息,根据id修改其余的信息
public void update(StudentNode newStuNode){}
//删除结点
public void delete(int id){}
//打印链表
public void print(){}
}
3.3 单链表的实现方法
3.3.1 添加
//无条件添加结点,直接添加到尾部
public void add(StudentNode stuNode){
StudentNode temp = head;
while (true){ //遍历链表,找到链表的最后
if (temp.next == null){
break;
}
temp = temp.next;
}
temp.next = stuNode; //将最后这个结点的next指向新的结点
}
//有条件添加结点,根据id顺序添加
public void addById(StudentNode stuNode){
StudentNode temp = head;
boolean flag = false; //作为添加的id是否存在的标志
while (true){
if (temp.next == null){ //表示待插入结点的id应该位于链表的最后
break;
}
if (temp.next.id > stuNode.id){ //temp就是待插入结点的位置
break;
}else if (temp.next.id == stuNode.id){ //待插入结点的id已存在
flag = true; //说明此id已存在
break;
}
temp = temp.next; //遍历当前链表
}
if (flag == true){
System.out.println("待插入结点的id已存在,此id是:"+stuNode.id);
}else {
//将待插入结点插入到temp的后面
stuNode.next = temp.next;
temp.next = stuNode;
}
}
3.3.2 修改
//修改结点的信息,根据id修改其余的信息
public void update(StudentNode newStuNode){
if (head.next == null){
System.out.println("链表为空");
return;
}
//首先找到需要修改的结点
StudentNode temp = head.next;
boolean flag = false; //表示是否找到该结点
while (true){
if (temp == null){ //已经遍历完整个链表
break;
}
if (temp.id == newStuNode.id){ //找到待修改的结点
flag = true;
break;
}
temp = temp.next;
}
if (flag){
temp.name = newStuNode.name;
temp.school = newStuNode.school;
}else {
System.out.println("此编号不存在,编号为:" + newStuNode.id);
}
}
3.3.3 删除
//删除结点
public void delete(int id){
StudentNode temp = head;
boolean flag = false; //表示是否找到待插入结点的前一个结点
while (true){
if (temp.next == null){ //已经遍历完整个链表
break;
}
if (temp.next.id == id){ //找到待插入结点的前一个结点
flag = true;
break;
}
temp = temp.next; //如果都不满足,则继续遍历下一个结点
}
if (flag){
temp.next = temp.next.next;
}else {
System.out.println("待删除结点不存在");
}
}
3.3. 打印
//打印链表
public void print(){
if (head.next == null){ //判断链表是否为空
System.out.println("链表为空");
return;
}
StudentNode temp = head.next;
while (temp != null){ //判断是否到链表最后
System.out.println(temp);
temp = temp.next; //temp向后移一位
}
}
4. 常见面试题
4.1 求单链表中有效结点的个数
//获取到单链表的结点个数(带头结点的链表不统计头结点)
public int getLength(StudentNode head){
if (head.next == null){ //空链表
return 0;
}
int length = 0; //存放链表的长度
StudentNode cur = head.next;
while (cur != null){
length++;
cur = cur.next;
}
return length;
}
4.2 查找单链表中的倒数第k个结点
思路分析:
- 把链表遍历一遍,得到链表的总长度
- 得到长度后,再从头开始遍历,遍历到第(length - k)个就是倒数第k个节点
- 找到即返回该节点,否则返回null
public StudentNode findLastIndexNode(StudentNode head,int index){
if (head.next == null){ //空链表
return null;
}
int length = 0;
StudentNode cur = head.next;
while (cur != null){ //得到链表的长度,若程序中有获取长度的方法,可以直接调用
length++;
cur = cur.next;
}
if (index <=0 || index > length){ //若index值不在指定范围内,之间返回空
return null;
}
cur = head.next; //重新遍历链表,先将cur指向头结点的后一个
for (int i = 0; i < length - index; i++) {
cur = cur.next;
}
return cur;
}
4.3 单链表的反转
思路分析:
- 定义一个新结点reverseHead
- 遍历原链表,每遍历一个结点,就将此结点取出,放到刚定义的新结点reverseHead的最前端
- 最后将原链表的头部指向新链表的头部,head.next = reverseHead.next;
//单链表反转
public void reverseList(StudentNode head){
if (head.next == null || head.next.next == null){ //空链表
return;
}
StudentNode cur = head.next;
StudentNode next = null; //指向当前结点[cur]的下一结点
StudentNode reverseHead = new StudentNode(0,"","");
while (cur != null){
next = cur.next; //先保存当前结点的下一结点
cur.next = reverseHead.next; //让cur的下一结点指向新链表的最前端
reverseHead.next = cur; //将cur连接到新链表中
cur = next; //cur后移一位
}
head.next = reverseHead.next;
print();
}
4.4 从尾到头打印单链表
思路分析:
- 先将链表反转,再打印,这样会改变原链表,不推荐
- 利用栈,将各个结点压入栈中,然后利用栈“先进后出”的特点,实现逆序打印。不会改变原链表
//逆序打印链表
//l
public void reversePrint(StudentNode head){
if (head.next == null){ //空链表
return;
}
//创建一个栈,将链表中各个结点压入栈中
Stack<StudentNode> stack = new Stack<>();
StudentNode cur = head.next;
while (cur != null){ //压栈
stack.push(cur);
cur = cur.next;
}
while (stack.size() > 0){ //弹栈,并打印
System.out.println(stack.pop());
}
}
4.5 合并两个有序单链表
//合并两个链表
public StudentNode mergeList(StudentNode head1,StudentNode head2){
if (head1.next == null){ //如果当前链表为空,则直接返回另一个链表的地址
return head2;
}
if (head2.next == null){
return head1;
}
//创建一个新的链表,用于存放合并后的链表
StudentNode tempHead = new StudentNode(0,"","");
//由于头指针不能移动,所以每个链表定义一个指向其链表的指针,用于遍历链表
StudentNode temp = tempHead;
StudentNode cur1 = head1.next;
StudentNode cur2 = head2.next;
while (cur1 != null && cur2 != null){ //只要有一个链表遍历到尾部就可以结束循环了,因为另外一个链表可以直接接在新链表的尾部
if (cur1.id < cur2.id){ //根据id排序
temp.next = cur1;
cur1 = cur1.next;
}else { //由于id定义是不允许出现重复的,所以不需要额外考虑相等的情况
temp.next = cur2;
cur2 = cur2.next;
}
//每次循环新链表都会新产生一个结点,所以每次都要将temp向后移动一位,保证每次循环中temp是指向新链表的尾部
temp = temp.next;
}
//将剩下那个没有遍历到尾部的链表接入新链表的尾部
if (cur1 != null){
temp.next = cur1;
}
if (cur2 != null){
temp.next = cur2;
}
return tempHead; //最后返回新链表的头结点
}