链表及常见算法(java实现)
注意:本文中的链表默认都是带头节点的。
1.链表的实现
实现很简单,只有两个值,一个data,一个指向下一个节点的指针。由于java语言的特点,这里的指针其实是引用,但为了方便叙述,下文全部称为指针。
public class LNode {
int data;
LNode next;
}
2.链表的逆序
就地逆序
就地逆序需要三个指针,遍历的过程中就像是排着一排的人一个一个转身,cur标识现在转身的节点,pre和next则记录转身前前后的两个人,以保证重建连接。
这个方法只需要对链表进行一次遍历,时间复杂度为O(N)。需要常数个额外变量记录前后节点,空间复杂度为O(1)。
总的来说,是一个中规中矩的算法。
//就地逆转
public static void Reverse_still(LNode head) {
if(head == null || head.next == null) {
return ;
}
LNode pre = null;
LNode cur = null;
LNode next = null;
cur = head.next;//指针移动
next = cur.next;
cur.next = null;//第一个节点转为第二个节点
//继续下一个移动
pre = cur;
cur = next;
while(cur.next !=null) {
next = cur.next;
cur.next = pre;
//继续移动
pre = cur;
cur = next;
}
cur.next = pre;
head.next = cur;
}
递归逆序
因为递归部分只能对没有头结点的链表进行处理,所以分成了两个函数进行处理。
个人以为递归永远都是相对优雅的一种方法,并且是相对难理解一些的。这个方法,我觉得就像是把头节点以后的部分看成一个黑盒,工作已经全部完成,算法里面需要体现的就是完成最后一步。但实际运行中,就像是拆一个俄罗斯套娃,只要内存足够大,且做好判断条件,就可以一层一层拆下去,直到任务的完成。
我理解的时候,遇到的难点就是这个黑盒假设,但想通以后,就会感觉很明了,很优雅。
这个方法和就地逆序差不多,都是对链表进行一次遍历,但因为不断的调用自己,就要不断的进行压栈和弹栈操作,所以性能必然会有所欠缺。(但是代码写的好看啊!!!)
//递归逆序(加上头节点)
public static void RecursiveReverse_head(LNode head) {
if(head == null) {
return ;
}
LNode firstNode = head.next;
LNode newhead = RecursiveReverse(firstNode);
head.next = newhead;
}
//对没有头节点的链表进行处理
private static LNode RecursiveReverse(LNode head) {
if(head == null || head.next == null) {
return head;
}else {
LNode newhead = RecursiveReverse(head.next);
head.next.next = head;
head.next = null;
return newhead;
}
}
插入逆序
插入逆序时间复杂度同样为O(N)。但是,和就地逆序相比,它不用保存前驱节点,和递归相比,它不用频繁的调用函数,所以它的效率最高。
我在后面写一些算法的时候发现,插入法只适合处理带头节点的链表,一旦有需求逆序没有头节点的链表时,就地逆序显得更简单易用。因为插入法能省下前驱节点的原因就是,它有效的利用了头节点的作用,每次插入都是以头节点作为标准,不断的在头节点,和head.next()之间进行插入操作。一旦没有头节点,则需要考虑程序结束后对头节点的处理,这样的话和就地逆序对比,就会有点得不偿失。
//插入逆序
public static void Reverse_insert(LNode head) {
if(head == null || head.next == null) {
return ;
}
//初始化指针
LNode cur = null;
LNode next = null;
//指针开始移动,从第二个节点开始
cur = head.next.next;
//清空第一个节点的指针,使其成为尾节点
head.next.next = null;
while(cur != null){
next = cur.next;
//插入节点
cur.next = head.next;
head.next = cur;
cur = next;//开始下一次循环
}
}
测试代码
以下为上面代码的执行类,当时有点懒,直接写了个for循环构造链表,没有单独封装出一个方法出来,后续的会逐渐完善。
public static void main(String[] args) {
LNode head = new LNode();
head.next = null;
LNode tmp = null;
LNode cur = head;
for(int i=1; i<8; i++) {
tmp = new LNode();
tmp.data = i;
tmp.next = null;
cur.next = tmp;
cur = tmp;
}
System.out.print("逆序前:");
for(cur = head.next; cur != null; cur = cur.next) {
System.out.print(cur.data+" ");
}
System.out.println();
Reverse_insert(head);
//Reverse_still(head);
//RecursiveReverse_head(head);
System.out.print("逆序后:");
for(cur = head.next; cur != null; cur = cur.next) {
System.out.print(cur.data+" ");
}
}
未完待续…