3.单链表

一、概念

单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

节点结构:
在这里插入图片描述

  • data域–存放结点值的数据域;
  • next域–存放结点的直接后继的地址(位置)的指针域(链域)。

头指针head和终端结点:
在这里插入图片描述

  • 单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。链表由头指针唯一确定,单链表可以用头指针的名字来命名;
  • 终端结点无后继,故终端结点的指针域为空,即NULL。



二、单链表的相关操作

1. 单链表的插入

直接把节点添加到链表的尾部。

思路:

  1. 先创建一个head头结点(单链表不存在的情况下),表示单链表的头;
  2. 通过定义一个辅助变量 temp 指向 head (因为head是链表的头结点,不能移动)来遍历链表,找到链表的最后一个节点;
  3. 链表的最后一个节点的next域指向新节点。

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

2. 链表按顺序插入

按照HeroNode的no递增插入节点。
思路:

  1. 根据比较节点的no属性的大小来找到所要插入节点的位置;
  2. 设置一个标记位 flag 标记插入节点的编号是否已存在(若编号已存在则不添加),初始值为false;
  3. temp.next.no > 新节点.no:找到插入节点的位置,新节点就插入在temp的后面;
  4. emp.next.no == heroNode.no:所要插入节点的编号已存在,所以令 flag = true;
  5. 若到最后 flag 仍然为 false:新节点.next = temp.next ,temp.next = 新节点;
  6. flag = true:需要插入节点和已存在的节点的编号重复,故不插入,并给出提示。

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

3. 修改链表节点信息

思路:

  1. 通过传入节点的 no 遍历查询链表中所要修改的节点的信息;
  2. 设置一个标记位 flag 标记是否找到所要修改的节点,初始值为false;
  3. 根据 判断传入节点.no == temp.no 来找到所要修改的节点;
  4. 若找到所要修改的节点则把 flag 设置为 true;
  5. flag == true:把传入节点的信息赋值给 temp 节点(此时temp代表要修改的节点),no属性不赋值;
  6. flag == false:找不到要修改的节点,给出提示信息。

4. 删除指定节点

根据传入的 no 遍历查询链表,删除编号为 no 的节点。
思路:

  1. 通过传入的 no 遍历链表;
  2. 设置一个标记位 flag 标记是否找到所要删除节点的上一个节点,初始值为false;
  3. temp.next.no == no:表示找到了所要删除节点的上一个节点temp,设置 flag = true;
  4. 根据flag表示是否要删除节点;
  5. flag = true:temp.next = temp.next.next;
  6. flag = false:未找到所要删除的节点。

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

5. 遍历链表


6. 上述操作代码实现

定义一个单链表:

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 + '\'' +
                '}';
    }
}

单链表管理类:

/**
 * 定义SingleLinkedList,管理HeroNode
 */
class SingleLinkedList {

    //先初始化一个头结点,不存放具体的数据
    private HeroNode head = new HeroNode(0,null,null);
    
    public HeroNode getHead() {
        return head;
    }
   

    /**
     * 向单链表中添加节点(添加到最后).
     * @param heroNode
     */
    public void add(HeroNode heroNode) {
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点
        HeroNode temp = head;

        //变量链表,找到最后一个节点
        while (true) {
            //指向了链表的最后一个节点
            if (temp.next == null) {
                break;
            }
            //指针后移
            temp = temp.next;
        }
        //把新节点添加到链表最后一个节点的next
        temp.next = heroNode;
    }


