单向链表SingleLinkedList

目录

前言

一、链表是什么?

二、单向链表

1.单向链表介绍

2.思路分析

1.添加

2.删除

3.查询单向链表倒数第k个节点

4.逆序遍历

5.反转单向链表

3.代码实现

总结



前言

本文案例仅仅使用Java语言实现单向链表的一些简单的增删改查,完成反转单向链表,以及借助栈逆序遍历单向链表等功能。


提示:以下是本篇文章正文内容,下面案例可供参考

一、链表是什么?

链表有单向链表、双向链表、环形链表。链表是一种物理上非连续,逻辑上连续的一种数据结构。链表不需要像数组一样需要提前知晓数据大小(用于开辟空间),所以可以充分利用计算机内存空间。因为自身拥有指针域,所以直接在链表末尾增加和删除某一个节点的时候比数组更容易,但是在遍历和随机存取方面没有数组方便,也正是因为多了指针域的缘故,所以增大了内存空间的开销。

二、单向链表

1.单向链表介绍

1.单向链表是以节点形式存储的,节点中包含一个数据(data)域用于存放数据,一个指针域用于指向下一个节点的地址。

2.如图,每个节点不一定是连续存储的(物理上非连续),但是逻辑上是连续的。

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

2.思路分析

1.添加

        如果是无序添加需要找到链表的末尾,将链表中最后一个节点的指针指向新节点。

         

        如果是有序添加需要找到正确的排序位置,先将新节点的指针指向正确位置的下一个节点,再将链表中正确位置的上一个节点的指针指向新节点。(例如下图,b为新添加的节点)

2.删除

        1)需要找到要删除节点的前一个节点

        2)将找到节点的的指针指向下下个节点(如图,删除b节点)

3.查询单向链表倒数第k个节点

        获取链表总长度length,正着数第(length-k)个元素  就是倒数第k个元素。

4.逆序遍历

        方式1:反转单向链表,然后遍历(不建议:原因是会破坏链表的原始状态)

        方式2:创建一个栈,利用栈的先进后出的特点,先正序遍历将链表中每个节点压入栈中,再依次出栈遍历。

5.反转单向链表

        1)先创建一个新链表的头节点

        2)遍历原链表,取出一个节点,这个取出的节点就将其添加到新链表头节点的后面,新链表头节点之后节点的前面。(如图)

 ​​​​​​​

3.代码实现

public class SingleLinkedListDemo {
    /**
     *
     *  需求:使用带头节点的链表实现水浒传英雄排行榜 对 英雄进行增删改查操作
     *
     */
    public static void main(String[] args) {

        SingleLinkedList.Node node1 = new SingleLinkedList.Node(1,"宋江", "及时雨");
        SingleLinkedList.Node node2 = new SingleLinkedList.Node(2,"卢俊义", "玉麒麟");
        SingleLinkedList.Node node3 = new SingleLinkedList.Node(3,"吴用", "智多星");
        SingleLinkedList.Node node4 = new SingleLinkedList.Node(4,"林冲", "豹子头");


        SingleLinkedList list = new SingleLinkedList();

        //无序添加方法
        list.add(node1);
        list.add(node4);
        list.add(node3);
        list.add(node2);

//        System.out.println("正向遍历单项链表:");
        list.printList();
//
        System.out.println("------------");
//
//        //测试反向遍历链表
//        System.out.println("反向遍历单项链表:");
//        SingleLinkedList.reverseTraversal(list.getHead());

        SingleLinkedList list1 = new SingleLinkedList();

        //有序添加方法
        list1.addByOrder(node1);
        list1.addByOrder(node4);
        list1.addByOrder(node3);
        list1.addByOrder(node2);

        //验证反转单项链表
//        System.out.println("反转前单项链表:");
        list1.printList();
//
//        SingleLinkedList.reverseList(list.getHead());
//        System.out.println("反转后单项链表:");
//        list.printList();

        //验证修改方法
//        SingleLinkedList.Node targetNode = new SingleLinkedList.Node(3, "海绵宝宝", "哈哈哈哈");
//        list.update(targetNode);
//
        //验证删除的方法
//        System.out.println("删除前:");
//        list.printList();
//
//        list.remove(1);
//        list.remove(1);
//        System.out.println("删除后:");
//        list.printList();

        //验证有效节点个数
//        System.out.println("有效节点个数:" + list.getLength(list.head));
//
//        System.out.println("-------------------------------");
//
//        验证查找倒数第k个节点
//        SingleLinkedList.Node find1 = list.findLastIndexNode(list.head, 1);//k=1
//        SingleLinkedList.Node find2 = list.findLastIndexNode(list.head, 2);//k=2
//        SingleLinkedList.Node find3 = list.findLastIndexNode(list.head, 3);//k=3
//        SingleLinkedList.Node find4 = list.findLastIndexNode(list.head, 4);//k=4
//        SingleLinkedList.Node find5 = list.findLastIndexNode(list.head, 5);//k=5
//        System.out.println("查找结果:" + find1);
//        System.out.println("查找结果:" + find2);
//        System.out.println("查找结果:" + find3);
//        System.out.println("查找结果:" + find4);
//        System.out.println("查找结果:" + find5);

        //打印链表
//        list.printList();


    }

