链表-单链表的查找与反转

一、查找单链表中第index个元素
	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());
	}

猜你喜欢

转载自margaret0071.iteye.com/blog/2353709