1.链表节点的定义
链表是一种动态的数据结构。在创建链表时,无需知道链表的长度。当插入一个新的节点时,只需要为新节点分配内存,然后调整指针只想来确保新节点被链接到当前的链表中。内存分配不是在创建链表是一次性完成的,而是每次添加一个节点分配一次内存。由于没有闲置的内存,链表的空间效率比数组高。以单链表为例,仅包含值和下一个节点的地址:
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x;}
}
2.对链表一些简单的操作
链表的操作主要包含向链表的末尾添加一个节点、链表的遍历、在链表中找到第一个含有某值的节点并删除该节点。
往末尾添加一个节点:
public void add(ListNode head, int value) {
ListNode node = new ListNode(value);
node.next = null;
if(head == null)
head = node;
else {
ListNode cur;
cur = head;
while(cur.next != null)
cur = cur.next;
cur.next = node;
}
}
链表的遍历:
public void travel(ListNode head) {
if(head == null) return;
ListNode cur = head;
while(cur != null) {
//对链表进行的操作
}
}
在链表中找到第一个含有某值的节点并删除:
public void removeNode(ListNode head, int value) {
if(head == null) return;
ListNode cur, toBeDeleted;
cur = head;
toBeDeleted = null;
//如果要删除的节点为链表的头结点,则直接将第二个节点作为链表的头结点返回
if(cur.val == value) {
head = head.next;
return;
}else {
//找到含有某值的节点,并记录该节点的前一个节点
while(cur.next != null){
if(cur.next.val == value) {
toBeDeleted = cur.next;
break;
}
cur = cur.next;
}
//该链表存在含有某值的节点,否则对原链表不进行任何改动
if(toBeDeleted.next != null) {
cur.next = toBeDeleted.next;
}
}
}
3.从尾到头输出链表
从头到尾输出链表会比较简单,所以我们考虑怎么把链表中链接节点的指针反转过来,改变链表的方向,于是就可以自然从头到尾输出链表了。
第二种方法则可以这样考虑。遍历的顺序是从头到尾,可输出却是从尾到头。也就是说,第一个便利道德节点最后一个输出,而最后一个便利道德节点第一个输出。自然而然的我们会想到栈这种数据结构,“先进后出”。每经过一个节点,就把该节点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出节点的值。此时输出的顺序就翻转过来了。
public void printReverse(ListNode head) {
if(head == null) return;
Stack<ListNode> stack = new Stack<>();
ListNode cur = head;
while(cur != null) {
stack.push(cur);
cur = cur.next;
}
while(stack.isEmpty()) {
System.out.println(stack.peek().val);
stack.pop();
}
}
既然想到了用栈来实现,而递归本质上就是一个找结构,所以我们可以想到也可用地归来实现。要实现反过来输出链表,每访问一个节点上时,先输出它后面的节点,在输出该节点的值。
// 递归的方法实现逆序输出
public void printReverse_Recursively(ListNode head) {
if(head == null) return;
if(head.next == null) {
System.out.println(head.val);
}else{
printReverse_Recursively(head.next);
System.out.println(head);
}
}
有了前面的铺垫,现在我们来讨论怎么将一个链表反转,比如该链表的值依次为1,2,3则逆序后变为3,2,1。如果可以在原链表上改动的话,则不需要额外的内存空间。在遍历链表的的时候,需要知道当前节点的前一个节点,然后将当前节点的指针指向前一个链表的地址,这样该节点后面的节点就会断开。因此我们需要三个指针分别表示前一个节点beforeNode,当前节点curNode以及后一个节点afterNode。需要注意的是之前链表的头结点和尾节点反转后会变为对应的尾节点和头结点。
public void reverse(ListNode head) {
if(head == null) return;
ListNode beforeNode, curNode, afterNode;
beforeNode = null;
curNode = head;
while(curNode.next != null) {
afterNode = curNode.next;
curNode.next = null;
beforeNode = curNode;
curNode = afterNode;
}
curNode.next = beforeNode;
head = curNode;
}