数据结构与算法第2章:链表

单链表

单链表的结构特点

  • 链表的结构特点
  1. 链表是有序的列表,在内存中的1存储如下图 ,是以节点的方式来存储的!
  2. 每个节点包含data域,next域:指向下一节点
  3. 如下图可以发现链表的各个节点不一定是连续存储的
  4. 链表分带头节点的链表和没有头节点的链表,根据实际需求来
    大打算
    链表在逻辑结构上来看属于线性结构
    在这里插入图片描述

单链表的增删改查

单链表构建以及显示

/**
 * @Date:2021/5/23
 * @Author:GuoHeLong
 *
 * 单链表,实现水浒英雄排名
 */
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 linkedList = new SingleLinkedList();
        linkedList.add(hero1);
        linkedList.add(hero2);
        linkedList.add(hero3);
        linkedList.add(hero4);
        //显示一把
        linkedList.list();
    }

    //定义一个单链表来管理英雄
    static class SingleLinkedList{
        //先初始化一个头节点不存放具体数据,头节点不能动
        private HeroNode head = new HeroNode(0,"","");
        //添加节点到单向链表
        /*
        当不考虑编号顺序时
        1:找到当前链表的最后节点
        2:将最后这个节点的next指向新的节点
         */
        public void add(HeroNode heroNode){
            //因为head节点不能动,因此我们需要一个辅助遍历节点temp。
            // head一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了
            HeroNode temp = head;
            while (true){
                //找到链表的最后
                if (temp.next == null){
                    break;
                }
                //若没有找到将temp后移
                temp = temp.next;
            }
            //当退出while循环时,temp就指向了链表的最后。此时将temp指向加入的节点
            temp.next = heroNode;
        }
        //显示链表
        public void list(){
            //首先判断是否需要循环
            if (head.next == null){
                System.out.println("链表为空");
                return;
            }
            //定义辅助变量,因为头节点不能动
            HeroNode temp = head.next;
            while (true){
                //判断是否到链表的最后了
                if (temp == null){
                    break;
                }
                System.out.println(temp);
                //temp后移指向下一节点
                temp = temp.next;
            }
        }

    }
    //定义HeroNode,每个Hero对象就是一个节点
    static 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;
        }

        //为了显示方便
        @Override
        public String toString() {
            return "HeroNode{" +
                    "no=" + no +
                    ", name='" + name + '\'' +
                    ", nickName='" + nickName + '\'' +
                    ", next=" + next +
                    '}';
        }
    }
}

插入链表,需求:在添加英雄时根据英雄排名插入到指定位置(若有这个排名这添加失败,并给出提示)

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 linkedList = new SingleLinkedList();
        //非顺序插入
        linkedList.addByOrder(hero3);
        linkedList.addByOrder(hero1);
        linkedList.addByOrder(hero4);
        linkedList.addByOrder(hero2);
        linkedList.addByOrder(hero2);
        //显示一把
        linkedList.list();
    }
        public void addByOrder(HeroNode heroNode){
            //因为头节点不能动,因此我们仍然通过定一个辅助指针来帮忙找到添加的位置
            //找到temp一定是位于添加位置的前一节点
            HeroNode temp = head;
            boolean flag = true;
            while (true){
                if (temp.next == null || temp.next.no > heroNode.no){
                    //代表head下一节点就是要插入的位置
                    break;
                }else if (temp.next.no == heroNode.no){
                    flag = false;
                    break;
                }
                temp = temp.next; //节点后移
            }
            if (!flag){
                System.out.println("====不能添加,该英雄编号已经存在"+heroNode.no);
            }else {
                //此时temp是要插入位置的上一节点
                heroNode.next = temp.next;
                temp.next = heroNode;
            }
        }

单链表修改节点操作

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 linkedList = new SingleLinkedList();
        linkedList.add(hero1);
        linkedList.add(hero2);
        linkedList.add(hero3);
        linkedList.add(hero4);
        //显示一把
        linkedList.list();
        System.out.println("==============");
        HeroNode edit = new HeroNode(4, "公孙胜", "入云龙");
        linkedList.update(edit);
        linkedList.list();
    }
		//修改指定节点信息 根据no编号来修改,即no编号不能改.
        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) {
                    System.out.println("已遍历全表");
                }
                if (temp.no == newHeroNode.no){
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            if (!flag){
                System.out.println("未找到要修改的节点");
                return;
            }
            temp.name = newHeroNode.name;
            temp.nickName = newHeroNode.nickName;

        }

