3.单链表Java实现(包括增删改查遍历链表,逆转链表,快慢指针查找倒数节点,递归逆序打印)

1.链表(完整代码在尾部)

1.1链表介绍

链表是有序的列表,但是它在内存中是存储如下

在这里插入图片描述

链表是以节点的方式来存储,是链式存储

每个节点包含 data 域, next 域:指向下一个节点.

如图:发现链表的各个节点不一定是连续存储.

链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

1.2单链表的应用实例

使用带 head 头的单向链表实现 –水浒英雄排行榜管理完成对英雄人物的增删改查操作

//定义英雄节点
class HeroNode{
    public int no;          //编号
    public String name;     //名称
    public String nickName; //外号
    public HeroNode next;   //指向下一个节点的指针
}

//定义单链表
class SingleLinkedList{
    //初始化头节点
    private HeroNode head  = new HeroNode(0,"","");
}
1.2.1增删改节点
  • 添加节点

在这里插入图片描述

  • 根据排名添加节点

在这里插入图片描述

  • 删除和修改同理
1.2.2复杂功能
  • 求单链表中有效节点的个数
    public int getLength(HeroNode head){
        int length =0;
        while (head.next!=null){
            length++;
            head = head.next;
        }
        return length;
    }
  • 查找单链表中的倒数第 k 个结点
//***使用快慢指针一次遍历实现查找链表的倒数第n个节点***
public HeroNode findLastIndexNode(HeroNode head,int n){
    if (head.next==null)
        throw new RuntimeException("链表为空!!!");
     HeroNode fast = head.next;
     HeroNode slow = head.next;
     while (fast.next!=null){
         //快指针先移动
         fast = fast.next;
         if (n!=1)
             n--;
         else
             //当快指针移动了n次时,代表快慢指针相隔n,慢指针可以开始移动,此时当快指针移动到链表的尾部时,慢指针指向的是链表倒数第n个节点
             slow = slow.next;
         //当快指针已经到达链表尾部时,快慢指针相隔任然小于n,说明n小于了链表的长度
         if (fast.next==null&&n>1)
             throw new RuntimeException("n大于了链表的长度!!!");
     }
     return slow;
}
  • 单链表的反转

在这里插入图片描述

//头插法实现链表的逆转
public void reversetList(HeroNode head){
    if (head.next==null||head.next.next==null)
        throw new RuntimeException("链表为空!!!");
    //将原来链表上面的节点依次插到该链表后
    HeroNode revHead = new HeroNode(0, "", "");
    HeroNode temp;
    while (head.next!=null){
        temp = head.next.next;          //temp用来保存与那链表的第二个节点,以防止数据丢失
        head.next.next=revHead.next;    //将原链表的第一个节点的next指向反转链表的第一个节点
        revHead.next = head.next;       //将反转链表的头结点的next指向原链表的第一个节点
        head.next = temp;               //原链表的第一个节点此时已经转移为了反转链表的第一个节点,将原链表的第二个节点作为原链表的第一个节点
    }
    //此处不能head = revHead 因为函数结束之后,revHead作为局部变量会失效
    head.next = revHead.next;
}
  • 从尾到头打印单链表
//递归实现逆序打印链表内容
public void reversePrint1(HeroNode head){
    if(head.next == null) {
        return;//空链表,不能打印
    }
    //当head.next指向链表尾节点时递归结束
    if (head.next.next==null)
        System.out.println(head.next);
    else{
        reversePrint1(head.next);
        System.out.println(head.next);
    }
}

//使用栈Stack逆序打印链表内容
public void reversePrint2(HeroNode head){
    if(head.next == null) {
        return;//空链表,不能打印
    }
    //创建要给一个栈,将各个节点压入栈
    Stack<HeroNode> stack = new Stack<HeroNode>();
    HeroNode cur = head.next;
    //将链表的所有节点压入栈
    while(cur != null) {
        stack.push(cur);
        cur = cur.next; //cur 后移,这样就可以压入下一个节点
    }
    //将栈中的节点进行打印,pop 出栈
    while (stack.size() > 0) {
        System.out.println(stack.pop()); //stack 的特点是先进后出
    }
}

1.3完整代码实现

import com.sun.org.apache.xpath.internal.WhitespaceStrippingElementMatcher;
import com.sun.xml.internal.ws.policy.privateutil.RuntimePolicyUtilsException;
import java.util.Stack;
//单向链表
public class SingleLinkedListDemo {
    public static void main(String[] args) {

        //创建节点
        HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
        HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
        HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
        HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();

//         singleLinkedList.addNode(hero1);
//         singleLinkedList.addNode(hero4);
//         singleLinkedList.addNode(hero2);
//         singleLinkedList.addNode(hero3);
        //加入按照编号的顺序
        singleLinkedList.addNodeByOrder(hero1);
        singleLinkedList.addNodeByOrder(hero4);
        singleLinkedList.addNodeByOrder(hero2);
        singleLinkedList.addNodeByOrder(hero3);

        singleLinkedList.list();


        singleLinkedList.updateNode(new HeroNode(3, "lusir", "兰陵王"));
        singleLinkedList.deleteNode(2);
        singleLinkedList.list();
        System.out.println(singleLinkedList.getLength(singleLinkedList.getHead()));

        //打印链表倒数第二个节点
        //System.out.println(singleLinkedList.findLastIndexNode(singleLinkedList.getHead(), 2));

        //将链表逆转
        //singleLinkedList.reversetList(singleLinkedList.getHead());

        //将链表逆序打印
        //singleLinkedList.reversePrint1(singleLinkedList.getHead());
        //singleLinkedList.reversePrint2(singleLinkedList.getHead());

        //singleLinkedList.list();
    }
}


