本文为数据结构基础,研究得不是很深。用Java实现单链表的反转,虽然本文研究得不是很深,但是因为是数据结构,所以必须是在对Java内存比较清楚的情况下才能真正的搞懂吃透,如果对Java内存不够清楚,那最多只能学形而不能学其内在。
首先我们要搞清楚链表是啥玩意儿?先看看定义:
讲链表之前我们先说说Java内存的分配情况:我们new对象的时候,会在java堆中为对象分配内存,当我们调用方法的时候,会将方法加载到方法区,在方法区保存了加载类的信息,常量,静态变量等等。搞明白这个我们再来讲链表。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 这个定义估计太过书面化,对初学者来说,不好理解,其实简单点说就是酱紫的。我们创建了一个类Node,这个类当中有两个变量,一个data用于存储数据,还有一个Node类型的变量next用于存储另一个对象在java堆中的地址。然后new了很多个Node类的对象,我们通过setNext方法将第二个对象node2的地址给node1保存起来,同样的将第三个对象node3的地址交给node2保存起来。通过这种方式,我们就将很多个对象连成串了,形成了一种链状。这就是链表了。
这儿着重声明:在Java中,没有地址一说,只有hashCode。其实hashCode就是通过算法,将每一个对象的地址算成一个code转成一个特有的字符串。当我们没有复写Object类的toString方法的时候,该类的对象调用toString方法,打印出来,或者不调用toString方法,直接打印该类的对象,其实就是将hashCode打印出来了。这个hashCode就相当于是内存了。
好了搞懂了这些我们就可以来看看实例了:
节点Node类,其实节点就是我们的对象,每一个节点就是一个对象。
/** * 其实一个节点就对应我们java中的一个对象,我们在分析的时候需要注意除了next要存储一个地址外,自己也是对象自己也有地址 * Created by PICO-USER dragon on 2017/3/16. */ public class Node { //数据域存储数据 private int data; //指针域用于存储下一个节点的地址 private Node next; public Node(int data) { this.data = data; } public int getData() { return data; } public void setData(int data) { this.data = data; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } }
反转链表的方法,当传入的节点为null的时候,直接染回null。如果只有一个节点,头尾都是它,直接返回该节点
public static Node reverseList(Node head) { if (head == null) { return null; } if (head.getNext() == null) { return head; } //previous上一个节点 Node preNode = null; //current节点当前节点,并让它指向传进来的对象所在地址(是保存该对象的地址,不是它的next值) Node curNode = head; //next节点下一个节点 Node nextNode = null; while (curNode != null) { //让next节点指向后一个节点所在地址,并改变新地址的值(包括data,next) nextNode = curNode.getNext(); if (nextNode != null) { System.out.print("nextNode data :" + nextNode.getData() + " next :" + nextNode.getNext() + " " + nextNode + "\n"); } //将current节点存储的地址(也就是next)的值改为preNode节点所指向的地址(这样就把指向箭头反转了)这儿有个误区 //注意:是将preNode指向的地址给curNode的next,不是把preNode的next给它。 curNode.setNext(preNode); if (curNode != null) { System.out.print("curNode data :" + curNode.getData() + " next :" + curNode.getNext() + " " + curNode + "\n"); } //让previous节点指向的地址向后移动一个单位,并改变新地址的值(包括data,next) preNode = curNode; if (preNode != null) { System.out.print("preNode data :" + preNode.getData() + " next :" + preNode.getNext() + " " + preNode + "\n"); } //让current节点的索引向后移动一个单位,并改变新地址的值包括(data,next) curNode = nextNode; if (curNode != null) { System.out.print("curNode data :" + curNode.getData() + " next :" + curNode.getNext() + " " + curNode + "\n"); } System.out.print("-----------------------\n"); } return preNode; }
public class MainRun { public static void main(String[] arg0) { //创建链表的节点,创建了三个对象,那就是三个节点 Node node0 = new Node(1); Node node1 = new Node(2); Node node2 = new Node(3); //将这些节点,串连起来形成链表 node0.setNext(node1); node1.setNext(node2); //链表的头结点代表了该链表,因为头结点能找到第二个,第二个能找到第三个,依次找下去,全都找到了 Node head1 = node0; //先打印反转之前的链表的值,将hashCode一起打印出来,方便去每一行代码都对谁做了什么操作 while (head1 != null) { System.out.print("data :" + head1.getData() + " next :" + head1.getNext() + " " + head1.toString() + "\n"); head1 = head1.getNext(); } System.out.print("---++++++-----\n"); //注意了,我们是从头开始反转,所以这儿不能用head1,因为head1在上面的while循环中已经成为最后一个节点了 Node oldHead = node0; Node newHead = reverseList(oldHead); //打印反转后的节点 while (newHead != null) { System.out.print("data :" + newHead.getData() + " next :" + newHead.getNext() + " " + newHead + "\n"); newHead = newHead.getNext(); } }
看看运行结果:
下面给出分析结果,我自己用笔画的,网友可以根据这个分析步骤,跟着while循环的代码一句一句往下分析,每一行代码运行之后改动的值是什么?多看看,多分析分析就通了。
第二种方法:递归调用实现单链表反转
/** * 因为递归的思想是直接更改当前节点的next的值为前一个节点所在的地址,所以需要用到两个参数,当前节点和前一个节点, * 这儿给外面用就只给一个方法,我们再自己封一个两个参数的方法。 * * @param head * @return */public static Node reverseList2(Node head) { return reverseListRecursively( null, head);} /** * 递归调用实现的思想很简单,就是直接改变 curNode 的 next 的值。原本是指向后面一个节点的,现在需要改为前一个节点。 * 所以参与算法的人只有当前节点和当前的前一个节点,而下一个节点的作用只是用于让需要更换 next 的对象往后面移动 * * @param preNode * @param curNode * @return */ public static Node reverseListRecursively(Node preNode, Node curNode) { if (curNode == null) { return null; } if (curNode.getNext() == null) { curNode.setNext(preNode); return curNode; } // 将 curNode 中保存的地址改成前一个节点所在的地址 curNode.setNext(preNode); // 如果当前节点有下一个节点就将该节点拿出来 Node nextNode = curNode.getNext(); // 递归调用本方法,相当于让 preNode 和 curNode 指向的地址都向后移动一个单位,直到所有的节点都将自己保存的地址改为前一个为止 Node newNode = reverseListRecursively(curNode, nextNode); return newNode;}
public static void main(String[] arg0) { //创建链表的节点,创建了三个对象,那就是三个节点 Node node0 = new Node(1); Node node1 = new Node(2); Node node2 = new Node(3); //将这些节点,串连起来形成链表 node0.setNext(node1); node1.setNext(node2); //链表的头结点代表了该链表,因为头结点能找到第二个,第二个能找到第三个,依次找下去,全都找到了 Node head1 = node0; //先打印反转之前的链表的值,将hashCode一起打印出来,方便去每一行代码都对谁做了什么操作 while (head1 != null) { System.out.print("data :" + head1.getData() + " next :" + head1.getNext() + " " + head1.toString() + "\n"); head1 = head1.getNext(); } System.out.print("---++++++-----\n"); //注意了,我们是从头开始反转,所以这儿不能用head1,因为head1在上面的while循环中已经成为最后一个节点了 Node oldHead = node0; Node newHead = reverseList2(oldHead); //打印反转后的节点 while (newHead != null) { System.out.print("data :" + newHead.getData() + " next :" + newHead.getNext() + " " + newHead + "\n"); newHead = newHead.getNext(); } }