单链表删除节点:找到要删除的no直接temp.next = temp.next.next即可,跳过的节点会被GC自动回收

 public void del(int no){
            //如何删除?temp = temp.next.next 即可删除 temp.next 节点,该节点没有引用指向它,会被垃圾回收机制回收
            HeroNode temp = head;
            boolean flag = false;
            while (true){
                if (temp.next == null){
                    System.out.println("已找到链表最后");
                    break;
                }
                if (temp.next.no == no){
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            if (flag){
                //跳过要删除的节点指向下一个节点,该节点会被GC回收
                temp.next = temp.next.next;
                list();
            }else {
                System.out.println("未找到要删除的节点");
            }
        }

单链表面试题

【新浪】面试题1:计算出链表中有效节点的个数(不包含头节点)

public int getLength(HeroNode head) {
            //初始化数量
            int count = 0;
            if (head.next == null) {
                return count;
            }
            HeroNode temp = head.next;
            while (temp != null) {
                temp = temp.next;
                count++;
            }
            return count;
        }

【新浪】面试题2: 查找单链表中倒数第n个节点
思路:链表有效节点的长度-n=要找的节点

public HeroNode findLastIndexNode(HeroNode head, int index) {
            if (head.next == null) {
                return null;
            }
            int length = getLength(head);
            if (length == 0 || index > length || index < 0) {
                return null;
            }
            HeroNode temp = head.next;
            for (int i = 0; i < length - index; i++) {
                temp = temp.next;
            }
            return temp;
        }

腾讯】将单链表反转
如:1-2-3反转为3-2-1
思路:
1定义临时链表,
2循环原链表将循环到的节点的next指向临时链表的首个有效节点
3把临时链表head的next指向步骤3所得链表
4将原链表head的next指向临时链表head的next

代码实现(个人写法)

 public void reversetList(HeroNode head) {
            if (head.next == null || head.next.next == null){
                return;
            }
            HeroNode reverHead = new HeroNode(0, "", "");
            HeroNode temp = head.next;
            while (temp != null) {
                //每次循环取到当前节点temp的value,定义一个临时的节点来存储
                HeroNode upNode = new HeroNode(temp.no, temp.name, temp.nickName);
                upNode.next = reverHead.next; //临时节点的next指向当前反转链表head的next。
                reverHead.next = upNode; //再将反转链表head的next指向临时节点
                temp = temp.next; //原链表节点后移
            }
            head.next = reverHead.next;
        }

尚硅谷老师写法)

//【腾讯】单链表反转(尚硅谷老师写法)
        public void reversetListBySGG(HeroNode head) {
            if (head.next == null || head.next.next == null){
                return;
            }
            //定义一个辅助指针帮助遍历原来的链表
            HeroNode cur = head.next;
            HeroNode next = null;//指向当前节点的下一节点
            HeroNode reverseHead = new HeroNode(0,"","");
            while (cur != null){
                next = cur.next; //先将遍历节点的下一节点存起来
                cur.next = reverseHead.next;  //当前节点的next指向反转链表head的next
                reverseHead.next = cur; //反转链表的head的next再指向cur就完成了换位
                cur = next; //cur后移
            }
            head.next = reverseHead.next; //最后将原链表head的next指向反转链表head的next
        }

【百度】将链表逆向打印
思路
利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点

public void reversePrint(HeroNode head){
            //利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点
            if (head.next == null){
                return;
            }
            Stack<HeroNode> stack = new Stack<>();
            HeroNode cur = head.next;
            while (cur != null){
                stack.push(cur);
                cur = cur.next;
            }
            while (stack.size() > 0){
                HeroNode pop = stack.pop();
                System.out.println(pop);
            }
        }

单链表缺点分析

  • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  • 单项链表不能自我删除,需要依靠辅助接点,而双向链表则可以自我删除

双向链表

在这里插入图片描述
总结:多出了pre上一节点,灵活变通即可

双向链表的增删改查

代码示例

package com.ghl.likendList;

/**
 * @Date:2021/5/31
 * @Author:GuoHeLong 双向链表
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        //测试,先创建几个节点
        HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
        HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
        HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
        HeroNode2 hero4 = new HeroNode2(4, "林冲", "豹子头");
        //加入链表
        DoubleLinkendList linkendList = new DoubleLinkendList();
        //=======================测试顺序插入===============================================================
        //顺序插入
        linkendList.add(hero1);
        linkendList.add(hero2);
        linkendList.add(hero3);
        linkendList.add(hero4);
        linkendList.list();
        //linkendList.del(2);//删除
        linkendList.update(new HeroNode2(1,"宋老黑","解绑星"));
        linkendList.update(new HeroNode2(4,"林教头","进军教头"));
    }

    //创建一个双向链表类
    static class DoubleLinkendList {
        //先初始化一个头节点不存放具体数据,头节点不能动
        private HeroNode2 head = new HeroNode2(0, "", "");

        //返回头节点
        public HeroNode2 getHead() {
            return head;
        }

        //添加一个节点到双向链表最后
        public void add(HeroNode2 heroNode) {
            HeroNode2 temp = head;
            while (true) {
                //找到最后节点
                if (temp.next == null) {
                    break;
                }
                temp = temp.next;
            }
            //要加入的节点的pre指向链表最后一个节点
            heroNode.pre = temp;
            //链表的最后一个节点指向新加节点
            temp.next = heroNode;

        }
        //插入
        public void addByOrder(HeroNode2 hero) {
            System.out.println("插入节点");
            HeroNode2 temp = head;
            boolean flag = false;
            while (true) {
                if (temp.next == null || temp.next.no > hero.no) {
                    flag = true;
                    break;
                }
                if (temp.next.no == hero.no) {
                    System.out.println("已有相同的不能插入");
                    break;
                }
                temp = temp.next;
            }
            if (flag) {
                HeroNode2 heroNode2 = temp.next; //要插入位置的下一节点
                hero.pre = temp;
                hero.next = heroNode2;
                temp.next = hero;
                if (heroNode2 != null) {
                    hero.next.pre = hero;
                }
            }
            list();
        }

        //修改双向链表的一个节点
        public void update(HeroNode2 upHeroNode) {
            System.out.println("================显示链表======================");
            HeroNode2 temp = head;
            if (temp.next == null) {
                System.out.println("要修改的链表为空~~~~");
                return;
            }
            boolean flag = false;
            while (true) {
                if (temp == null) {
                    System.out.println("已经遍历全表");
                }
                if (temp.no == upHeroNode.no) {
                    flag = true;
                    break;
                }
                temp = temp.next; //后移
            }
            if (!flag) {
                System.out.println("未找到要删除的节点");
                return;
            }
            temp.name = upHeroNode.name;
            temp.nickName = upHeroNode.nickName;
            System.out.println("修改成功");
            list();
        }

        /*
        del=要删除节点
        删除节点。
        找到del的上一节点,然后上一节点的next指向del的下一节点,
        若del的下一节点不为null则下一节点的pre指向del的上一节点
        删除完毕,GC自动清理del
         */
        public void del(int no) {
            System.out.println("=================删除节点===========================");
            HeroNode2 temp = head;
            if (temp.next == null) {
                System.out.println("链表为空~~~");
                return;
            }
            boolean flag = false;
            while (temp.next != null) {
                if (temp.next.no == no) {
                    flag = true; //找到了del,此时指针停留在上一节点
                    break;
                }
                temp = temp.next;
            }
            if (!flag) {
                System.out.println("未找到要删除节点");
                return;
            }
            HeroNode2 delNext = temp.next.next; //先拿到要del的下一节点
            if (delNext != null) { //防止最有一个节点是null导致nullPoint
                delNext.pre = temp; //del的下一节点指向del的上一节点
            }
            temp.next = delNext; //del的上一节点的next指向del的下一节点
            System.out.println("删除成功");
            list();
        }

        //遍历双向链表
        public void list() {
            System.out.println("================显示链表======================");
            if (head.next == null) {
                System.out.println("链表为空~~~~");
            }
            HeroNode2 temp = head.next;
            while (temp != null) {
                System.out.println(temp);
                temp = temp.next;
            }
        }
    }

    //定义HeroNode,每个Hero对象就是一个节点
    static class HeroNode2 {
        public int no; //英雄编号
        public String name;//英雄名
        public String nickName; //英雄昵称
        public HeroNode2 next; //指向下一节点,默认为null
        public HeroNode2 pre; //指向上一节点,默认为null

        public HeroNode2(int no, String name, String nickName) {
            this.no = no;
            this.name = name;
            this.nickName = nickName;
        }

        @Override
        public String toString() {
            return "HeroNode2{" +
                    "no=" + no +
                    ", name='" + name + '\'' +
                    ", nickName='" + nickName + '\'' +
                    '}';
        }

    }
}

