文章目录

  • 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. 思路分析

  1. 定义,定义一个自定义类,里面包含了所需要的数据域和指针域
  2. 创建,创建一个head头结点,其作用就是表示单链表的头
  3. 添加
  1. 无条件添加,每添加一个结点,直接加到链表的最后
  2. 有条件添加,例如按照id顺序添加,需要先遍历链表找到待插入的位置,再将新结点插入在正确的位置,如果id重复则添加失败
  1. 修改,遍历整个链表,找到待修改的结点,修改结点的数据
  2. 删除
  1. 首先遍历整个链表,找到待删除结点的前一个结点
  2. temp.next = temp.next.next
  3. 被删除的结点,如果没有其他引用指向,将会自动被垃圾回收机制回收。


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 从尾到头打印单链表

思路分析:

  1. 先将链表反转,再打印,这样会改变原链表,不推荐
  2. 利用栈,将各个结点压入栈中,然后利用栈“先进后出”的特点,实现逆序打印。不会改变原链表
//逆序打印链表
//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;  //最后返回新链表的头结点
}