问题
- 给定一个带头节点的单链表,将其逆序。head->1->2->3->4->5->6->7变为 head->7->6->5->4->3->2->1。
分析
单链表与数组不同,单链表中每个节点的地址都存储在其前驱节点的指针域中,因此对链表中任何一个节点的访问只能从链表的头指针开始进行遍历。在修改节点指针域的时候,记录后继节点的地址,以防丢失后继节点。
注:在之前的文章通过java实现了单链表,我们以此具体链表为例,添加倒叙操作。点击此连接跳转查看具体实现
方法一:就地逆序
- 在遍历列表时,修改当前节点的指针域指向,让其指向它的前驱节点。因此,需要一个变量来记录当前节点,以防后继节点消失,还需要一个变量保存后继节点,除此之外还需要对首位节点进行处理。算法实现如下:逆序是最下面的 reverse() 方法
public class TextLink<T> {
private class Node {//存储数据和next节点
private Object data;
Node next;
public Node() {
data = null;
}
public Node(T data) {
this.data = data;
}
}
private Node head;//头指针
private Node rear;//记录尾指针
private Node point;//临时指针
private int length;//链表长度
public TextLink() {
head = new Node();
rear = head;
length = 0;
}
/**
* 插入数据,从尾部添加
*/
public void add(T elem) {
point = new Node(elem);
rear.next = point;
rear = point;
length++;
}
public void traverse() {
point = head;
if (head != null) {
while (point.next != null) {
System.out.print(" [" + point.next.data + "]");
point = point.next;
}
}
}
/**
* 获取链表长度
*/
public int getLength() {
return length;
}
/**
* 将元素插入指定位置
*/
public void insert(int position, T elem) {
if (position >= 0 && position <= length) {
point = movePoint(position);
Node tmp = new Node(elem);
tmp.next = point.next;
point.next = tmp;
length++;
} else {
throw new ArrayIndexOutOfBoundsException("insert position is" + position + ",max length is" + length);
}
}
/**
* 移除指定位置元素
*/
public void remove(int position) {
if (position >= 0 && position < length) {
point = movePoint(position);
Node tmp = point.next;
point.next = tmp.next;
length--;
} else {
throw new ArrayIndexOutOfBoundsException("insert position is" + position + ",max length is" + length);
}
}
public void set(int position, T elem) {
if (position >= 0 && position < length) {
point = movePoint(position);
Node tmp = point.next;
tmp.data = elem;
} else {
throw new ArrayIndexOutOfBoundsException("insert position is" + position + ",max length is" + length);
}
}
public T findByPosition(int position) {
if (position >= 0 && position < length) {
point = movePoint(position);
return (T) point.next.data;
} else {
throw new ArrayIndexOutOfBoundsException("insert position is" + position + ",max length is" + length);
}
}
/**
* 通过内容查找下标
*/
public int findByData(T elem) {
int index = -1;
point = head.next;
while (point != null) {
index++;
if (point.data == elem) {
break;
}
point = point.next;
}
return index;
}
/**
* 查找position位置的元素
*/
private Node movePoint(int position) {
if (position >= 0 && position <= length) {
point = head;
while (point != null) {
if (position == 0) {
break;
}
position--;
point = point.next;
}
}
return point;
}
/**
* 倒叙操作
*/
public void reverse() {
if (head == null || head.next == null) {
return;
}
Node pre = null; // 前驱节点
Node cur = null; // 当前节点
Node next = null; // 后继节点
// 把链表首节点变为尾节点
cur = head.next;
next = cur.next;
cur.next = null;
pre = cur;
cur = next;
// 使当前遍历到的节点 cur 指向前驱节点
while (cur.next != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
cur.next = pre;
head.next = cur;
}
}
上面代码中 reverse() 为添加的倒序方法。假设当前已经遍历到了 cur 节点,由于它所有的前驱节点都已经完成了逆序操作,因此只需要使 cur.next = pre; 即可完成逆序操作。再此之前,为了记录当前节点的后继节点地址,需要用一个 next 的额外指针来保存后继节点信息。当前节点完成逆序后,通过后移指针来对后续节点用同样的方法进行逆序操作。
程序运行如下:
public static void main(String[] args) {
TextLink<String> link = new TextLink<>();
link.add("1");
link.add("2");
link.add("3");
link.add("4");
link.add("5");
link.add("6");
link.add("7");
link.traverse();
link.reverse();
System.out.println();
link.traverse();
}
输出如下:
[1] [2] [3] [4] [5] [6] [7]
[7] [6] [5] [4] [3] [2] [1]
- 此方法性能分析
此方法只需要对链表进行一次遍历,因此时间复杂度为O(N)。 其中,N为链表的长度,但是需要额外的变量来保存当前节点的前驱节点和后驱节点因此控件复杂度为O(1)。