Java-自学笔记-9

本文介绍了链表的概念,并通过递归思想探讨了如何计算链表长度、获取链表内容以及进行链表元素的计算。示例代码展示了递归在解决链表问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java-自学-9

链表&应用(递归思想)

链表的引入

说链表之前,不得不先提到一个词——recursion,也就是所谓的递归。什么是递归呢,可以理解成套娃吧,在一个方法中调用自己大概就可以算作是递归了。那么链表和递归有什么关系呢?
我们先不说链表,我们看一段语句:

public class IntList_new{
	public int first;
	public IntList_new rest;

	/** construtor 1 */
	public IntList_new(int f, IntList_new r){
		first = f;
		rest = r;
	}
	
	public static void main(String[] args){
		IntList_new L = new IntList_new(200, null);
		L = new IntList_new(250, L);
		L = new IntList_new(350, L);
		L = new IntList_new(450, L);

首先,我们有一个构建器,定义了俩东西,一个是f,对应着整个类中的first;另一个是r,对应着整个类中的rest。可以看到first的类型是int这没什么值得注意的,而rest的类型是IntList_new这个类,也就是rest这一个单元也具有着IntList_new这一性质。
那么在mian函数中,我们构造了这样一串东西,首先,创建一个类型为IntList_new的实例L,其等于IntList_new(200, null);通过这个语句,我们做出来了这么个东西:
在这里插入图片描述
L是一个指针,指向实例IntList(200, null);平平无奇对吧?我们看下一个语句:

L = new IntList_new(250, L);

我们重新将指针L指向一个新的实例IntList_new(250, L);我们看到,该语句将L传递给了rest。所以我们新的指针,相当于将原有的实例给了新的实例中的rest,此时我们得到了一个扩充的表格,如下图所示:
在这里插入图片描述
注意,我们最开始定义的L,反而被推到了第二个
OK,那么接下来的代码全部执行下去我们最终得到的结果如下图所示:
在这里插入图片描述
L作为指针指向一个实例化的链表,而链表之所以称之为链表,就是它们如上图所示,首尾相连,形成一种独特的结构。

链表的应用(递归的思想)

OK,我们来创建一些函数来探究一下链表的性质吧

1. 我们想得到链表的长度size():

如果我们要得到链表的长度,有一种办法就是我们设定一个指针,指向链表中的第一项,之后一步一步后移指针(根据rest中存储的地址直接访问当前表格后与之链接的下一个表格)当最后指针指向null时,表示我们遍历完整了这个链表,最终就可以返回链表的长度。如果我们对上图中的链表使用size()函数,我们希望它返回的结果是4。
我们首先先用最为朴素的方式尝试解决这个问题:

public int size(){
	IntList_new p = this;     //create a pointer
	int totalsize = 0;        //create a counter to calculate the length
	while(p != null){          //p != null means after "this" box, we have another box, the link not end
		totalsize += 1;
		p = p.rest;       //指针的值复制到当前的rest中存储指向下一个链表的地址
	}
	return totalsize;    //finally return total length of this link.

但是我们能不能有一种更为简便的方法来设计这个函数呢?
答案是有的,那就是使用递归:
递归很有意思,也就是“我调用我自己”。但是调用自己的前提是当我们使用递归函数时,我们一定要有一个base case,什么意思呢,就是我们需要一个最终最终,函数执行的动作。光看文字可能没有感觉,看一段代码吧:

public int size(){
	/** base case */
	if(this.rest == null){
		return 1;
	}
	return 1 + this.rest.size();
}

这里面的base case就是if循环中的语句,而当rest != null时,我们的返回值要在1的基础上加上对size()函数递归多次的结果,而递归的终点就是this.rest == null

2. 我们想得到链表中存储的内容(IntList.first)

还用我们之前创建的链表:
在这里插入图片描述
我们假设一个函数get()具有这样的功能:当我们输入L.get(0)时,其将返回链表中第一个first值——450,get(1)即返回3500,以此类推
关于这个函数,也是一样可以运用递归和非递归实现:
非递归实现:

	public int get(int i){
		IntList_new p = this;   //create a pointer
		int get_number = 0;		//this is our return value
		if(i == 0){				//assume the input i = 0, we'll return the value L.first
			get_number = p.first;
		}
		while(i != 0){			//while, if i != 0, we have to change get_number to catch the next box after "this" box.also we change our pointer to p.rest.rest for the next step.
			get_number = p.rest.first;
			p.rest = p.rest.rest;
			i -= 1;				//we decrease i to open determine whether we begin the next loop
		}
		return get_number;     //as a result, we return the value we want
	}

我们使用递归的方式又该如何构建代码呢?
我们还是一样需要找到base case,我们想到最终肯定是要把i递减至0最后输出结果,所以我们的base case就是i = 0时响应应该是什么。如果i = 0我们应该是将链表中第一个表格中的first返回。在i != 0时,我们要返回的值要通过L.rest再使用get函数,但此时,get函数中的值不再是i,而应该是i - 1,因此递归的思路大致也就出来了:

public int get(int i){
		IntList_new p = this;
		int get_number = 0;
		/** base case */
		if (i == 0){
			return get_number = p.first;
		}
		return get_number = p.rest.get(i - 1);
}
3. 对链表做一些计算(老师留的两个练习)

也就是老师给的课堂练习:

  1. /** Returns an IntList identical to L, but with
    
    • each element incremented by x. L is not allowed
    • to change. */
      我们观察到:**L is not allowed to change.**说明我们一定要用实例,而不能用指针解决问题,因为如果是指针的话,L也会随之改变!因此我们需要采用new IntList_new()语句生成一个新的实例化。
      再分析第二个事,我们如果想用递归,那么base case是什么?
      自然会想到是L == null时,我们直接返回null
      那么 L != null时,我们应该怎么做呢?
      继续我们的实例化思想,我们要返回一个实例new IntList_new(),但是其中的参数如何设定?
      first项,我们希望使其增加x,所以第一项为L.first + x
      rest项,我们希望指向下一个表格(L != null时),并且对下一个表格使用相同的方法incrList(),但是其中的参数为L.rest以及x。所以代码如下:
    public static IntList_new incrList(IntList_new L, int x) {
        /* Your code here. */
        if (L == null){
            return null;
        }else{
            return new IntList_new(L.first + x, incrList(L.rest, x));
        }
           
    }
  1. /** Returns an IntList identical to L, but with
    
    • each element incremented by x. Not allowed to use
    • the ‘new’ keyword. */
      注意到Not allowed to use the ‘new’ keyword.,因此我们不得不使用指针,L也无法避免的会被改变。整体思路基本和1变化不大,代码如下:
    public static IntList_new dincrList(IntList_new L, int x) {
        /* Your code here. */
        IntList_new p = L;
        if(L == null){
            return null;
        }else{
            L.first = L.first + x;
            L.rest = dincrList(L.rest, x);
            return L;
        }
    }

这里我们注意到了,由于不能使用new,我们再else语句中只能分别对L.first、L.rest赋值,所以一定会对L有改变。
那么我们来看一下结果,首先我们再main函数中设定这四行代码:

    public static void main(String[] args) {
        IntList_new L = new IntList_new(5, null);
        L.rest = new IntList_new(7, null);
        L.rest.rest = new IntList_new(9, null);
        
		/** use incrList method */
        /** L was not changed */
        IntList_new Q = incrList(L, 20);
        System.out.println(Q.first);
        System.out.println(L.first);
        
		/** use incrList method */
        /** L was changed! */
        IntList_new M = dincrList(L, 20);
        System.out.println(L.first);
        System.out.println(M.first);

主要观察L的变化结果:
在这里插入图片描述
当我们调用incrList时,L未发生变化,为5
当我们调用dincrList时,L随M发生相同变化,为25

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值