    //单向链表实现类
    static class SingleLinkedList{

        Node head;//单向链表头节点

        public SingleLinkedList() {
          
            head = new Node(0,null,null);//创建链表头 -- 完成头节点的初始化
        }


        //获取头节点
        public Node getHead() {
            return head;
        }


        /**
         * 无序添加新节点
         * @param newNode 新节点
         */
        public void add(Node newNode) {
            //定义一个辅助变量,辅助寻找链表尾,原因是直接使用头节点会破坏链表结构
            // 注意:这里其实可以做一个优化,每次添加节点到链表尾部都需要遍历寻找链表尾部,可以在该类里创建一个成员变量用于记录链表尾部,这样虽然损失了一点空间,但是可以节省时间(省去遍历)。
            Node temp =  head;
            while(true) {
                if(temp.next == null) {
                    break;
                }
                temp = temp.next;
            }
            //当while循环结束后,temp记录最后一个节点,在最后一个节点后添加
            temp.next = newNode;

        }

        /**
         * 按照排名顺序添加节点(有序)
         * @param newNode 新节点
         */
        public void addByOrder(Node newNode) {
            
            //定义一个辅助变量,辅助寻找插入位置
            Node temp = head;

            //记录当前节点是否有相同排名的英雄
            boolean flag = false;
            while(true) {
                //如果temp的下一个节点为null;已经找到链表的尾部,说明该添加的节点就该在链表尾部
                if(temp.next == null) {
                    break;
                }

                //如果寻找到的节点的下一个节点排名大于当前节点,说明找到了添加节点应该添加的位置
                if(temp.next.no > newNode.no) {
                    break;
                }else if(temp.next.no == newNode.no) {//如果相等,说明已经存在相同排名的节点
                    flag = true;
                    break;
                }

                //如果前面判断都没有跳出循环,就将temp后移一个
                temp = temp.next;
            }

            //判断是否可以添加节点 true -- 存在相同节点,false -- 可以添加
            if(flag) {
                System.out.printf("添加失败--已存在排名%d节点\n",temp.next.no);
            }else {
                //1.将新节点的下一个节点记录当前temp节点的下一个节点
                newNode.next = temp.next;
                //2.将当前temp节点的下一个节点,记录新节点地址
                temp.next = newNode;
            }
        }

        
        /**
         * 修改节点信息:不能修改节点排名,因为排名是有序的依据
         * @param targetNode 要修改的节点
         */
        public void update(Node targetNode) {

            if(head.next == null) {
                System.out.println("链表为空!");
                return;
            }

            //创建辅助节点根据排名来找需要修改节点
            Node temp = head.next;
            boolean flag = false;//记录是否找到要修改节点信息(查找结果状态)
            while (true) {
                //如果当前节点为null,说明没有该节点
                if(temp == null) {
                    break;
                }
                //如果当前节点排名和要修改的节点排名相同
                if(temp.no == targetNode.no) {
                    flag = true;
                    break;
                }
                //找下一个节点
                temp = temp.next;
            }

            //根据查找状态执行相应操作
            if(flag) {
                //找到要修改的节点
                temp.name = targetNode.name;
                temp.nickname = targetNode.nickname;
            }else {
                //没有找到节点
                System.out.printf("没有找到排名为%d的节点\n",targetNode.no);
            }
        }

        
        /**
         * 删除链表中的元素 -- 被删除的节点没有被引用就会被垃圾回收器回收
         * @param no 删除第几个节点
         */
        public void remove(int no) {

            if(no < 0 || no > getLength(head)) {
                System.out.println("没有该节点");
                return;
            }

            //定义个一个辅助节点,记录删除节点的前一个节点
            Node 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) {
                //如果找到删除节点的前一个节点,就将该节点的next指向该节点下下一个节点地址
                temp.next = temp.next.next;
            }else {
                System.out.printf("没有找到要删除的排名%d\n",no);
            }
        }


