链表

0.链表介绍

​ 链表是有序的列表,但是他在内存中是这样存储的:

在这里插入图片描述注:

[1].链表是以节点的方式来存储的,是链式存储。

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

[3].如图发现,链表的的各个节点不一定是连续存储。

[4].链表分带有头结点的链表和没有头结点的链表,根据实际的需求来确定。

1.单链表

​ 单链表(带有头节点)逻辑结构示意图如下:

单链表

1.1单链表操作实现:

添加

1.先创建一个head头节点,作用就是指向单链表的头。

2.后面我们每添加一个节点,就直接添加到链表的最后。

在这里插入图片描述

遍历

1.通过一个辅助指针变量,帮助我们遍历整个链表。

按照编号顺序添加

1.首先找到新添加节点的位置,是通过辅助变量,通过遍历找到要添加位置的前一个结点。

2.新的节点.next = temp.next。

3.temp.next = 新的节点。

在这里插入图片描述

从单链表中删除一个节点的思路

1.我们要先找到需要删除的这个节点的前一个节点temp。

2.temp.next = temp.next.next。

3.被删除的这个节点,将不会有其他引用指向,会被垃圾回收机制回收。

在这里插入图片描述

修改

1.通过辅助指针遍历找到要修改的节点。

2.修改节点相应信息。

1.2单链表的应用实例
//链表节点元素类
public class Fund {
    /**
     * 基金编号
     */
    private int no;

    /**
     * 基金名
     */
    private String name;

    /**
     * 基金经理
     */
    private String manager;

    /**
     * 每一分额价格
     */
    private double price;

    /**
     * 后继结点
     */
    private Fund next;

    /**
     * 构造器
     * @param no
     * @param name
     * @param manager
     * @param price
     */
    public Fund(int no,String name,String manager,double price) {
        this.no = no;
        this.name = name;
        this.manager = manager;
        this.price = price;
    }

