数据结构与算法--代码鲁棒性案例分析

代码鲁棒性

  • 鲁棒是robust的音译,就是健壮性。指程序能够判断输入是否符合规范,对不合要求的输入能够给出合理的结果。
  • 容错性是鲁棒的一个重要体现。不鲁棒的代码发生异常的时候,会出现不可预测的异常,或者程序奔溃。
  • 由于鲁棒性非常重要,因此我们在写代码的时候,必须进行防御性编程,这个必须成为我们编程的一种习惯,编码过程中应该能够预见可能出现的问题,并适当处理。

最容易出错的双指针操作

  • 链表中倒数第K个节点
  • 题目:输入一个链表,输出链表中倒数第k个结点。例如,链表依次是1,2,3,4,5,6,倒数第三个就是4。
  • 我们依然用之前我们在讲解链表实现时候的链表对象定义
分析
  • 此处题目要求的应该是单向链表,因为双向链表没有复杂度可言。为了得到倒数第K个节点,并且不能从尾部遍历,同样的案例从尾部到头打印单向链表,我们在之前的文章(数据结构与算法–链表实现以及应用)中已经有详细的分析,此处同样也可以利用这种方法。
  • 以上方法中借助其他数据接口,栈实现,时间复杂度是O(N+k),空间复杂度O(2n)。
  • 应该还有更加高效率的算法,我们还是从头遍历链表,假设有n个节点,那么倒数第k个节点从头开始数是第n-k+1 个节点
  • 如果我们能得到节点个数n大小,那么直接从头遍历n-k+1个就得到倒数第k个了 。
  • 我们用双指针实现,利用两个指针中间步数的差值来得到倒数第k个,比如,我们需要n-k+1的差距,也就是n-(k-1)
  • 当指针A 走到末尾的时候,指针B需要走k-1 的距离,那么此时B指针指向的就是倒数第k个
  • 例如倒数第2 个,那么B就比A 少走了2-1 = 1 步骤,就是指向倒数第二个了。
  • 如下图:

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