        /**
         * 遍历链表
         */
        public void printList() {
            //1.判断链表是否为null,同时防止打印链表头
            if(head.next == null) {
                System.out.println("链表为null");
                return;
            }
            //2.不为null就循环遍历
            Node temp = head.next;
            while (true) {
                //判断链表是否到最后一个(null),到达就跳出循环
                if(temp == null) {
                    break;
                }
                //打印节点信息
                System.out.println(temp);
                temp = temp.next;
            }
        }

        /**
         * 获取链表有效节点个数
         * @param head 头节点
         * @return 有效节点个数
         */
        public int getLength(Node head) {
            //空链表
            if(head.next == null) {
                return 0;
            }

            //遍历链表找有效节点个数
            int length = 0;
            Node currentNode = head.next;
            while(currentNode != null) {
                length++;
                currentNode = currentNode.next;
            }

            return length;
        }

        /**
         * 查找返回倒数第几个节点的节点
         * @param head 头节点
         * @param index 倒数第几个节点
         * @return 找到的节点或null
         */
        public Node findLastIndexNode(Node head,int index) {

            //1.链表为null
            if(head.next == null) {
                return null;
            }

            //2.获取链表有效节点个数
            int size = getLength(head);

            //3.验证传入下标合法性
            if(index<0 || index>size) {
                return null;
            }

            //3.定义一个辅助节点指向第一个节点(不包括头节点)
            Node currentNode = head.next;

            //4.遍历寻找
            for (int i = 0; i < size - index; i++) {
                currentNode = currentNode.next;
            }
            return currentNode;
        }

        /**
         * 反转单向链表
         * @param head 需要反转的单项链表的头节点
         */
        public static void reverseList(Node head) {
            //1.当链表为null 或者 链表中只有一个元素的时候无需反转
            if(head.next==null || head.next.next==null) {
                return;
            }

            //2.辅助节点
            Node currentNode = head.next;// 用一个辅助节点记录当前节点的信息,用来辅助遍历原来的链表
            Node next = currentNode.next;//保存当前节点的下一个节点(防止操作原链表的当前节点currentNode丢失当前链表后面的节点)
            Node reverseHead = new Node(0,"","");//新的头节点(反转链表的头节点)

            //3.循环遍历原数组
            while(currentNode != null) {//当前节点为null时,说明遍历到了链表的最后
                next = currentNode.next;//先将当前节点的下一个节点保存到next
                currentNode.next = reverseHead.next;//将当前节点的下一个节点指向反转链表的头节点的下一个节点(也就是将 当前节点 插入到 头节点下一个节点 之前)
                reverseHead.next = currentNode;//再将反转链表头节点的下一个节点 指向 当前节点
                currentNode = next;//当前节点后移
            }
            //4.将反转链表交还给原先的头节点
            head.next = reverseHead.next;

        }

        /**
         * 逆序遍历单向链表
         * @param head 需要遍历的链表头节点
         */
        public static void reverseTraversal(Node head) {
            //1.判断链表是否为null,为null就不需要遍历
            if(head.next == null) {
                return;
            }

            //2.创建一个栈的对象
            Stack<Node> stack = new Stack<>();

            //3.辅助遍历链表的辅助节点
            Node currentNode = head.next;

            //4.遍历链表,压入栈顶
            while(currentNode != null) {//当前链表中的节点不为null
                stack.push(currentNode);//将当前节点压入栈顶
                currentNode = currentNode.next;//将当前节点后移
            }

            //5.出栈 - 输出
            while(stack.size()>0) {//栈中元素大于0
                System.out.println(stack.pop());//出栈
            }
        }


        /**
         * 单向链表中的节点(一个静态内部类)
         */
        static class Node{
            private int no;
            private String name;
            private String nickname;

            private Node next;//指针域,用于指向下一个节点地址

            //有参构造
            public Node(int no, String name, String nickname) {
                this.no = no;
                this.name = name;
                this.nickname = nickname;
            }

            @Override
            public String toString() {
                return "Node{" +
                        "排名=" + no +
                        ", 英雄='" + name + '\'' +
                        ", 绰号='" + nickname +
                        '}';
            }
        }
    }
}


总结

以上就是今天要讲的内容,本文仅仅简单地使用Java语言实现了简单版的单向链表,重点是理解单向链表,需求可能不同,但原理不变。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值