数据结构(四)--链表和递归

(一)递归

递归:本质上,将原来的问题,转化为更小的问题
应用:数组求和
package cn.leetCode;

public class Sum {
	
	public static int sum(int[] arr){
		return sum(arr,0);
	}
	//计算arr[l..n)这个区间(从l到n-1)内所有数字的和,私有化
	private static int sum(int[] arr,int l){
		
		if(l == arr.length){
			//说明数组为空
			return 0;
		}
		return arr[l] + sum(arr,l+1);
	}
	
	public static void main(String[] args) {
		
		int[] nums = {1,2,3,4,5,6,7,8};
		System.out.println(sum(nums));
	}
}
控制台输出:36
分析:
//计算arr[l,n)范围里的数字和
public static int sum(int[] arr,int l){
	//求解最基本的问题
	if(l == arr.length){
	return 0;
	}
 //把原问题转化成更小的问题
return arr[l]+sum(arr,l+1);

}
递归(函数A中调用函数A与函数A中调用函数B没有本质区别,写程序时可以以第二种方式思考):
在进行递归调用时,就将调用的函数想成一个子函数,它具有一定的功能,至于具体功能如何实现,不用去细致追究
只要可以调用子函数的功能实现上层函数的功能就行
1.注意递归函数的"宏观"语义
2.递归函数就是一个函数,完成一个功能

(二)删除链表中某个节点

1.构建链表节点类
package cn.leetCode;
//此类为一个节点
public class ListNode {
	
	int val;
	ListNode next;
	ListNode(int x){
		val = x;
	}
	
	//链表节点的构造函数,构造以ListNode为头结点的链表
	//使用arr为参数,创建一个链表,当前的ListNode为链表的头结点
	public ListNode(int[] arr){
		if(arr == null || arr.length == 0){
			throw new IllegalArgumentException("arr can not be empty");
		}	
		this.val = arr[0];//当前节点
		ListNode cur = this;
		//将数组后面的元素挂到链表后面
		for(int i=1;i<arr.length;i++){
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}		
	}
	//返回以当前节点为头结点的链表信息字符串
	@Override
	public String toString(){
		StringBuilder res = new StringBuilder();
		//从自身开始进行循环
		ListNode cur = this;
		while(cur != null){
			res.append(cur.val + "->");
			cur = cur.next;
		}
		//添加null,代表链表的尾部
		res.append("NULL");
		return res.toString();
	}
		
}
(a)不存在虚拟头结点的情况删除链表中的节点
//不存在虚拟头结点的情况
package cn.leetCode;

public class Solution {
	//删除链表中value=val的节点
	public ListNode removeElements(ListNode head,int val){
		//在没有虚拟头结点的情况下,删除头结点需要进行单独处理(比较特殊)
		//若head节点的值为val,那么此时删除的节点就是头结点
		//要注意边界条件,比如传进来的是空链表的时候,头结点也为空
		//if(head!=null && head.val == val){
		//若删完之后新的头结点的值还为val,那就要继续删除了,所以要使用while循环
		while(head!=null && head.val == val){
			ListNode delNode = head;
			head = head.next;
			//是要删除的的节点指向空,脱离链表
			delNode.next = null;	
			//head = head.next;
		}
		//若整个链表全为要删除的节点组成,那么在进行上述操作后,链表为空,就没必要进行后面的操作了
		if(head == null){
			return null;
		}
		
		//接下来删除链表中间需要删除的节点(删除操作的关键是:找到待删除的节点的前一个节点)
		//运行到此处,若链表有头结点,那么一定不是需要删除的节点,而是在此之后的节点
		ListNode prev = head;
		//prev.next!= null意为没有到链表的尾节点
		while(prev.next!= null){
			if(prev.next.val == val){
				ListNode delNode = prev.next;
				prev.next = delNode.next;
				delNode.next = null;
				//prev.next = prev.next.next;
			}else{
				//不用删除,继续后续遍历寻找需要删除的节点
				prev = prev.next;
			}
			
		}
				return head;	
	}
	
	//制作测试用例,测试上面的代码
	public static void main(String[] args) {
		
		int[] nums = {1,2,6,3,4,5,6};
		ListNode head = new ListNode(nums);
		//打印以head为头结点的链表
		System.out.println(head);
		
		ListNode res = (new Solution()).removeElements(head, 6);
		System.out.println(res);
		
	}	
}
控制台输出结果:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL


(b)设置虚拟头结点删除链表中节点
//存在虚拟头结点的情况
package cn.leetCode;

public class Solution2 {
	public ListNode removeElements(ListNode head, int val){
	//创建一个虚拟的头结点指向head,我们永远都不会去访问虚拟头结点的值,将其设为某个值就行
	ListNode dummyHead = new ListNode(-1);
	dummyHead.next = head;
	
	//由于存在dummyHead,因此不需要像第一种方法一样对删除头结点操作进行特殊处理
	//从头开始遍历链表,查看是否有需要删除的节点
	ListNode prev = dummyHead;
	while(prev.next != null){
		if(prev.next.val == val){
			prev.next = prev.next.next;
		}else{
			prev = prev.next;
		}
		
	}
	return dummyHead.next;
}
	
	
	//制作测试用例,测试上面的代码
		public static void main(String[] args) {
			
			int[] nums = {1,2,6,3,4,5,6};
			ListNode head = new ListNode(nums);
			//打印以head为头结点的链表
			System.out.println(head);
			
			ListNode res = (new Solution2()).removeElements(head, 6);

			System.out.println(res);
			
		}
}

