2.链表
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。
按照面向对象的思想,我们可以设计一个类,来描述结点这个事物,用一个属性描述这个结点存储的元素,用来另外一个属性描述这个结点的下一个结点。
结点API设计:
2.1 单向链表
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。
2.1.1 单向链表API设计
2.1.2 单向链表代码实现
下面展示一些 单向链表代码实现
。
import java.util.Iterator;
/**
* @author shkstart
* @create 2021-08-10 14:45
*/
public class LinkList<T> implements Iterable<T>{
//成员变量
private Node head;//记录首结点
private int Num;//记录链表的长度
//Node不需要添加泛型<T>,会导致item的结果与LinkList的泛型结果出现紊乱
private class Node {
//存储元素
T item;
//指向下一个节点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
//添加单向链表的构造器,对首结点和链表长度进行初始化
public LinkList() {
this.head = new Node(null,null);
Num = 0;
}
//空置线性表
public void clear() {
head.next = null;
head.item = null;
Num = 0;
}
//判断线性表是否为空,是就返回true,反之false
public boolean isEmpty() {
return Num == 0;
}
//获取线性表中的元素个数
public int length() {
return Num;
}
//读取并返回线性表中的第i个元素的值
public T get(int i) {
//需要对链表中的元素进行遍历,引入Iterator接口
if (i < 0 || i > Num){
throw new IllegalArgumentException("输入索引有误!");
}
Node n = head;
for (int index = 0; index <= i; index++) {
n = n.next;
}
return n.item;
}
//往线性表中添加了一个元素
public void insert(T t){
Node n = head;
while (n.next != null){
n = n.next;
}
Node newNode = new Node(t,null);
n.next = newNode;
//链表长度加1
Num++;
}
//在线性表的第i个元素之前插入一个值为t的数据元素
public void insert(int i,T t){
if (i < 0 || i > Num){
throw new RuntimeException("插入的数据不合法");
}
Node prev = head;
for (int index = 0; index < i; index++) {
prev = prev.next;
}
//位置i的结点,现i的结点cur指向原有i的下一个结点
Node cur = prev.next;
//构建新的结点让新的结点指向位置i的结点
Node newNode = new Node(t, cur);
//让之前的结点指向新结点
prev.next = newNode;
//元素数量加1
Num++;
}
//删除并返回线性表中第i个元素
public T remove(int i){
if (i < 0 || i >= Num) {
throw new IllegalArgumentException("删除的元素不存在!");
}
Node prev = head;
for (int index = 0; index < i; index++) {
prev = prev.next;
}
//原i位置删除,原i-1指向i+1位置,现在i的位置cur,由原来的i+1替代
Node cur = prev.next;
prev.next = cur.next;
//元素个数减1
Num--;
return cur.item;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在返回-1
public int indexOf(T t){
Node prev = head;
for (int index = 0;prev.next != null; index++) {
prev = prev.next;
if (prev.item.equals(t)){
return index;
}
}
return -1;
}
//对接口中的iterator方法进行重写,对集合中的元素进行遍历
@Override
public Iterator<T> iterator() {
return new LinkIterator();
}
private class LinkIterator implements Iterator<T> {
//建立一个节点的成员变量
private Node n;
//使用构造器初始化首结点为head
public LinkIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public T next() {
n = n.next;
return n.item;
}
}
}
下面展示一些 单向链表代码测试
。
import Algorithm.LinkTable.LinkList;
/**
* @author shkstart
* @create 2021-08-10 14:45
*/
public class LinkListTest {
public static void main(String[] args) throws Exception{
LinkList<String> linkList = new LinkList<>();
linkList.insert(0,"张三");
linkList.insert(1,"李四");
linkList.insert(2,"王五");
linkList.insert(3,"小六");
linkList.insert("七宝");
//测试length方法
for (String s:linkList){
System.out.println(s);
}
System.out.println(linkList.length());
System.out.println("----------");
//检测get方法
String s = linkList.get(2);
System.out.println(s);
//测试remove方法
System.out.println("----------");
String removeResult = linkList.remove(2);
System.out.println(removeResult);
System.out.println(linkList.length());
System.out.println("----------");
for (String s1 : linkList){
System.out.println(s1);
}
System.out.println("----------");
System.out.println(linkList.indexOf("李四"));
System.out.println("----------");
linkList.clear();
System.out.println(linkList.isEmpty());
}
}
2.2 双向链表
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
按照面向对象的思想,我们需要设计一个类,来描述结点这个事物。由于结点是属于链表的,所以我们把结点类作为链表类的一个内部类来实现
2.2.1 结点API设计
2.2.2 双向链表API设计
2.2.3 双向链表代码实现
下面展示一些 双向链表代码实现
。
package LinkTable;
import java.util.Iterator;
/**
* @author shkstart
* @create 2021-08-11 19:29
*/
public class TwoWaysLink<T> implements Iterable<T>{
private Node head;//记录首节点
private Node last;//记录尾结点
private int Num;//记录双向链表长度
//初始化构造器
public TwoWaysLink() {
this.head = new Node(null,null,null);
this.last = null;
Num = 0;
}
//空置线性表
public void clear() {
head.next = null;
head.item = null;
head.pre = null;
last = null;
Num = 0;
}
//判断线性表是否为空
public boolean isEmpty() {
return Num == 0;
}
//获取线性表中的元素的个数
public int length() {
return Num;
}
//读取并返回线性表中第i个元素的值
public T get(int i){
if (i < 0 || i > Num){
throw new RuntimeException("获取的索引有误!");
}
Node cur = head.next;
//找到位置i的前i个元素,通过遍历实现
for (int index = 0; index < i; index++) {
cur = cur.next;
}
return cur.item;
}
//往线性表中添加一个元素
public void insert(T t){
if (isEmpty()){
//如果链表为空
//创建新的结点
Node newNode = new Node(t, head, null);
//让新结点成为尾结点
last = newNode;
//让头结点指向尾结点
head.next = last;
}else{
//链表如果不为空
Node oldLast = last;
//创建新的结点
Node newNode = new Node(t, oldLast, null);
//让当前的结尾指向新的结点
oldLast.next = newNode;
//让新结点成为尾结点
last = newNode;
}
//元素加1
Num++;
}
//在线性表中的第i个元素之前插入一个值为t的数据元素
public void insert(int i,T t){
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//找到原有i位置,现向后移一位
Node cur = pre.next;
//建立新i位置的新节点
Node newNode = new Node(t, pre, cur);
//让原i位置的前一个结点的下一个节点变为现i结点
pre.next = newNode;
//原i节点的前一个结点变为现i结点
cur.pre = newNode;
//元素个数加1
Num++;
}
//删除并返回线性表中的第i个元素
public T remove(int i){
//找到原i位置的结点
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//原i节点的是原i - 1的下一个结点
Node cur = pre.next;
//现i节点是原i结点的下一个结点
Node nextNode = cur.next;
//原i -1结点的下一个结点是现i结点
pre.next = nextNode;
//现i节点的上结点是原i - 1结点
nextNode.pre = pre;
//元素个数减1
Num--;
return cur.item;
}
//返回线性表中首次出现的指定的数据元素的位序号,若不存在,返回-1
public int indexOf(T t){
Node n = head;
for (int index = 0;n.next != null; index++) {
n = n.next;
if (n.item.equals(t)){
return index;
}
}
return - 1;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
@Override
public Iterator<T> iterator() {
return new TwoLinkIterator();
}
private class TwoLinkIterator implements Iterator<T> {
private Node n;
//初始化构造器,使得元素从head起始
public TwoLinkIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return this.n.next != null;
}
@Override
public T next() {
//head不算链表中的元素
n = n.next;
return n.item;
}
}
//创建Node的对象
class Node{
public Node pre;//指向上一个节点
public T item; //存储数据
public Node next;//指向下一个节点
public Node() {
}
public Node(T item, Node prev,Node next) {
this.item = item;
this.next = next;
this.pre = prev;
}
}
}
下面展示一些 双向链表代码功能测试
。
package LinkTableTest;
import LinkTable.TwoWaysLink;
/**
* @author shkstart
* @create 2021-08-11 19:29
*/
//创建双向列表对象
public class TwoWaysLinkTest {
public static void main(String[] args) {
//创建双向链表对象
TwoWaysLink<String> s1 = new TwoWaysLink<>();
//测试插入
s1.insert("姚明");
s1.insert("科比");
s1.insert("麦迪");
s1.insert(1,"詹姆斯");
for (String s : s1) {
System.out.println(s);
}
System.out.println("--------------------------------------");
System.out.println("第一个元素是:"+s1.getFirst());
System.out.println("最后一个元素是:"+s1.getLast());
System.out.println("------------------------------------------");
//测试获取
String getResult = s1.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
//测试删除
String removeResult = s1.remove(0);
System.out.println("删除的元素是:"+removeResult);
//测试清空
s1.clear();
System.out.println("清空后的线性表中的元素个数为:"+s1.length());
}
}
2.2.4 java中LinkedList实现
java中LinkedList集合也是使用双向链表实现,并提供了增删改查等相关方法
1.底层使用用双向链表实现;
2.结点类是否有三个域
2.3 链表的复杂度分析
get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表
2.4 链表反转(面试高频)
单链表的反转,是面试中的一个高频题目。
需求:
原链表中数据为:1->2->3>4
反转后链表中数据为:4->3->2->1
反转API:
使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
下面展示一些 内联代码片
。
package LinkTable;
import java.util.Iterator;
/**
* @author shkstart
* @create 2021-08-10 14:45
*/
public class LinkList<T> implements Iterable<T>{
//成员变量
private Node head;//记录首结点
private int Num;//记录链表的长度
//Node不需要添加泛型<T>,会导致item的结果与LinkList的泛型结果出现紊乱
private class Node {
//存储元素
T item;
//指向下一个节点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
//添加单向链表的构造器,对首结点和链表长度进行初始化
public LinkList() {
this.head = new Node(null,null);
Num = 0;
}
//空置线性表
public void clear() {
head.next = null;
head.item = null;
Num = 0;
}
//判断线性表是否为空,是就返回true,反之false
public boolean isEmpty() {
return Num == 0;
}
//获取线性表中的元素个数
public int length() {
return Num;
}
//对接口中的iterator方法进行重写,对集合中的元素进行遍历
@Override
public Iterator<T> iterator() {
return new LinkIterator();
}
private class LinkIterator implements Iterator<T> {
//建立一个节点的成员变量
private Node n;
//使用构造器初始化首结点为head
public LinkIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public T next() {
n = n.next;
return n.item;
}
}
//面试题一:计算链表中的节点个数
//不包含头结点,头结点只是个导引
public int getPoint(){
if (isEmpty()){
return 0;
}
int length = 0;
// Node cur = head.next;
// for (int index = 0; cur != null; index++) {
// length ++;
// cur = cur.next;
//
// }
//方式二:道理一样,这个计算了头节点,不算尾结点,上面反之
Node cur = head;
for (int index = 0; cur.next != null; index++) {
length ++;
cur = cur.next;
}
return length;
}
//计算链表中倒数第k个结点
public T getItem(int i){
if (isEmpty()) {
return null;
}
//方法一:遍历
Node cur = head.next;
for (int index = 0; index < Num - i; index++) {
cur = cur.next;
}
return cur.item;
//方法二:使用快慢指针
}
//对链表进行反转,使用递归
public void reverse(){
if(isEmpty()){
return;
}
reverse(head.next);
}
//反转方法一:利用递归
//反转指定的结点cur,并把反转的结点返回
//采用了方法的重载和递归
public Node reverse(Node cur) {
//开始进行反转,首先当现结点没有下一个结点时,即为尾结点,则head的下一个结点即为现结点(也是链表的尾结点)
if(cur.next == null){
head.next = cur;
return cur;
}
//原cur结点的前一结点成为现cur的下一个结点
Node pre = reverse(cur.next);
//原前一个结点的下一个结点就是结点cur
pre.next = cur;
//指定结点重置指向null
cur.next = null;
return cur;
}
//反转方法二:利用指针
public void reverse1(){
if (head.next == null || head.next.next == null){
return;
}
//定义一个辅助的指针变量
Node cur = head.next;
//初始化指针下一个指向为空
Node next = null;
//初始化反转过后的链表头指针.原链表尾指针
Node reverseHead = new Node(null,null);
//对链表进行遍历,将链表元素逐一反转添加到反转链表中
for (int index = 0; cur != null; index++) {
//先暂时保存当前节点的下一节点,用于后面cur进行迭代
next = cur.next;
//以下两句不能混淆,否则会出现死循环
//将cur的下一个结点指向新链表的最前端
cur.next = reverseHead.next;
//将cur连接到反转后的链表上
reverseHead.next = cur;
//将cur后移进行迭代
cur = next;
}
//原头结点转变为现头结点
head.next = reverseHead.next;
}
}
下面展示一些 内联代码片
。
public class LinkListTest {
public static void main(String[] args) throws Exception{
LinkList<String> linkList = new LinkList<>();
linkList.insert(0,"张三");
linkList.insert(1,"李四");
linkList.insert(2,"王五");
linkList.insert(3,"小六");
linkList.insert(4,"小七");
linkList.insert(5,"小八");
linkList.insert(6,"小九");
linkList.insert("七宝");
//测试length方法
for (String s:linkList){
System.out.println(s);
}
System.out.println("********");
//测试节点个数
int length = linkList.getPoint();
System.out.println(length);
System.out.println("********");
String item = linkList.getItem(2);
System.out.println(item);
//测试链表反转的方法
System.out.println("----------");
linkList.reverse();
for (String s1: linkList) {
System.out.println(s1);
}
//测试链表反转指针方法
System.out.println("*********");
linkList.reverse1();
for(String s2 :linkList){
System.out.println(s2);
}
}
}
2.5 快慢指针
快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。
2.5.1 中间值问题
需求:
请完善测试类Test中的getMid方法,可以找出链表的中间元素值并返回。利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。
下面展示一些 快慢指针解决中间值问题
。
public class FastSlow {
public static void main(String[] args) {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//Node<String> eight = new Node<String>("hh", null);
//实现结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//seven.next = eight;
//查找中间值
String mid = getMidValue(first);
System.out.println("中间值为:"+mid);
}
//利用快慢指针找到链表中的中间值
public static String getMidValue(Node<String> first){
Node<String> fast = first;
Node<String> slow = first;
while (fast != null && fast.next!= null){
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
}
2.5.2 单向链表是否有环问题
需求:
请完善测试类Test中的isCircle方法,返回链表中是否有环。
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇(这是数学的统计知识),只要相遇那么就说明有环。
下面展示一些 单向链表是否有环问题
。
//单向链表是否有环问题
public static boolean isCircle(Node<String> first){
Node<String> fast = first;
Node<String> slow = first;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
2.5.3 有环链表入口问题
请完善Test类中的getEntrance方法,查找有环链表中环的入口结点。
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识(就不在此多说了)。
下面展示一些 /查找单向链表中的闭环入口
。
//查找单向链表中的闭环入口
public static Node getEntrance(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
temp = first;
continue;//结束循环执行下面的代码
}
if (temp != null){
temp = temp.next;
if (temp.equals(slow)){
return temp;
}
}
}
return null;
}
下面展示一些 快慢指针的问题合集
。
package LinkTable;
/**
* @author shkstart
* @create 2021-08-12 16:45
*/
public class FastSlow {
public static void main(String[] args) {
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//Node<String> eight = new Node<String>("hh", null);
//实现结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//是否有环的判断条件
seven.next = third;
//seven.next = eight;
// //查找中间值
// String mid = getMidValue(first);
// System.out.println("中间值为:"+mid);
//判断是否有环
boolean isCircle = isCircle(first);
System.out.println("是否有环:" + isCircle);
Node<String> entrance = getEntrance(first);
System.out.println("闭环入口是:" + entrance.item);
}
// //利用快慢指针找到链表中的中间值
// public static String getMidValue(Node<String> first){
//
// Node<String> fast = first;
// Node<String> slow = first;
// while (fast != null && fast.next!= null){
// fast = fast.next.next;
// slow = slow.next;
// }
// return slow.item;
// }
//单向链表是否有环问题
public static boolean isCircle(Node<String> first){
Node<String> fast = first;
Node<String> slow = first;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
//查找单向链表中的闭环入口
public static Node getEntrance(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
Node<String> temp = null;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
temp = first;
continue;//结束循环执行下面的代码
}
if (temp != null){
temp = temp.next;
if (temp.equals(slow)){
return temp;
}
}
}
return null;
}
}
2.6 循环链表
循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。
2.7 约瑟夫问题(面试高频)
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
解题思路:
1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
2.使用计数器count,记录当前报数的值;
3.遍历链表,每循环一次,count++;
4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
下面展示一些 约瑟夫循环问题代码实现
。
package LinkTable;
/**
* @author shkstart
* @create 2021-08-12 19:53
*/
//约瑟夫的循环问题
public class JosephList {
public static void main(String[] args) {
//1.构建循环列表,包含41各结点,分别存储1~41之间的值
//记录第一个结点
Node<Integer> first = null;
//记录前一个结点
Node<Integer> pre = null;
for (int i = 1; i <= 41 ; i++) {
//如果是从第一个结点开始
if (i == 1) {
first = new Node(i, null);
//考虑循环的使用
pre = first;
continue;
}
//如果不是第一个结点开始
Node<Integer> newNode = new Node<>(i, null);
//前一个结点的下一个结点就是新结点
pre.next = newNode;
//循环刷新
pre = newNode;
//如果是最后一个结点,就要把尾结点与头结点连接起来,形成循环链表
if (i == 41) {
pre.next = first;
}
}
//2.需要count计数器,模拟报数
int count = 0;
//3.遍历循环列表
//记录每次遍历拿到的结点,默认从首结点开始
Node<Integer> n = first;
//记录当前节点的上一个结点
Node<Integer> before = null;
while ( n != n.next){
count++;
//判断当前报数是否为3
if (count == 3) {
//如果为3,将当前结点删除,前一结点指向当前结点的下一结点
before.next = n.next;
System.out.print(n.item + ",");
count = 0;
n = n.next;
}else {
//若果不是3,让before变为当前结点,当前结点后移
before = n;
n = n.next;
}
}
//打印最后一个元素
System.out.println("最后一个元素是:" + n.item);
}
}