链表

#  单向链表

1、介绍

  • 以节点的方式存储(链式存储)

  • 其中包含data和next :data 域存放数据,next 域指向下一个节点

  • 内存结构:非连续,与数组不同

  • 逻辑结构:上一个next始终指向下一个节点

DummyHead :头结点不存放数据,仅仅作为当前链表的入口

head 字段的值不能改变,一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了

因为:head始终指向头节点,在代码中的作用是保证 每次遍历从头节点开始,所以后面需要使用临时变量辅助。

     **内存结构图**(带头节点)
image-20210405200645394

逻辑结构图(带头节点)***

image-20210405200832454

2、链表应用实例

使用带 head 头的单向链表实现【水浒英雄排行榜管理】

2.1、创建英雄类

  • no :英雄编号
  • name :英雄名字
  • nickName :英雄昵称
  • next :指向下一个 HeroNode 节点,是一个对象
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 +
                '}';
    }
}

2.2、遍历链表

2.2.1、思路及步骤

(从head开始循环,依次后移直到temp == null

  1. 定义辅助变量 temp ,相当于一个指针,指向当前节点
  2. 何时遍历完成?temp == null 表明当前节点为 null ,即表示已到链表末尾
  3. 如何遍历?temp = temp.next ,每次输出当前节点信息之后,temp 指针后移
2.2.2、代码
public void ListShow(){
    //判断链表是否为空
    if(head.next == null) {
        System.out.println("链表为空");
        return;//return返回函数,break返回循环
    }
    
    HeroNode temp = head.next;
    
    while(temp != null) {
        //输出节点的信息
        System.out.println(temp);
        //将temp后移, 一定小心
        temp = temp.next;		//后移
    }
}

2.3、尾部插入

2.3.1、思路及步骤

(找到最后的节点,在这之后插入即可)

  1. 定义辅助变量 temp ,相当于一个指针,指向当前节点
  2. 如何在链表末尾插入节点?
  3. 首先需要遍历链表,找到链表最后一个节点,当 temp.next == null时,temp 节点指向链表最后一个节点
  4. 然后在 temp 节点之后插入节点即可:temp.next = heroNode
  5. img
2.3.2、代码
public void add(HeroNode heroNode) {

    //因为head节点不能动,因此我们需要一个辅助遍历 temp
    HeroNode temp = head;
    
    //遍历链表,找到最后
    while(temp.next != null) {
        //如果没有找到最后, 将将temp后移
        temp = temp.next;
    }
    
    //当退出while循环时,temp就指向了链表的最后
    //将最后这个节点的next 指向 新的节点
    temp.next = heroNode;
}

2.4顺序插入

2.4.1思路及步骤

从头开始遍历找到第一个编号比他大的,插入他之前即可,要考虑已经存在该编号

具体步骤如下

  1. 定义辅助变量 temp ,相当于一个指针,指向当前节点

  2. 首先需要遍历链表,找到链表中编号值比 heroNode.no 大的节点,暂且叫它 biggerNode ,然后把 heroNode 插入到 biggerNode 之前即可

  3. 怎么找 biggerNode ?当 temp.next.no > heroNode.no 时,这时 temp.next 节点就是 biggerNode 节点。

  4. 为什么是 temp.next 节点?只有找到 temp 节点和 temp.next(biggerNode )节点,才能在 temp 节点和 temp.next 节点之间插入 heroNode 节点

  5. 怎么插入?很重要,注意理解插入代码的实现,后续的反转中也需要用到

    heroNode.next = temp.next;
    temp.next = heroNode;
    
2.4.2代码
public void addOrder(HeroNode heroNode){

//        辅助变量temp
        HeroNode temp=head;
         Boolean flag=false;

        while (true) {
            if(temp.next==null){
                break;
            }

            if (temp.next.no > heroNode.no){
                break;
            }else if (temp.next.no == heroNode.no){
                System.out.println("编号已经存在,无法添加");
                flag=true;
                break;
            }
            
            temp=temp.next;//后移,在循环中遍历

        }
    
        if(flag) { //不能添加,说明编号存在
            System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", 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 singleLinkedList = new SingleLinkedList();
    
    // 加入按照编号的顺序
    singleLinkedList.addByOrder(hero1);
    singleLinkedList.addByOrder(hero4);
    singleLinkedList.addByOrder(hero2);
    singleLinkedList.addByOrder(hero3);
    
    // 显示一把
    singleLinkedList.list();

}

2.5修改节点

找到对应编号的节点,直接将该节点的各个信息进行替换即可,需要用到该节点的全部信息,形参需要传入head

2.5.1、思路及步骤
  1. 遍历找到该节点
  2. 定义辅助变量 temp ,相当于一个指针,指向当前节点
  3. 如何找到指定节点?temp.no = newHeroNode.no

2.6删除节点

2.6.1、思路及步骤
  1. 遍历通过编号找到该节点
  2. temp.next=该节点
  3. temp.next=temp.next.next

因为只要通过编号便可找到节点,直接将下一个节点的地址赋值给上一个节点.next即可,与本节点无关,所以函数形参只需传递(int no)不需要整个节点。

2.6.2、代码
 public void del(int no) {
        Boolean flag = false;
        if (head.next == null) {
            System.out.println("链表为空");
            return;//return返回函数,break返回循环
        }
        HeroNode temp = head;
     
        while (true) {//遍历
            if (temp.next == null) {
                break;
            }

            if (temp.next.no == no) {
                // 找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next; // temp后移
        }
        // 判断flag
        if (flag) { // 找到
            // 可以删除
            temp.next = temp.next.next;
        } else {
            System.out.println("要删除的节点不存在");
        }
    }

3、总结

  • 遍历链表,执行操作时,判断条件有时候是 temp ,有时候是 temp.next ,Why?
  • 对于插入、删除节点来说,需要知道当前待操作的节点(heroNode)前一个节点的地址(指针),如果直接定位至当前待操作的节点 heroNode ,那没得玩。。。因为不知道heroNode 前一个节点的地址,无法进行插入、删除操作,所以 while 循环中的条件使用 temp.next 进行判断
  • 对于更新、遍历操作来说,我需要的仅仅就只是当前节点的信息,所以 while 循环中的条件使用 temp进行判断

4、全部代码

package com.Jason;

import java.util.Stack;

public class SingleLinkList {
    public static void main(String[] args) {

        HeroNode n1=new HeroNode(1,"宋江","及时雨");
        HeroNode n2=new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode n3=new HeroNode(3,"吴用","智多星");
        HeroNode n4=new HeroNode(4,"公孙胜","入云龙");
        HeroNode n5=new HeroNode(4,"公","龙");

        SingleLink singleLink=new SingleLink();

        singleLink.addOrder(n1);
        singleLink.addOrder(n4);
        singleLink.addOrder(n2);
        singleLink.addOrder(n3);
        System.out.println("==========原始链表=========");
        singleLink.ListShow();



//        测试更新
        singleLink.update(n5);
        System.out.println("==========更新之后的链表=========");
        singleLink.ListShow();

        System.out.println("==========删除之后的链表=========");
        singleLink.del(3);
        singleLink.ListShow();

        //有效个数
        System.out.println("有效个数为:"+SingleLink.GetLength(singleLink.getHead()));


        System.out.println("====测试倒数第k个");
        singleLink.PrintIndex(2);


        System.out.println("==========反转之后的链表=========");
        singleLink.ReverseList(singleLink.getHead());
        singleLink.ListShow();
        
         System.out.println("===========测试逆序打印单链表, 没有改变链表的结构=========");
    	reversePrint(singleLinkedList.getHead());
    }
}

class SingleLink{
//初始化头节点
    private HeroNode head =new HeroNode(0,"","");

    public HeroNode getHead() {
        return head;
    }

    public void add(HeroNode heroNode) {

        //因为head节点不能动,因此我们需要一个辅助遍历 temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while(temp.next != null) {

            //如果没有找到最后, 将将temp后移
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        //将最后这个节点的next 指向 新的节点
        temp.next = heroNode;
    }




//顺序插入
    public void addOrder(HeroNode heroNode){

//        辅助变量temp
        HeroNode temp=head;
         Boolean flag=false;

        while (true) {
            if(temp.next==null){
                break;
            }

            if (temp.next.no > heroNode.no){
                break;
            }else if (temp.next.no == heroNode.no){
                System.out.println("编号已经存在,无法添加");
                flag=true;
                break;
            }
            temp=temp.next;//后移,在循环中遍历

        }
        if(flag) { //不能添加,说明编号存在
            System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
        } else {
            //插入到链表中, temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;//注意不能调换顺序
        }
    }


//    更新
    public void update(HeroNode NewHeroNode){

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

        HeroNode temp=head;
        while (true) {
            if (temp.next == null) {
                break;
            }

            if(temp.next.no==NewHeroNode.no){
                temp.next.name = NewHeroNode.name;
                temp.next.nickname = NewHeroNode.nickname;
                break;
            }

            temp=temp.next;
        }

    }

//    删除

    public void del(int no) {
        Boolean flag = false;
        if (head.next == null) {
            System.out.println("链表为空");
            return;//return返回函数,break返回循环
        }
        HeroNode temp = head;
        while (true) {
            if (temp.next == null) {
                break;
            }

            if (temp.next.no == no) {
                // 找到的待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next; // temp后移,遍历
        }
        // 判断flag
        if (flag) { // 找到
            // 可以删除
            temp.next = temp.next.next;
        } else {
            System.out.printf("要删除的 %d 节点不存在\n", no);
        }
    }
    public void ListShow(){
        //判断链表是否为空
        if(head.next == null) {
            System.out.println("链表为空");
            return;//return返回函数,break返回循环

        }

        HeroNode temp = head.next;
        while(temp != null) {

            //输出节点的信息
            System.out.println(temp);
            //将temp后移, 一定小心
            temp = temp.next;
        }
    }

    //求有效节点的个数
    public static int  GetLength(HeroNode head) {

        if (head.next == null) {

            System.out.println("链表为空");
            return 0;
        }

        int size = 0;
        HeroNode temp = head.next;
        while (temp != null) {

            size++;
            temp = temp.next;

        }
        return size;
    }

//    打(印倒数第k个
    public  void PrintIndex(int k){
        int length=SingleLink.GetLength(getHead());
        if(k==0||k>length){
            System.out.println("节点不存在");
        }
        HeroNode temp = head.next;

        for (int i=0; i <length-k; i++) {
            temp=temp.next;

        }
        System.out.println("倒数第"+k+"个节点是:"+temp);
    }

//链表反转
    public void ReverseList(HeroNode head){
//        首先判断是否为空,或者只有一个
        if (head.next == null || head.next.next == null) {
            return;
        }

        HeroNode temp = head.next;
        HeroNode next=null;


        HeroNode reverseHead = new HeroNode(0, "", "");

        while(temp!=null){

            next=temp.next;
            //同顺序插入
            temp.next = reverseHead.next;// 将cur的下一个节点指向新的链表的最前端
            reverseHead.next = temp; // 将cur 连接到新的链表上

            temp = next;//后移

        }

        head.next = reverseHead.next;
    }

    public  void reversePrint(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的特点是先进后出
        }
    }



}


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

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值