//定义单链表
class SingleLinkedList{
    //初始化头节点
    private HeroNode head  = new HeroNode(0,"","");

    public HeroNode getHead() {
        return head;
    }

    //添加节点到单向链表
    public void addNode(HeroNode node){
        //因为头节点不能动,所以使用中介节点
        HeroNode temp = head;
        while (true){
            if (temp.next==null)
                break;
            else
                //将指针移动到链表的最后一位
                temp = temp.next;
        }
        temp.next = node;
    }

    //根据no值添加节点到单向链表
    public void addNodeByOrder(HeroNode node){
        //因为头节点不能动,所以使用中介节点
        HeroNode temp = head;
        //将中介节点移动到合适的位置
        while (true){
            if (temp.next==null)
                break;
            else if (temp.next.no>node.no)
                break;
            else if (temp.next.no==node.no){
                System.out.println("编号已存在,无法加入");
                return;
            }
            else
                //将指针移动到链表的最后一位
                temp = temp.next;
        }
        //将节点插入
        node.next = temp.next;
        temp.next = node;
    }

    public void updateNode(HeroNode node){
        HeroNode temp = head;
        //将中介节点移动到合适的位置
        while (true){
            if (temp.next==null){
                System.out.println("没有该编号节点存在");
                return;
            }
            else if (temp.next.no==node.no){
                temp.next.name = node.name;
                temp.next.nickName = node.nickName;
                return;
            }
            else
                //将指针移动到链表的最后一位
                temp = temp.next;
        }
    }

    //根据编号删除节点
    public void deleteNode(int no){
        //因为头节点不能动,所以使用中介节点
        HeroNode temp = head;
        //将中介节点移动到合适的位置
        while (true){
            if (temp.next==null){
                System.out.println("没有该编号节点存在");
                return;
            }
            else if (temp.next.no==no){
                temp.next = temp.next.next;
                return;
            }
            else
                //将指针移动到链表的最后一位
                temp = temp.next;
        }
    }

    //获得链表的长度
    public int getLength(HeroNode head){
        int length =0;
        while (head.next!=null){
            length++;
            head = head.next;
        }
        return length;
    }

    //***使用快慢指针实现一次遍历查找链表的倒数第n个节点***
    public HeroNode findLastIndexNode(HeroNode head,int n){
        if (head.next==null)
            throw new RuntimeException("链表为空!!!");
         HeroNode fast = head.next;
         HeroNode slow = head.next;
         while (fast.next!=null){
             //快指针先移动
             fast = fast.next;
             if (n!=1)
                 n--;
             else
                 //当快指针移动了n次时,代表快慢指针相隔n,慢指针可以开始移动,此时当快指针移动到链表的尾部时,慢指针指向的是链表倒数第n个节点
                 slow = slow.next;
             //当快指针已经到达链表尾部时,快慢指针相隔任然小于n,说明n小于了链表的长度
             if (fast.next==null&&n>1)
                 throw new RuntimeException("n大于了链表的长度!!!");
         }
         return slow;
    }

    //头插法实现链表的逆转
    public void reversetList(HeroNode head){
        if (head.next==null||head.next.next==null)
            throw new RuntimeException("链表为空!!!");
        //将原来链表上面的节点依次插到该链表后
        HeroNode revHead = new HeroNode(0, "", "");
        HeroNode temp;
        while (head.next!=null){
            temp = head.next.next;          //temp用来保存与那链表的第二个节点,以防止数据丢失
            head.next.next=revHead.next;    //将原链表的第一个节点的next指向反转链表的第一个节点
            revHead.next = head.next;       //将反转链表的头结点的next指向原链表的第一个节点
            head.next = temp;               //原链表的第一个节点此时已经转移为了反转链表的第一个节点,将原链表的第二个节点作为原链表的第一个节点
        }
        //此处不能head = revHead 因为函数结束之后,revHead作为局部变量会失效
        head.next = revHead.next;
    }

    //递归实现逆序打印链表内容
    public void reversePrint1(HeroNode head){
        if(head.next == null) {
            return;//空链表,不能打印
        }
        //当head.next指向链表尾节点时递归结束
        if (head.next.next==null)
            System.out.println(head.next);
        else{
            reversePrint1(head.next);
            System.out.println(head.next);
        }
    }

    //使用栈Stack逆序打印链表内容
    public void reversePrint2(HeroNode head){
        if(head.next == null) {
            return;//空链表,不能打印
        }
        //创建要给一个栈,将各个节点压入栈
        Stack<HeroNode> stack = new Stack<HeroNode>();
        HeroNode cur = head.next;
        //将链表的所有节点压入栈
        while(cur != null) {
            stack.push(cur);
            cur = cur.next; //cur 后移,这样就可以压入下一个节点
        }
        //将栈中的节点进行打印,pop 出栈
        while (stack.size() > 0) {
            System.out.println(stack.pop()); //stack 的特点是先进后出
        }
    }

    //遍历链表
    public void list(){
        if (head.next==null)
            System.out.println("链表为空无法遍历");
        //不遍历头节点
        HeroNode temp = head.next;
        while(temp!=null){
            System.out.println(temp);
            temp = temp.next;
        }
    }

}

//定义英雄节点
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;
        this.next = null;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值