    public int getNo() {
        return no;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getManager() {
        return manager;
    }

    public void setManager(String manager) {
        this.manager = manager;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Fund getNext() {
        return next;
    }

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

    @Override
    public String toString() {
        return "Fund [no=" + no + ", name=" + name + ", manager=" + manager + ", price=" + price + "]";
    }
}

单链表

public class SingleLinkedList{
    /**
     * 头结点
     */
    private Fund head = new Fund(0, "", "", 0);

    public Fund getHead() {
        return head;
    }

    public void setHead(Fund head) {
        this.head = head;
    }

    /**
     * 判断链表是否为空
     * @return
     */
    public boolean isEmpty() {
        return head.getNext() == null;
    }

    /**
     * 遍历链表
     */
    public void show() {
        //定义辅助指针
        Fund temp = head.getNext();

        while(temp != null) {
            System.out.println(temp.toString());
            temp = temp.getNext();//指针后移
        }
    }

    /**
     * 添加新的结点到链表尾部
     * @param fund
     */
    public void add(Fund fund) {
        //找到链表的尾结点
        Fund temp = head;
        while(temp.getNext() != null) {
            temp = temp.getNext();
        }

        //循环结束后,temp的后继为空,即是temp为尾结点
        temp.setNext(fund);
    }

    /**
     * 按照基金编号从小到大的顺序添加新的结点到链表中
     * @param fund
     */
    public void addByNo(Fund fund) {
        /*
         * 思路:遍历链表找到要插入位置的前一个结点
         * 辅助指针最初需要指向头结点,遍历到倒数第二个结点即可
         * 如果no值重复,return,如果找到插入位置的前一个位置,break
         * 否则指针后移
         *
         * 最后插入到辅助结点temp后边
         */
        System.out.println(fund.toString());
        Fund temp = head;
        while(temp.getNext() != null) {
            if(temp.getNext().getNo() == fund.getNo()) {
                System.out.println("该编号的基金信息已存在,无法重复插入!");
                return;
            }
            else if(temp.getNext().getNo() > fund.getNo()) {
                break;
            }
            else {
                temp = temp.getNext();
            }
        }
        //循环结束后,如果没有return,直接将结点插入到链表的尾结点
        fund.setNext(temp.getNext());
        temp.setNext(fund);
    }

    /**
     * 更新结点
     * @param fund
     */
    public void update(Fund fund) {
        /*
         * 思路:按照编号找到要修改的基金结点,如果找到修改信息
         * 找不到结束
         */
        //链表为空时,直接返回return
        if(isEmpty()) {
            System.out.println("链表为空,无法修改!");
            return;
        }

        //遍历链表找到要删除的结点,丛链表的第一个结点开始
        Fund temp = head.getNext();
        while(temp != null) {
            //如果找到结点,更新信息
            if(temp.getNo() == fund.getNo()) {
                temp.setName(fund.getName());
                temp.setManager(fund.getManager());
                temp.setPrice(fund.getPrice());
                return;
            }//找不到就继续后移指针
            else {
                temp = temp.getNext();
            }
        }
        System.out.println("没有找到基金编号为:"+fund.getNo()+"的基金,无法修改!");
    }

    /**
     * 通过编号删除结点并返回
     * @param no
     * @return
     */
    public Fund deleteByNo(int no) {
        /*
         * 思路:遍历链表找到要删除结点的前一个结点
         * 如果找不到返回null,找到删除结点并返回
         * 方法:从链表的头结点开始,如果temp.next.no == no表示找到
         * 使用boolean类型的flag变量标志是否找到要删除结点
         */
        if(isEmpty()) {
            System.out.println("链表为空,无法删除!");
            return null;
        }

        //定义辅助结点指向头结点
        Fund temp = head;
        boolean flag = false;
        while(temp.getNext() != null) {
            if(temp.getNext().getNo() == no) {
                flag = true;
                break;
            }
            else {
                temp = temp.getNext();
            }
        }

        //如果找到,执行删除操作并返回结点
        if(flag) {
            Fund fund = temp.getNext();//要删除的结点
            temp.setNext(fund.getNext());
            return fund;
        }//否则返回null
        else {
            System.out.println("没有找到基金编号为:"+no+"的基金,无法删除!");
            return null;
        }
    }
}

练习题:

1.获得链表有效节点个数

 /**
     * 获得有效结点个数
     * @return
     */
    public int getLength() {
        //如果链表长度为0,返回0
        if(isEmpty()) {
            return 0;
        }

        //否则从第一个结点遍历链表
        int length = 0;//记录结点个数
        Fund temp = head.getNext();
        while(temp != null) {
            length++;
            temp = temp.getNext();
        }
        return length;
    }

2.查找链表中的倒数第k个节点

 /**
     * 查找链表的倒数第k个结点
     * @param k
     * @return
     */
    public Fund getReverseOfK(int k) {
        /*
         * 思路:1.如果链表为空,返回null
         * 2.获得链表有效结点个数并验证k值是否合法
         * 3.定义辅助指针指向第一个结点,然后让指针后移length-k次即可找到
         */
        //1.链表为空
        if(isEmpty()) {
            System.out.println("链表为空,无法查找!");
            return null;
        }

        //2.k值不合法
        int length = getLength();
        if(k <= 0 || k > length){
            System.out.println("k值不合法!!");
            return  null;
        }

        //3.辅助指针指向第一个有效结点
        //指针后移length - k次
        Fund temp = head.getNext();
        for(int i = 0; i < length - k; i++) {
            temp = temp.getNext();//指针后移
        }
        //循环结束后,temp指向的结点就是要找的倒数第k个结点
        return temp;
    }

3.单链表的反转

/**
     * 反转链表
     */
    public void reverse(){
        //1.如果链表为空或者长度为1,直接返回
        if(isEmpty() || getLength() == 1){
            return;
        }

        //2.如果链表长度大于1
        //定义一个新的头结点
        Fund newHead = new Fund(0,"","",1.12);
        //遍历链表,将结点逐个出入到新链表的第一个结点

        Fund temp = head.getNext();
        while(temp != null){
            //记录辅助结点的后一个结点
            Fund tempNext = temp.getNext();
            //插入到新的链表
            temp.setNext(newHead.getNext());
            newHead.setNext(temp);
            //辅助指针后移
            temp = tempNext;
        }

        //循环结束后,新的链表就是原链表的反转
        //老的头结点指向新的头结点即可
        head.setNext(newHead.getNext());
    }

4.从未到头打印链表,不改变链表的结构

使用栈,遍历链表入栈,然后逐个出栈。

5.合并两个有序的单链表,合并之后的链表依然有序

/**
     * 合并两个递增的有序链表
     * @param s1
     * @param s2
     * @return
     */
    public static SingleLinkedList merge(SingleLinkedList s1,SingleLinkedList s2){
        //1.两个链表有一个为空时,返回另一个
        if(s1.isEmpty()){
            return  s2;
        }
        else if(s2.isEmpty()){
            return s1;
        }

        //2.将链表1结点逐个插入到链表2
        //辅助指针指向表1的第一个有效结点
        Fund temp = s1.getHead().getNext();
        //当temp不为空时,插入到表2
        while(temp != null){
            //记录temp的后继
            Fund tempNext = temp.getNext();
            s2.addByNo(temp);
            temp = tempNext;//辅助指针后移
        }
        //循环结束时,s1中的所有结点都被遍历了一次,并且已经加入到了s2中
        //返回链表s2即可
        return s2;
    }

2.双向链表

管理单向链表的缺点分析:

1.单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。

2.单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点。

2.1双链表操作

添加:默认添加到双链表的最后

1.先找到双向链表的最后这个节点

2.temp.next = newNode;

newNode.pre = temp;

修改

和单链表修改操作思路一致

删除

1.因为是双向链表,因此,我们可以实现自我删除某个节点

2.直接找到要删除的节点

3.temp.pre.next = temp.next

4.temp.next.pre = temp.pre

在这里插入图片描述

遍历

方法和单链表一样,但是双链表可以正向遍历也可以反向遍历。

2.2双链表实例
public class Fund {
    /**
     * 基金编号
     */
    private int no;

    /**
     * 基金名
     */
    private String name;

    /**
     * 基金经理
     */
    private String manager;

    /**
     * 每一分额价格
     */
    private double price;

    /**
     * 后继结点
     */
    private Fund next;

    /**
     * 前驱结点
     */
    private Fund prior;



    /**
     * 构造器
     * @param no
     * @param name
     * @param manager
     * @param price
     */
    public Fund(int no, String name, String manager, double price) {
        this.no = no;
        this.name = name;
        this.manager = manager;
        this.price = price;
    }

    public int getNo() {
        return no;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getManager() {
        return manager;
    }

    public void setManager(String manager) {
        this.manager = manager;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Fund getNext() {
        return next;
    }

    public void setNext(Fund next) {
        this.next = next;
    }
    public Fund getPrior() {
        return prior;
    }

    public void setPrior(Fund prior) {
        this.prior = prior;
    }

    @Override
    public String toString() {
        return "Fund [no=" + no + ", name=" + name + ", manager=" + manager + ", price=" + price + "]";
    }
}
public class DoubleLinkedList {
    /**
     * 头结点
     */
    private Fund head = new Fund(0,"","",0);

    /**
     * 判断链表是否为空
     * @return
     */
    public boolean isEmpty(){
        return head.getNext() == null;
    }

    /**
     * 打印链表
     */
    public void show(){
        if(isEmpty()){
            System.out.println("链表为空!!!");
            return;
        }

        //定义辅助结点指向第一个有效结点
        Fund temp = head.getNext();
        while(temp != null){
            System.out.println(temp.toString());
            temp = temp.getNext();
        }
    }

    /**
     * 添加新的结点到链表尾部
     * @param fund
     */
    public void add(Fund fund){
        //找到链表的尾结点
        Fund temp = head;
        while(temp.getNext() != null){
            temp = temp.getNext();
        }
        //插入
        temp.setNext(fund);
        fund.setPrior(temp);
    }

    /**
     * 按照基金标号从小到大的顺序添加
     * @param fund
     */
    public void addByNo(Fund fund){
        /*
        思路:需要找到待插入结点的前一个结点
        1.辅助指针temp从头结点开始
        2.如果temp.next!=null执行循环
        3.如果temp.next.no == fund.no 方法执行结束,提示该编号的基金已存在
        4.需要一个boolean变量表示该节点是否已经插入
        5.循环结束后如果flag=false,需要插入到最后
         */
        Fund temp = head;
        boolean flag = false;
        while(temp.getNext() != null){
            if(temp.getNext().getNo() == fund.getNo()){
                System.out.println("该基金信息已经存在,无法重复添加");
                return;
            }
            else if(temp.getNext().getNo() > fund.getNo()){
                flag = true;
                //执行插入
                fund.setNext(temp.getNext());
                temp.getNext().setPrior(fund);
                fund.setPrior(temp);
                temp.setNext(fund);
                break;
            }
            else{
                temp = temp.getNext();
            }
        }

        //插入到最后
        if(!flag){
            temp.setNext(fund);
            fund.setPrior(temp);
        }
    }

    /**
     *修改结点信息
     * @param fund
     */
    public void update(Fund fund) {
        /*
         * 思路:按照编号找到要修改的基金结点,如果找到修改信息
         * 找不到结束
         */
        //链表为空时,直接返回return
        if(isEmpty()) {
            System.out.println("链表为空,无法修改!");
            return;
        }

        //遍历链表找到要删除的结点,丛链表的第一个结点开始
        Fund temp = head.getNext();
        while(temp != null) {
            //如果找到结点,更新信息
            if(temp.getNo() == fund.getNo()) {
                temp.setName(fund.getName());
                temp.setManager(fund.getManager());
                temp.setPrice(fund.getPrice());
                return;
            }//找不到就继续后移指针
            else {
                temp = temp.getNext();
            }
        }
        System.out.println("没有找到基金编号为:"+fund.getNo()+"的基金,无法修改!");
    }

    /**
     * 按照编号删除结点并返回
     * @param fund
     * @return
     */
    public Fund deleteByNo(Fund fund){
        if(isEmpty()){
            System.out.println("链表为空,无法删除");
            return null;
        }

        //遍历链表找到要删除的结点
        Fund temp = head.getNext();
        while(temp != null){
            //如果找到就删除
            if(temp.getNo() == fund.getNo()){
                temp.getPrior().setNext(temp.getNext());
                //如果不是尾结点才需要执行这条语句
                if(temp.getNext() != null){
                    temp.getNext().setPrior(temp.getPrior());
                }
                return temp;//返回删除的结点
            }
            //否则辅助指针后移
            else{
                temp = temp.getNext();
            }
        }
        System.out.println("没有找到要删除的结点信息!");
        return  null;
    }
}

3.环形链表与约瑟夫问题

Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

3.1操作

构建环形链表

1.先创建第一个节点,让first指向该节点,并形成环:first.next = first

2.后面当我们每创建一个新的节点,就把该节点加入到已有的环形链表即可。

class Boy {
	/**
	 * 编号
	 */
	private int no;
	
	/**
	 * 后继结点
	 */
	private Boy next;
	
	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;
	}

	@Override
	public String toString() {
		return "Boy{" +
				"no=" + no +
				'}';
	}
}
 /**
     * 第一个结点
     */
    private Boy first = null;

    /**
     * 添加孩子结点nums个
     * @param nums
     */
    public void add(int nums){
        //验证nums是否合法
        if(nums <= 0){
            System.out.println("nums非法!");
            return;
        }

        //添加第一个结点
        if(first == null){
            Boy boy = new Boy(1);
            //单个结点构成环
            first = boy;
            boy.setNext(first);
        }

        //使用for循环添加另外nums-1和结点
        //因为first不能动,所以定义一个辅助结点,指向链表的最后一个结点
        Boy temp = first;
        while(temp.getNext() != first){
            temp = temp.getNext();
        }

        //循环添加
        for(int i = 2; i < nums + 1;i++){
            Boy boy = new Boy(i);
            temp.setNext(boy);//插入
            boy.setNext(first);//构成环
            temp = boy;//temp继续指向结尾
        }
    }

遍历环形链表

1.先让一个辅助指针cur,指向first结点

2.然后通过一个while循环遍历该链表,cur == first结束

 /**
     * 遍历链表
     */
    public void show(){
        Boy temp= first;
        while(true){
            System.out.println(temp.toString());
            if(temp.getNext() == first){
                break;
            }
            temp = temp.getNext();
        }
    }
3.2约瑟夫问题实现

根据用户的输入,生成一个小孩出圈的顺序

n = 5 , 即有5个人

k = 1, 从第一个人开始报数

m = 2, 数2下

在这里插入图片描述

实现

1.需要创建一个辅助指针helper,事先应该指向环形链表的最后这个节点

补充:小孩报数之前,先让first和helper移动k-1次,即开始报数位置

2.当小孩开始报数时,让first和helper同时移动m-1次

3.这是就可以将first指针指向小孩结点 出圈

first = first.next;

helper.next = first;

原来first指向的节点就没有任何引用了,就会被会少

出圈顺序: 2—4---1—5---3

代码实现

/**
     * 报数
     * @param start 报数起始位置
     * @param k 报到k的人出列
     * @param nums 总人数
     */
    public void numberOff(int start,int k,int nums){
        //1.验证k值和start以及nums值是否合法
        if(nums <= 0){
            System.out.println("nums值不合法!");
            return;
        }
        else if(k <=0 ){
            System.out.println("k值不合法!");
            return;
        }
        else if(start <= 0 || start > nums){
            System.out.println("start值不合法!");
            return;
        }

        //2.创建环形队列并打印
        add(nums);
        System.out.println("孩子站队编号如下:");
        show();
        System.out.println("********************************");

        //3.报数
        //首先要找到起始位置
        //先让temp指向尾结点,然后temp和first前进start-1次
        Boy temp = first;
        while(temp.getNext() != first){
            temp = temp.getNext();
        }
        //temp前进start-1次
        for(int i = 0; i < start-1;i++){
            temp = temp.getNext();
            first = first.getNext();
        }
        System.out.println("报数起始位置:"+start);

        //循环,每前进k-1次执行一次出列
        while(true){
            //只有一个结点时,报数出循环
            //因为temp在first的后面,所以二者相遇时表示只有一个结点
            if(first == temp){
                System.out.println("最后出列的编号是:"+temp.getNo());
                break;
            }
            //前进k-1步
            for(int i = 0; i < k -1; i++){
                first = first.getNext();
                temp = temp.getNext();
            }

            //执行出列
            System.out.println("孩子编号为"+temp.getNext().getNo()+"出列");
            temp.setNext(first.getNext());
            first = temp.getNext();
        }
    }

temp指向尾结点,然后temp和first前进start-1次
Boy temp = first;
while(temp.getNext() != first){
temp = temp.getNext();
}
//temp前进start-1次
for(int i = 0; i < start-1;i++){
temp = temp.getNext();
first = first.getNext();
}
System.out.println(“报数起始位置:”+start);

    //循环,每前进k-1次执行一次出列
    while(true){
        //只有一个结点时,报数出循环
        //因为temp在first的后面,所以二者相遇时表示只有一个结点
        if(first == temp){
            System.out.println("最后出列的编号是:"+temp.getNo());
            break;
        }
        //前进k-1步
        for(int i = 0; i < k -1; i++){
            first = first.getNext();
            temp = temp.getNext();
        }

        //执行出列
        System.out.println("孩子编号为"+temp.getNext().getNo()+"出列");
        temp.setNext(first.getNext());
        first = temp.getNext();
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Phil Jackson

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值