您的位置:首页 > 理论基础 > 数据结构算法

数据结构之Java单链表反转

2017-03-16 15:44 169 查看
        本文为数据结构基础,研究得不是很深。用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
4000
(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();
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息