public Node findNode(int index){ //元素的位置不合理 if(index<1 || index>length()){ return null; } Node curNode=head; Node preNode=null; int pos=0; while(curNode!=null){ pos++; preNode=curNode; curNode=curNode.next; if(pos==index){ break; } } return preNode; }
二、查找单链表中倒数第k个元素
最容易想到的方法是首先遍历一遍单链表,求出单链表的长度n,然后将倒数第k个元素转换为正数第n-k个元素,接着去遍历一遍就可以得到结果。
public Node findNode_Counter(int k){ int n = length(); return findNode(n-k+1); }
这个方法有个缺点,就是需要对链表进行两次遍历。
显然,以上这种方法可以进行优化,如果沿着从头到尾的方向从链表中的某个元素开始,遍历k个元素后,刚好达到链表尾,那么该元素就是要找的倒数第k个元素。根据这一性质,设计如下算法:从头节点开始,依次对链表的每一个结点元素进行这样的测试,遍历k个元素,查看是否达到链表尾,直到找到倒数第k个元素。
public Node findNode_Counter(int k){ // 元素的位置不合理 if (k < 1 || k > length()) { return null; } Node curNode = head; while (curNode != null) { Node nextNode = curNode; for (int i = 1; i < k; i++) { nextNode = nextNode.next; } if (nextNode.next == null) { return curNode; } curNode = curNode.next; } return null; }
这种方法将对同一批元素进行反复多次的遍历,如果链表长度为n,该算法的时间复杂度为O(Kn)级,效率太低。
下面介绍另外一种更高效的方式,只需要一次遍历即可查找到倒数第k个元素。由于单链表只能从头到尾依次访问链表的各个结点,在查找过程中,设置2个指针,让其中一个指针比另一个指针先前移k-1步,然后两个指针同时往前移动,循环直到先行的指针值为null为止,另一个指针所指的位置就是所要找的位置。
public Node findNode_Counter(int index){ //元素的位置不合理 if(index<1 || index>length()){ return null; } Node p1=head; Node p2=head; // p1前移k-1步 for(int i=0;i<index-1;i++){ p1=p1.next; } while(p1.next!=null){ p1=p1.next; p2=p2.next; } return p2; }
三、链表的反转
为了正确地反转一个链表,需要调整指针的指向,而与指针操作相关的代码总是非常容易出错。例如,i,m,n是3个相邻的结点,假设经过若干步操作,已经把结点i之前的指针调整完毕,这些结点的next指针都指向前面一个结点。现在遍历到m,需要调整结点的next指针,让它指向i,这里需要注意,一旦调整了指针的指向,链表就断开了,因为没有指针指向结点n。所以为了避免链表断开,需要在调整m的next之前要把n保存下来。反转后链表的头节点是原始链表的尾结点。实现代码如下:
public void reverseIteratively(){ Node reverseNode=head; // 反转后链表的头节点 Node curNode=head; Node perNode=null; while(curNode!=null){ Node nextNode = curNode.next; if(nextNode != null){ reverseNode = nextNode; } curNode.next = perNode; // 结点的next指向前一个节点 perNode = curNode; curNode = nextNode; } head=reverseNode; }
四、从尾到头输出单链表
首先想到的是,先将链表反转,然后就可以从尾到头输出了。
第二种方法是,从头到尾遍历链表,每经过一个结点,把该结点放到一个栈中。当遍历完整个链表后,再从栈顶开始输出结点的值,此时输出的结点的顺序就反转过来了。
public void printList_Counter(){ Node temp = head; Stack<Node> stack = new Stack<Node>(); while(temp!=null){ stack.push(temp); temp=temp.next; } Enumeration<Node> items=stack.elements(); while(items.hasMoreElements()){ System.out.println(stack.pop().data.toString()); } }
该方法虽然只遍历一遍链表,但是需要另外维护一个栈空间。
第三种方法,递归本身就是一个栈结构,要实现反过来输出一个栈表,每访问到一个节点,先递归输出它后面的结点,再输出该结点自身,这样链表的输出结果就反过来了。
public void printList_Counter(Node node){ while(node!=null){ printList_Counter(node.next); System.out.println(node.data.toString()); break; } }
五、寻找单链表的中间节点
首先想到的是,先求链表的长度n,然后遍历n/2的距离即可找到单链表的中间结点。
第二种方法,采用双指针的方式来实现中间结点的快速查找。
1)有两个指针从头开始遍历;
2)一个快指针一次走两步,一个慢指针一次走一步;
3)快指针走到链表尾部,慢指针刚好在链表中部;
public void searchMid(Node head){ Node slow = null; Node fast = head; int pos=1; while (fast != null && fast.next != null) { fast = fast.next.next; if(pos>1){ slow = slow.next; }else{ slow = head; } pos++; } if(fast == null){ System.out.println(slow.data.toString()); } System.out.println(slow.next.data.toString()); }