控制台输出:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL


注:链表和递归的特点:
链表天然的递归性
0->1->2->3->4->null
可看作
0->一个更短的链表(少了一个节点)

递归是有代价的:函数调用+系统栈空间

关于递归:近乎和链表相关的所有操作,都可以使用递归的形式完成
建议同学们对链表的增删改查进行递归实现

(c)使用递归的方法删除链表中的节点
package cn.leetCode;

import cn.data.LinkedList.Main;

//使用递归的方法删除链表中的元素
public class Solution3 {
	
	public ListNode removeElements(ListNode head, int val){
		//对于一个空链表,删除了值为val的节点,链表还是为空
		if(head == null){
			return null;
		}
		//使用递归删除头结点后面的链表中值为val的节点,返回删除元素后的链表
		ListNode res = removeElements(head.next,val);
		//此时会出现两种情形
		//(1)头结点是需要删除的节点,后面的链表中没有元素被删除
		//(2)头结点不删除,头结点后面跟随的链表中有元素需要被删除
		//在对某个链表中的元素进行删除的过程中,以上两种情形有可能是交叉进行的,但都符合递归的形式
		if(head.val == val){
			//情形(1)
			return res;
		}else{
			//如果头结点不是需要删除的节点,将头结点后面连接的链表传入函数
			//待删完需要删除的节点后,再将链表接在head节点后面
			head.next = res;
			return head;
		}
		/**
		 * 另一种写法:
		 * head.next = removeElements(head.next,val);
		 * if(head.val == val){
		 *     return head.next;
		 * }else{
		 *   return head;
		 * }
		 *三目运算符return head.val == val?head.next:head;
		 * */
	}	
      public static void main(String[] args){
    	  
    	  int[] nums = {1,2,6,3,4,5,6};
    	  ListNode head = new ListNode(nums);
    	  System.out.println(head);
    	  
    	  ListNode res = (new Solution3()).removeElements(head, 6);
    	System.out.println(res);  
      }
}
控制台输出:
1->2->6->3->4->5->6->NULL
1->2->3->4->5->NULL

(三)通过打印输出的方式显示递归调用的过程

package cn.leetCode;

public class showDiGui {
	//depth表示递归深度,函数每实现一次调用,递归深度加一
	//通过打印输出的方式来显示递归调用的过程
	public ListNode removeElements(ListNode head,int val,int depth){
		
		String depthString = generateDepthString(depth);
		System.out.print(depthString);
		System.out.println("Call: remove " + val + " in " + head);
		
		if(head == null){
			System.out.print(depthString);
			System.out.println("return: " + head);
			return head;
		}
		
		ListNode res = removeElements(head.next,val,depth+1);
		System.out.print(depthString);
		System.out.println("After remove " + val + ": " + res);
		
		ListNode ret;
		if(head.val == val){
			ret = res;
		}else{
			head.next = res;
			ret = head;
		}
		System.out.print(depthString);
		System.out.println("Return: " + ret);
		return ret;
	}	
	//深度字符串“--”,字符串越长,代表递归深度越深
		private String generateDepthString(int depth){
			StringBuilder res = new StringBuilder();
			for(int i = 0;i<depth;i++){
				
				res.append("--");
				
			}return res.toString();
		}
		
		public static void main(String[] args){
	    	  
	    	  int[] nums = {1,2,6,3,4,5,6};
	    	  ListNode head = new ListNode(nums);
	    	  System.out.println(head);
	    	  
	    	  ListNode res = (new showDiGui()).removeElements(head, 6,0);
	    	System.out.println(res);  
	      }
	

}
控制台输出:
1->2->6->3->4->5->6->NULL
Call: remove 6 in 1->2->6->3->4->5->6->NULL     从链表1->2->6->3->4->5->6->null中移除6,此时调用头结点后面的链表,后面类似
--Call: remove 6 in 2->6->3->4->5->6->NULL
----Call: remove 6 in 6->3->4->5->6->NULL
------Call: remove 6 in 3->4->5->6->NULL
--------Call: remove 6 in 4->5->6->NULL
----------Call: remove 6 in 5->6->NULL
------------Call: remove 6 in 6->NULL
--------------Call: remove 6 in null       从空链表中删除6
--------------return: null                  由于空链表的头节点的值不等于6,所以直接返回null
------------After remove 6: null
------------Return: null                  此时链表为6->null
----------After remove 6: null            头结点元素值为6,是需要删除的节点,故只剩下null
----------Return: 5->NULL                 再返回上一层调用现场,将后面的null接在5后面
--------After remove 6: 5->NULL
--------Return: 4->5->NULL                     5->6->null删除6后的链表(5->null)接在4后面,得到4->5->null
------After remove 6: 4->5->NULL              4->-5->null中没有要删除的元素,将其连接在3后面并返回
------Return: 3->4->5->NULL                 
----After remove 6: 3->4->5->NULL
----Return: 3->4->5->NULL
--After remove 6: 3->4->5->NULL          “--”深度一样代表调用和返回的是一起的
--Return: 2->3->4->5->NULL
After remove 6: 2->3->4->5->NULL
Return: 1->2->3->4->5->NULL
1->2->3->4->5->NULL



猜你喜欢

转载自blog.csdn.net/jaybillions/article/details/80978753