约瑟夫环问题

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

package com.ghl.likendList;

/**
 * @Date:2021/6/5
 * @Author:GuoHeLong
 *
 * 约瑟夫环实现
 */
public class JosepFu {
    public static void main(String[] args) {
        CircleSingleLekinList lekinList = new CircleSingleLekinList();
        lekinList.addBoy(5);
        lekinList.list();
        lekinList.countBoy(1, 2, 3); // 2->4->1->5->3
    }
    //创建一个环形的单向链表
    static class CircleSingleLekinList{
        //先创建一个firest节点,当前没有编号
        private Boy firest = null;
        //添加小孩节点,构建成一个环形链表
        public void addBoy(int nums){
            if (nums < 1){
                System.out.println("nums值不正确");
                return;
            }
            Boy curBoy = null;
            for (int i = 1; i <= nums; i++) {
                Boy boy = new Boy(i);
                if (i == 1){
                    firest = boy;
                    firest.setNext(firest); //先构成环
                    curBoy = firest; //让curBoy指向第一个小孩
                }else {
                    curBoy.setNext(boy);
                    boy.setNext(firest);
                    curBoy = boy;
                }
            }
        }

        //遍历环形链表
        public void  list(){
            if (firest == null){
                System.out.println("没有小孩");
                return;
            }
            Boy curBoy = firest;
            while (true){
                System.out.println("编号为 :"+curBoy.getNo());
                if (curBoy.getNext() == firest){
                    break;
                }
                curBoy = curBoy.getNext();
            }
        }
        /**
         *
         * @param startNo  表示从第几个小孩开始数数
         * @param countNum 表示数几下
         * @param nums     表示最初有多少小孩在圈中
         */
        public void countBoy(int startNo, int countNum, int nums) {
            // 先对数据进行校验
            if (firest == null || startNo < 1 || startNo > nums) {
                System.out.println("参数输入有误, 请重新输入");
                return;
            }
            // 创建要给辅助指针,帮助完成小孩出圈
            Boy helper = firest;
            // 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点
            while (true) {
                if (helper.getNext() == firest) { // 说明helper指向最后小孩节点
                    break;
                }
                helper = helper.getNext();
            }
            // 小孩报数前,先让 firest 和 helper 移动 k - 1次(firest指针指向开始节点,helper也是)
            for (int j = 0; j < startNo - 1; j++) {
                firest = firest.getNext();
                helper = helper.getNext();
            }
            // 当小孩报数时,让firest 和 helper 指针同时 的移动 m - 1 次, 然后出圈
            // 这里是一个循环操作,直到圈中只有一个节点
            while (true) {
                if (helper == firest) { // 说明圈中只有一个节点
                    break;
                }
                // 让 firest 和 helper 指针同时 的移动 countNum - 1(-1是因为自己也要数,也就是节点进位是countNum-1)
                for (int j = 0; j < countNum - 1; j++) {
                    firest = firest.getNext();
                    helper = helper.getNext();
                }
                // 这时firest指向的节点,就是要出圈的小孩节点
                System.out.printf("小孩%d出圈\n", firest.getNo());
                // 这时将firest指向的小孩节点出圈
                firest = firest.getNext();
                helper.setNext(firest);

            }
            System.out.printf("最后留在圈中的小孩编号%d \n", firest.getNo());

        }
    }
    static class Boy{
        private int no;
        private Boy next;

        public Boy() {
        }

        public Boy(int no) {
            this.no = no;
        }

        public int getNo() {
            return no;
        }

        public void setNo(int no) {
            this.no = no;
        }

        public Boy getNext() {
            return next;
        }

        public void setNext(Boy next) {
            this.next = next;
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值