单向链表的反转是一个出现频次非常高的面试题,我曾经就在这个题上吃过亏,但是不要紧,咱研究一下也就出来了。
先说链表的单向特点:
1. 只有从前向后才能找到某一个节点
2. 最后一个节点的下一个节点地址(也就是我们通常所说的next指针)是null
3. 链表有可能是个空链表,即:它的首个节点元素就是null
4. 链表通常都有add、insert、delete、contains等几个方法
而单向链表的反转,又是一个很考验人的问题,我在网上查了很多资料,最简单的是递归反转,但是我个人认为这个方法不好,因为每次递归调用方法或者函数,都要开辟新的函数栈,系统性能开销太大,而且链表如果很长,有可能会导致栈溢出所以应当使用循环的方式,而网上查找到的循环实现方式,都太过复杂,咱们来个简单的:
先看链表的定义(MyLinkedList.java):
import com.google.gson.Gson;
/**
* 单向链表
* Created by lidawei on 2017/2/10.
*/
public class MyLinkedList<T> {
private Node<T> root;
private int position;
private int size;
/**
* 构造器
*/
public MyLinkedList() {}
/**
* 添加元素
* @param data 指定节点数据
*/
public void add(T data) {
Node<T> node = new Node<>();
node.data = data;
if (root == null) {
root = node;
size++;
return;
}
Node<T> current = root;
while (current.next != null) {
current = current.next;
}
current.next = node;
size++;
}
/**
* 插入元素
* @param index 插入到指定的位置,此位置的数据会向后移动,索引位置从0开始
* @param data 指定节点数据
*/
public void insert(int index, T data) {
checkRange(index);
Node<T> previous = root, current = root;
while (index != position) {
position++;
previous = current;
current = current.next;
}
Node<T> obj = new Node<>();
obj.data = data;
previous.next = obj;
obj.next = current;
size++;
}
/**
* 删除元素
* @param index 指定索引位置,此位置的数据被删除之后后面的元素会依次向前递补
*/
public void delete(int index) {
checkRange(index);
Node<T> previous = root, current = root;
int pos = 0;
while (current.next != null) {
if (pos == index) {
break;
}
pos++;
previous = current;
current = current.next;
}
previous.next = current.next;
size--;
}
/**
* 是否包含指定的数据
* @param data 节点数据
* @return 包含时返回true,否则返回false
*/
public boolean contains(T data) {
Node<T> current = root;
while (current != null) {
if (current.data.equals(data))
return true;
else
current = current.next;
}
return false;
}
/**
* 获得链表长度
* @return 返回一个整数,代表链表节点的数量
*/
public int size() {
return size;
}
private void checkRange(int index) {
if (index < 0 ||index >= size) {
throw new IllegalArgumentException("索引值已经越界!");
}
}
/**
* 反转链表,此操作是原地反转,也就是会修改现有的链接结构,不会创建新的链表。
*/
public void reverse() {
if (root == null || root.next == null) return;
Node<T> previous = root;
Node<T> current = previous.next;
Node<T> temp;
while (current != null) {
temp = current.next;
current.next = previous;
previous = current;
current = temp;
}
root.next = null;
root = previous;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(size * 64);
sb.append("[");
Node<T> current = root;
Gson gson = new Gson();
while (current != null) {
sb.append(gson.toJson(current.data));
sb.append(",");
current = current.next;
}
int pos = sb.lastIndexOf(",");
if (pos != -1) {
sb.deleteCharAt(pos);
}
sb.append("]");
return sb.toString();
}
private class Node<T> {
Node next;
T data;
@Override
public String toString() {
Gson gson = new Gson();
return gson.toJson(this);
}
}
}
定义中我已经写好了reverse方法,这个方法的实现很简单,看懂下面这张图,就全明白了:
注意:
图中的保存后续节点、更新指向、移动操作指针都是在循环中进行的,循环的条件当然是current != null。
请大家对照图和代码过一遍,马上就能明白了,好了,下面是我的测试用例(已经覆盖了所有方法):
import org.junit.Assert;
import org.junit.Test;
public class MyLinkedListTest {
@Test
public void givenCharactorWhenInsertThenIncreaseOne() {
MyLinkedList<Character> list = new MyLinkedList<>();
list.add('a');
list.add('b');
list.add('c');
System.out.println(list);
list.insert(2, 'e');
System.out.println(list);
}
@Test
public void givenIntegerWhenFindThenRight() {
MyLinkedList<Integer> list = new MyLinkedList<>();
list.add(11);
list.add(22);
Assert.assertTrue(list.contains(11));
Assert.assertTrue(list.contains(22));
}
@Test
public void givenIntegerWhenDeleteThenShrinkOne() {
MyLinkedList<Integer> list = new MyLinkedList<>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list);
list.delete(2);
System.out.println(list);
}
@Test
public void givenDoubleWhenDeleteThenCheckSize() {
MyLinkedList<Double> list = new MyLinkedList<>();
list.add(1.0);
list.add(2.1);
list.add(3.2);
System.out.println(list);
Assert.assertTrue(list.size() == 3);
}
@Test
public void givenIntegerWhenReverseThenRight() {
MyLinkedList<Integer> list = new MyLinkedList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
System.out.println(list);
System.out.println("Reverse...");
list.reverse();
System.out.println(list);
}
@Test
public void givenStringWhenReverseThenRight() {
MyLinkedList<String> list = new MyLinkedList<>();
list.add("abc");
list.add("def");
list.add("hij");
list.add("klmn");
list.add("zzz");
System.out.println(list);
System.out.println("Reverse...");
list.reverse();
System.out.println(list);
}
}
运行结果:
哈哈。。。是不是很简单?!!!