数据结构与算法 --- 链表(二)

369 阅读5分钟

链表应用

链表实现栈和队列

思路:

反转链表

反转一个单链表。
示例:

输入: 1->2->3->4->5->null
输出: 5->4->3->2->1->null

思路:

  1. 递归方法反转链表 递归的精髓在于甩锅, 自己不能完成的事情,让别人先做,别人完成之后,再在别人的基础上完成。
  • 明确函数的功能,既然让别人做就要告诉它做什么,函数 reverse_digui(head)是从head 开始反转链表,函数的返回值new_head是反转后链表的首节点
  • 原本是从head 开始反转, 而你不会啊,就从 head.next 开始。head.next 完成反转后head.next 就是新链表的尾节点 连接上 head 就可以了。head.next.next = head; head.next = null, 这样就完成了反转
  • 递归必须有终止条件,不然就会无限循环下去。函数最终要返回新链表的头, 而新链表的头就是旧链表的尾。所以该题的终止条件就是到尾节点时直接返回尾节点。
function reverse_digui(head{
    if(!head) {
        return null
    }
    if(!head.next) {
        return head
    }
    
    const new_head = reverse_digui(head.next) // 先让 head.next 进行反转
    head.next.next = head  // 反转完成后, head.next 是新链表的尾节点, 再连接上 head 
    head.next = null  // head 成为新链表的尾节点
    return new_head  // 返回新链表的首节点
}
  1. 非递归反转链表

直接看上面的示例的化, 有点难以分析, 我们可以换个思路

输入:  1->2->3->4->5->null
输出:  null<-1<-2<-3<-4<-5

思路:

  • 如上面的示例, 我们只需要把当前节点指向上一个节点就行了
function reverse_link(head{
    if(!head) {
        return null
    }
    let curr_node = head
    let pre_node = null
    while(curr_node) {
        const next_node = curr_node.next // 先拿到当前节点的下一个节点
        curr_node.next = pre_node   // 当前节点指向上一个节点
        // pre_node 和 curr_node 向后滑动,让下一个节点指向上一个节点
        pre_node = curr_node
        curr_node = next_node
    }
    return pre_node
}

从尾到头打印链表

  1. 递归打印

当你拿到一个链表,得到是头节点, 只有头节点后面的节点打印完成了,头节点才能打印,这不正是递归甩锅吗

思路:

  • 拿到一个链表从 head 开始反向打印链表,但你不知道怎么打印, 甩锅给head.next 打印, head.next 打印完成后,再打印head 就可以了。
function reverse_print(head{
    if(!head) {
        console.log(null)
        return null
    }
    reverse_print(head.next)
    console.log(head.data)
}
  1. 非递归反向打印链表 思路:
  • 遍历整个链表
  • 先遍历的后打印,这是明显的 栈的先进后出的特性
function reverse_print(head{
    let stack = new Stack()
    while(head) {
        stack.push(head)
        head = head.next
    }
}

合并两个有序链表

思路:

  • 对于两个链表,各自设置一个游标节点指向首节点,比较首节点数值,数值小的那个,拿出来放入合并链表,同时游标节点向后滑动,继续比较游标节点数值。
  • 为了实现滑动,需要用到 while 循环,一个游标节点为null 时,循环终止,这时另一个游标可能还没有到达尾节点,需要把这段没有遍历结束的链表添加到合并链表上。
function merge_link(head1, head2){
    if(!head1) return head2
    if(!head2) return head1
    
    let merge_head = null  // 合并链表的首节点
    let merge_tail = null  // 合并链表的尾节点
    let min_node = null
    let curr1 = head1
    let curr2 = head2
    
    while(curr1 && curr2) {
        if(curr1.data < curr2.data) { // 值小的节点作为最小节点
            min_node = curr1
            curr1 = curr1.next
        }else {
            min_node = curr2
            curr2 = curr2.next
        }
        
        if(!merge_head) { // merge_head 为null ,说明这时第一个最小节点
            merge_head = min_node
            merge_tail = min_node
        }else {
            merge_tail.next = min_node
            merge_tail = min_node
        }
        
        // 连接上剩下还没有到尾节点的链表
        let rest_link = null
        if(curr1) {
            rest_link = curr1
        }
        
        if(curr2) {
            rest_link = curr2
        }
        while(rest_link) {
            merge_tail.next = rest_link
            merge_tail = rest_link
            rest_link = rest_link.next
        }
        
        return merge_head
    }
}