    /**
     * 根据 HeroNode的no顺序进行节点的添加(递增)
     * @param heroNode
     */
    public void addByOrder (HeroNode heroNode) {
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点
        HeroNode temp = head;
        //新增一个标志位,标志节点是否可以添加
        boolean flag = false;

        while (true) {
            //遍历到了最后一个节点
            if (temp.next == null) {
                break;
            }
            //位置找到,就在temp的后面插入
            if (temp.next.no > heroNode.no) {
                break;
            } else if (temp.next.no == heroNode.no) {
                flag = true; //说明编号存在
                break;
            }
            temp =temp.next;
        }

        //判断flag的值决定是否添加节点
        if (flag) {
            System.out.printf("准备插入的节点 %d 已存在",heroNode.no);
            System.out.println();
        } else {
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }


    /**
     * 修改链表的节点信息(根据no查找所要修改的节点)
     * @param heroNode
     */
    public void update(HeroNode heroNode) {
	    if (head.next == null) {
	            System.out.println("链表为空,不能修改");
	            return;
	        }
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
        HeroNode temp = head.next;
        //标志是否找到所要修改的节点
        boolean flag = false;

        while (true) {
            //遍历到了链表最后
            if (temp == null) {
                break;
            }
            //找到所要修改的节点
            if (temp.no == heroNode.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //根据flag判断是否修改节点
        if (flag) {
            temp.name = heroNode.name;
            temp.nickName = heroNode.nickName;
        } else {
            System.out.printf("未找到编号为 %d 的节点\n", heroNode.no);
        }
    }


    /**
     * 删除指定的链表节点
     * @param no
     */
    public void delete(int no) {
	    if (head.next == null) {
	            System.out.println("链表为空,不能删除");
	            return;
	        }
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点
        HeroNode temp = head;
        //标志是否找到所要删除节点的上一个节点
        boolean flag = false;

        while (true) {
            //遍历到了链表最后
            if (temp.next == null) {
                break;
            }
            //找到所要删除节点的上一个节点
            if (temp.next.no == no) {
                flag = true;//表示找到了所要删除节点的上一个节点temp
                break;
            }
            temp = temp.next;
        }

        //根据flag表示是否要删除节点 到了这里temp是所要删除节点的上一个节点
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.printf("未找到编号为 %d 的节点\n", no);
        }
    }


    /**
     * 遍历链表.
     */
    public void list() {
        //链表为空
        if (head.next == null) {
            System.out.println("链表为空!");
            return;
        }
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
        HeroNode temp = head.next;
        while (true) {
            //判断是否遍历到了链表最后
            //这里不能是temp.next == null
            if (temp == null) {
                break;
            }
            //输出节点信息
            System.out.println(temp);
            temp = temp.next;
        }
    }
}



三、单链表经典面试题

1. 获取链表的节点个数。

即链表长度,不包括头结点。

  /**
     * 求一个单链表的节点个数(即链表长度,不包括头结点)
     * @param singleLinkedList
     * @return
     */
    public static int getSingleLinkedListLength(SingleLinkedList singleLinkedList) {
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
        HeroNode temp = singleLinkedList.getHead().next;
        int length = 0;
        while (temp !=null) {
            length ++;
            temp = temp.next;
        }
        return length;
    }

2. 返回单链表中的倒数第 index 个结点

思路:

  1. 编写一个方法,传入一个单链表,同时传入一个index,index 表示的是此单链表中倒数第index个节点;
  2. 先把链表从头到尾遍历,得到链表的总的长度(调取上面的getSingleLinkedListLength方法即可);
  3. 得到size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到;
  4. 如果找到了,则返回该节点,否则返回null。

代码实现:

/**
     *查找单链表中的 倒数 第 index 个结点
     * @param singleLinkedList
     * @param index
     * @return
     */
    public static HeroNode findReverseIndexNode(SingleLinkedList singleLinkedList,int index) {
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
        HeroNode temp = singleLinkedList.getHead().next;
        //链表为空
        if (temp == null) {
            return null;
        }
        //获取链表长度
        int size = getSingleLinkedListLength(singleLinkedList);
        //遍历到size-index 位置,就是我们倒数的第 index 个节点
        for (int i = 0;i < size -index; i++) {
            temp = temp.next;
        }
        return temp;
    }

3. 将单链表反转

思路:

  • 创建一个新的单链表,遍历原旧链表,依次把旧链表的每一个节点通过头插法的方式添加在新链表里,然后把原链表的头结点指向新链表头结点的下一个节点即可。

代码实现:

/**
     * 将单链表反转(头插法)
     * @param singleLinkedList
     */
    public static void reverseSingleLinkedList(SingleLinkedList singleLinkedList) {
        //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
        HeroNode temp = singleLinkedList.getHead().next;
        //链表为空,或者链表只有一个节点
        if (temp == null || temp.next == null) {
            return;
        }
        //定义一个新的头结点
        HeroNode reverseHead = new HeroNode(0,"","");
        //定义一个辅助变量用于存储temp.next
        HeroNode next = null;
        //遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端(头插法)
        while (temp != null) {
            next = temp.next;
            temp.next = reverseHead.next; //当前节点的下一个节点为reverseHead的下一个节点,即头插
            reverseHead.next = temp; //reverseHead的下一个节点就是当前节点temp
            temp = next; //temp下移
        }
        //将原链表的头结点指向新链表头结点的下一个节点,完成原链表的反转
        singleLinkedList.getHead().next = reverseHead.next;
    }

4. 逆序打印单链表

思路:

  • 不破坏链表结构的前提下,通过栈实现即可。

代码实现:

/**
    * 逆序打印单链表
    * @param singleLinkedList
    */
   public static void reversePrintSingleLinkedList(SingleLinkedList singleLinkedList) {
       //因为头结点不能动,所以我们需要一个辅助变量指向头结点的下一个节点
       HeroNode temp = singleLinkedList.getHead().next;
       if(temp == null) {
           return;//空链表,不能打印
       }
       //创建要给一个栈,将各个节点压入栈
       Stack<HeroNode> stack = new Stack<HeroNode>();
       //将链表的所有节点压入栈
       while(temp != null) {
           stack.push(temp);
           temp = temp.next;
       }
       //将栈中的节点进行打印,pop 出栈
       while (stack.size() > 0) {
           System.out.println(stack.pop());
       }
   }

5. 合并两个有序的单链表,要求合并后的链表仍然是有序的

思路:

  1. 定义一个函数,传入两个有序的链表的管理对象SingleLinkedList,获取到两个有序链表的头结点;
  2. 函数形参中获取的两个头结点,把其中一个作为新链表的头结点;
  3. 定义temp1、temp2、newTemp,分别指向形参1获取的头结点的下一个节点、形参2获取的头结点的下一个节点、新链表的下一个节点(即辅助变量,因为头结点不能移动);
  4. 当指针temp1和指针temp2均未到达链表尾时,比较temp1和temp2的no值,从temp1或者temp2这两者中选择no值较小的节点插入到newTemp的后面;
  5. 如果temp1已经到达链表1的结尾,依次将链表1剩余元素插入到newTemp的最后;
  6. 如果temp2已经到达链表2的结尾,依次将链表1剩余元素插入到newTemp的最后;

代码实现:

 /**
    * 合并两个有序的单链表,要求合并后的链表仍然是有序的.
    * @param list1
    * @param list2
    * @return
    */
   public static SingleLinkedList connOrderListByOrder(SingleLinkedList list1,SingleLinkedList list2) {
       //以list1管理的链表的头结点作为新链表的头结点
       SingleLinkedList newList = list1;
       //头结点不能动,所以定义一个辅助变量指向新链表的头结点
       HeroNode newTemp = newList.getHead();

       //头结点不能动,所以定义两辅助变量指向头结点的下一个节点
       HeroNode temp1 = list1.getHead().next;
       HeroNode temp2 = list2.getHead().next;

       //list1和list2管理的链表均未到达表尾,依次摘取两表中值较小的节点插入到newHead后面
       while (temp1!=null && temp2!=null) {
           if (temp1.no <= temp2.no) { //摘取list1所管理的链表的结点
               newTemp.next = temp1;
               newTemp = newTemp.next; //newTemp 下移
               temp1 = temp1.next;
           } else {    //摘取list2所管理的链表的结点
               newTemp.next = temp2;
               newTemp = newTemp.next;
               temp2 = temp2.next;
           }
       }
       
       //若list1管理的链表已经遍历到结尾,则直接把list2所管理的链表直接接到新链表的后面
       if (temp1 == null) {
           newTemp.next = temp2;
       }
       //同理
       if (temp2 == null) {
           newTemp.next = temp1;
       }
       //返回新的链表管理
       return newList;
   }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值