在这里插入图片描述

  • 如上图一中P1走两步,P2 则指向头结点
  • 接着继续两个指针P1, P2,一起向前走,
  • 当P1走到链表尾,则,P2,正好走到倒数第三位置。
  • 如下diam实现:
   /**** 查找单向链表倒数第k个节点*/public static ListNode findLastKNode(ListNode head, int k){if(head == null || k == 0){return new ListNode(-1);}ListNode beforeNode = head;ListNode afterNode = head;for (int i = 0; i < k - 1; i++) {if(beforeNode.getNext() != null){beforeNode = beforeNode.getNext();}else {return new ListNode(-1);}}//确保有 k 个节点if(beforeNode.getNext() == null){return new ListNode(-1);}while (beforeNode.getNext() != null){beforeNode = beforeNode.getNext();afterNode = afterNode.getNext();}return afterNode;}
鲁棒性分析
  • 双指针方法中需要注意的点还是挺多的:
    • 当我们输入head 结点为null时候回,由于代码会范问空指针内存,次数程序会奔溃
    • 输入head为头肩底的链表节点总数少于k个,此时,我们需要先走k-1 步骤,如果正好k-1个节点,那么之后的步骤会抛出NPL,如果少于k-1个节点,则此时就已经npl
    • 输入参数k为0 的时候,此时 k-1= -1,非法数值,-1 二进制位符号位1 此时会读成数据位,此时数据变为4294967295,此时for循环将会超过执行次数。
相关问题
  • 问题一:求单向链表中介节点,奇数个返回中间节点,偶数个返回中间两个任意一个,同样双指针A,B, A每个循环走一步,B每个循环走两步,B到末尾,则A就在中间节点
  • 问题二:求解单向链表是否环形链表,同样双指针A,B,A走一步,B走两步,如果到最后B追上了A 则环形,如果B到最后null,则不是环形

最容易死循环的链表问题

  • 题目:定义一个函数,输入链表头结点,反转并输出反转后的链表头结点。
分析
  • 我们依然用 之前链表的实现文章中定义的链表节点如下:
public class ListNode implements Comparable<ListNode> {private Integer value;private ListNode next;
......
}
  • 链表反转涉及到每个节点的指针操作,非常容易出现死循环问题,为了正确理解整个过程,我们应该首先借助图形来直观的分析。如下:

在这里插入图片描述

  • 我一开始还是想到双指正方法,第一步骤分别指向钱两个节点,并且改变本节点的指针指向,我们此时需要知道的是,本节点信息,上一个节点信息,这里都符合,得到下一图步骤。

在这里插入图片描述

  • 但是此时我们无法循环到下一个节点3, 因此逻辑无法成立,我们需要借助第三个指针,P3,如下图

在这里插入图片描述

  • 如上,我们可以得到本节点信息,上一个节点信息,下一个节点信息,在经过p2 指针的转向后,我们无法通过P2.next得到下一个节点,因此我们循环时候,需要做如下调整 P2 = P1, P1 = P3, P3=P3.next,然后接着操作P1 节点指针,继续循环到最后p3.Next为null为止,如下图最终状态

在这里插入图片描述

  • 如上最终状态,因为我们每次只修改了P1 节点的指针指向,所以循环结束后,还有最后节点没有处理,我们需要在循环结束后处理。

  • 如上分析,我有如下实现:

  /*** 反转单向链表* */public static ListNode reverOverListNode(ListNode head){if(head == null){return head;}ListNode before = head;ListNode middle = head;ListNode after = head;if(middle.getNext() == null){return head;}middle = middle.getNext();if(middle.getNext() == null){middle.setNext(before);head.setNext(null);return middle;}after = middle.getNext();while (after.getNext() != null){middle.setNext(before);before = middle;middle = after;after = after.getNext();}//处理最后两个节点的指针head.setNext(null);middle.setNext(before);after.setNext(middle);return after;}
鲁棒性分析
  • 在指针操作时候,最容易出现的三个问题:
    • 输入的链表头指针是努力,或者整个链表只有一个节点,必须在前三个步骤中判断
    • 反转后出现环形链表,在如上处理过程中,最容易忽略的头节点指针指向null,导致环形链表
    • 链表断裂, 最后一个步骤没有对最后的节点进行指针指向操作,导致最后一个节点处断裂。

合并两个排序的链表

  • 题目:输入两个递增的链表,合并这两个链表并使得新的链表中节点任然按原有顺序有序。如下图:

  • 在这里插入图片描述

  • 链表一,链表而是两个递增链表,合并两个链表得到链表三

  • 这个问题我们需要注意的和上一个问题类似,还是链表断裂问题,还有环形链表问题,因为需要同时操作两个链表的指针。

  • 我们有如下分析:

    • 将一二个链表看出需要处理的一个组,比较第一个元素,得到小的一个,剔除当成新链表的head节点:
      在这里插入图片描述

在这里插入图片描述

  • 将1 节点剔除后,将剩下的链表1 ,链表2 看成是一个整体,仍然比较第一个节点的大小,继续如上步骤

在这里插入图片描述

  • 继续同样的逻辑合并剩余的节点,这是典型的递归流程,我们可以定义递归函数完成合并过程。.
  • 如上分析有如下代码
 /*** 递归合并两个顺序链表* */public static ListNode mixTwoSortList(ListNode sortOne, ListNode sortTwo){if(sortOne == null && sortTwo == null){return new ListNode();}if(sortOne == null){return sortTwo;}if(sortTwo == null){return sortOne;}ListNode mergeHead = null;if(sortOne.getValue() < sortTwo.getValue()){mergeHead = sortOne;mergeHead.setNext(mixTwoSortList(sortOne.getNext(), sortTwo));}else {mergeHead = sortTwo;mergeHead.setNext(mixTwoSortList(sortTwo.getNext(), sortOne));}return mergeHead;}
鲁棒性分析
  • 首先还是空指针问题,一旦输入空链表立刻npl,因此我们应该对空链表单独处理

更加复杂的指针操作案例树的子结构

  • 题目:输入两颗二叉树A,B,判断B是不是A树的子结构。二叉树的定义我们用之前章节中讲解的二叉树实现原理中定义的树节点来实现。
/*** 二叉树节点对象定义** @author liaojiamin* @Date:Created in 15:24 2020/12/11*/
public class BinaryNode {private Object element;private BinaryNode left;private BinaryNode right;
......
}
  • 例如有如下两棵二叉树,A中有一部分子树结构和B是一直的,因此B是A的子结构:
    在这里插入图片描述

  • 依据之前二叉树实现原理 的分析,树操作中指针比值链表更加复杂,与树相关的问题我们通常都会用递归去解决。

  • 如上题中查找A中包含B,可分为两步:

    • 第一步在A树中查找B的根节点一样的节点R,如果找到,执行下一步
    • 第二步,判断A中以R为根节点的子树与B树的结构是否一致,
  • 用如上图来分析:

    • 首先在A中找 8 这个节点,发现A的根节点就是8 ,我们将A以8节点为根节点的树,与B节点比较
    • 将8 的左子树看成完整的树,与B中左子树 比较,还是按第一步骤逻辑 发现不同则不需第三部
    • 如果相同则需要,将8 的右子树看出完整的树,与B中右子树比较,还是按第一步骤逻辑。
    • 如果以上都不同,则回到第一步,将A的左子树看出是一个完整的树与B的根节点比较,
    • 依次逻辑遍历整个A树,直到找到B一样结构或者遍历到叶子节点为止。
  • 依据如上分析,我们有如下递归实现,其中某些函数是用之前文章 二叉树实现原理的某些功能:

/***  判断 A树中是否包含B树*  @author liaojiamin* @Date:Created in 16:43 2021/3/30*/
public class BinaryNodeComparable {/*** 递归方式遍历树* */public static boolean isComparable(BinaryNode tree1, BinaryNode tree2){if(tree1 == null || tree2 == null){return false;}boolean result = false;if(tree1.compareTo(tree2.getElement()) == 0){result = tree1HaveTree2(tree1, tree2);}if(!result){result = isComparable(tree1.getLeft(), tree2);}if(!result){result = isComparable(tree1.getRight(), tree2);}return result;}/*** 依次比较跟,左,右节点* */public static boolean tree1HaveTree2(BinaryNode tree1, BinaryNode tree2){//t2 遍历完了并且都一致,则存在包含if(tree2 == null){return true;}//t2 不为空,t1 为空 ,则不存在包含if(tree1 == null){return false;}if(tree1.compareTo(tree2.getElement()) != 0){return false;}return tree1HaveTree2(tree1.getLeft(), tree2.getLeft())&& tree1HaveTree2(tree1.getRight(), tree2.getRight());}public static void main(String[] args) {BinaryNode node1 = new BinaryNode(null, null, null);BinarySearchTree tree1 = new BinarySearchTree();Random random = new Random();for (int i = 0; i < 20; i++) {node1 = tree1.insert(Integer.valueOf(i), node1);}tree1.printTree(node1);System.out.println("-------------");BinaryNode node2 = new BinaryNode(null, null, null);BinarySearchTree tree2 = new BinarySearchTree();for (int i = 0; i < 3; i++) {node2 = tree2.insert(Integer.valueOf(i), node2);}tree2.printTree(node2);System.out.println(isComparable(node1, node2));}
}
  • 以上考察二叉树遍历算法的理解 以及递归的能力
  • 考察代码的鲁棒性,每个题型都有大量的指针操作,稍不注意就会有npl奔溃。我们应该在程序开始的时候采用防御性编程的方式
  • 每次范问指针地址之前都需要考虑这个指针是否有可能是null

上一篇:数据结构与算法–代码完整性案例分析
下一篇:数据结构与算法–解决问题的方法- 二叉树的的镜像

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310420.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【半译】两个gRPC的C#库:grpc-dotnet vs Grpc.Core

grpc-dotnet 是在2019年随着 .NET Core 3.0 一起发布的一个gPRC官方库。在ASP.NET Core 的 gRPC项目模板里面就使用了这个库。.NET Core 3.0之前难道不可以使用gRPC吗&#xff1f;目前&#xff0c;gRPC 在.NET上有两种官方实现&#xff1a;Grpc.Core&#xff1a;这个是原来的gR…

[Java基础]String对象的特点(易错点)

String对象的特点: 1.通过new创建的字符串对象&#xff0c;每一次new都会申请一个内存空间&#xff0c;虽然内容相同&#xff0c;但是地址值不同。 2.以""方式给出的字符串&#xff0c;只要字符串相同(顺序和大小写)&#xff0c;无论在程序代码中出现几次&#xff0…

数据结构与算法--解决问题的方法- 二叉树的的镜像

解决问题的思路 工作中遇到的问题可能用到的数据结构由很多&#xff0c;并且各种数据结构都不简单&#xff0c;我们不可能光凭借想象就能得到问题的解法&#xff0c;因此画图是在家具问题过程中用来帮助自己分析&#xff0c;推理的常用手段。很多问题比较抽象&#xff0c;不容…

使用dnSpy调试asp.net core源码

环境&#xff1a;window 10vs2019 16.5.1dnspy v6.1.4.netcore3.1参考&#xff1a;.Net反编译技术详解及4个反编译工具介绍一、关于dnSpydnSpy是近几年的新秀&#xff0c;功能远比ILSpy强大&#xff0c;甩.net Reflector几条街&#xff0c;被汉化、破解、逆向方面的人才奉为神器…

数据结构与算法--解决问题的方法-顺时针打印矩阵

顺时针打印矩阵 题目输入一个矩阵&#xff0c;按照从外向里顺时针的顺序依次打印每一个数字。例如下案例&#xff1a; 如上图矩阵&#xff0c;顺时针打印&#xff1a;1,2,3,4,8,12,16,15,14,13,9,5,6,7,1,10 以上问题看起来比较复杂&#xff0c;但是又没有涉及到复杂的数据结…

.NET与鲲鹏共展翅,昇腾九万里(二)

在上一篇文章 .NET与鲲鹏共展翅&#xff0c;昇腾九万里&#xff08;一&#xff09;中&#xff0c;我们通过在鲲鹏架构的Euler系统上跑Docker的方式把dotnet core 跑起来了&#xff0c;有读者反馈说“还是走docker喽&#xff0c;你这个标题应该改成鲲鹏和docker两条鲸鱼的故事”…

[Java基础]final和static修饰符

final: final修饰局部变量时: static&#xff1a; static访问特点:

优化委托的 DynamicInvoke

优化委托的 DynamicInvokeIntro委托方法里有一个 DynamicInvoke 的方法&#xff0c;可以在不清楚委托实际类型的情况下执行委托方法&#xff0c;但是用 DynamicInvoke 去执行的话会比直接用 Invoke 的方法会慢上很多&#xff0c;差了两个数量级&#xff0c;所以在知道委托类型的…

数据结构与算法-- 广度优先打印二叉树

广度优先打印二叉树 题目&#xff1a;从上往下打印出二叉树的每一个节点&#xff0c;同一层节点按照从左到右顺序打印&#xff0c;例如下图中二叉树&#xff0c;依次打印出是8,6,10,5,7,9,11 如上题中二叉树的节点定义我们用之前文章 二叉树实现原理中定义的节点结构。此处提议…

实现一个基于动态代理的 AOP

实现一个基于动态代理的 AOPIntro上次看基于动态代理的 AOP 框架实现&#xff0c;立了一个 Flag&#xff0c; 自己写一个简单的 AOP 实现示例&#xff0c;今天过来填坑了目前的实现是基于 Emit 来做的&#xff0c;后面有时间再写一个基于 Roslyn 来实现的示例效果演示演示代码&…

数据结构与算法-- 二叉树后续遍历序列校验

二叉树后续遍历序列校验 题目&#xff1a;输入一个整数数组&#xff0c;判断改数组是否是某个二叉搜索树的后续遍历结果&#xff0c;如果是返回true否则false&#xff0c;假设输入数组的任意两个数字不相同。 例如输入{5,7,6,9,11,10,8}则返回true&#xff0c;因为这个整数序列…

程序员过关斩将-- 工作好多年可能还未真正了解接口和抽象类

点击上方“蓝字”关注我们菜菜哥&#xff0c;我偷偷出去面试了&#xff0c;然后面试官让我回来等消息那你可能挂了呀&#xff0c;有什么问题没回答上来吗确实有一个问题回答的不太好哎&#xff0c;就是接口和抽象类这个确实是面试官比较爱问的题目之一那能不能说说接口和抽象类…

数据结构与索引-- mysql InnoDB存储引擎索引

索引与算法 索引是我们在应用开发过程中程序数据可开发的一个重要助力。也是一个重要的研究方向&#xff0c;索引太多&#xff0c;应用的性能可能受到影响&#xff0c;如果索引太少&#xff0c;对查询性能又会有制约。我们需要找到一个合适的平衡点&#xff0c;这个对性能至关…

扫盲消息队列 | 消息中间件 | Kafka

先吐槽我真的写技术文章写到怀疑人生&#xff0c;我翻看历史发文记录&#xff0c;只要我一本正经的写的技术文章&#xff0c;都没人看&#xff0c;但是&#xff01;一发闲扯淡的内容&#xff0c;阅读量肯定是技术文的好几倍&#xff08;读者爸爸们别这么搞嘛&#xff09;这说明…

数据结构与索引-- B+树索引

B树索引 上一节中我们讨论的都是B树的数据结构的由来以及他的一些操作&#xff0c;B树索引在本质就是B树在数据库中的一个实现&#xff0c;但是B索引在数据库中有一个特点就是他的高扇出性&#xff0c;因此在数据库中&#xff0c;B树的高度一般是2~3层&#xff0c;也就是对于查…