最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 数据结构与算法:链表

    正文概述 掘金(潇潇雨歇)   2020-12-06   465

    链表简介

    链表是一种常见的数据结构,是一种线性表。它不会按线性顺序存储数据,相对于数组,它不需要开辟连续的存储空间。链表适合插入或者删除操作比较频繁的场景,时间复杂度为O(1);但是因为不能按下标访问元素,其查询效率较低,时间复杂度为O(n)。链表的节点有一个用于存储数据的属性,本文称作 val ,然后还有个 next 指针用于指向下一个节点,这样节点一个一个连接就构成链表了。链表类型主要有 单链表双向链表循环链表 。关于更多的内容,可查看 维基百科-链表。

    单链表

    单链表的表现形式如下图所示:

    数据结构与算法:链表

    单链表也叫单向链表,顾名思义,它只有一个方向,也就是只有一个next指针用于指向下个节点。知道了单链表的结构表现之后,我们可以开始写代码来创建单链表了。

    单链表的节点结构:

    class ListNode {
        constructor(val) {
        	// 用于保存数据
            this.val = val
            // 用于指向下个节点
            this.next = null
        }
    }
    

    单链表的创建以及添加元素和删除元素方法:

    class LinkedList {
        constructor(val) {
            this.head = new ListNode(val)
            this.size = 1
        }
    
        append(val) {
            if (!this.head) {
                this.head = new ListNode(val)
                return
            }
    
            const newNode = new ListNode(val)
            let p = this.head
    
            while (p.next) {
                p = p.next
            }
    
            p.next = newNode
            this.size++
        }
    
        deleteByVal(val) {
            // 虚拟头节点,便于删除头节点
            const dummyHead = new ListNode(0)
            dummyHead.next = this.head
            let p = dummyHead
    
            while (p.next && p.next.val !== val) {
                p = p.next
            }
    
            // 不存在该节点,返回-1
            if (!p.next) {
                return -1
            }
    
            p.next = p.next.next
            this.head = dummyHead.next
            this.size--
        }
    
        deleteByIndex(index) {
            if (index > this.size || index <= 0) {
                return -1
            }
    
            const dummyHead = new ListNode(0)
            dummyHead.next = this.head
            let p = dummyHead, i = 1
    
            while (p.next && i !== index) {
                p = p.next
                i++
            }
    
            p.next = p.next.next
            this.head = dummyHead.next
            this.size--
        }
    
        traversal() {
            const res = []
            let p = this.head
    
            while (p) {
                res.push(p.val)
                p = p.next
            }
    
            console.log(res)
        }
    }
    
    
    const linkedList = new LinkedList(0)
    linkedList.traversal() // [0]
    linkedList.append(1)
    linkedList.append(2)
    linkedList.append(3)
    linkedList.traversal() // [0, 1, 2, 3]
    linkedList.deleteByVal(3)
    linkedList.traversal() // [0, 1, 2]
    linkedList.deleteByVal(0)
    linkedList.traversal() // [1, 2]
    console.log(linkedList.deleteByVal(0)) // -1
    linkedList.deleteByIndex(1)
    linkedList.traversal() // [2]
    linkedList.deleteByIndex(1)
    linkedList.traversal() // []
    console.log(linkedList.deleteByIndex(1)) // -1
    

    这里主要实现了添加、删除、遍历的方法;在某索引前添加元素或者在某个值前添加的方法其实和删除节点差不多,有兴趣可以实现一下。我们可以注意到,这里我使用了 dummyHead ,这是一种技巧,处理头节点非常方便。后续例题讲解还会用到这个技巧。

    双向链表

    双向链表比单链表多个了 prev 指针,用于指向上一个节点。

    数据结构与算法:链表

    这里给个双向链表的节点结构定义代码:

    class DLinkedNode {
        constructor(val) {
            this.prev = null
            this.next = null
            this.val = val
        }
    }
    

    关于双向链表的创建以及添加元素方法实现,这里我们可以使用虚拟头节点和虚拟尾节点方便后续操作。

    class DLinkedList {
        constructor() {
            this.dummyHead = new DLinkedListNode('head')
            this.dummyTail = new DLinkedListNode('tail')
            this.dummyHead.next = this.dummyTail
            this.dummyTail.prev = this.dummyHead
        }
    
        append(val) {
            const newNode = new DLinkedListNode(val)
    
            newNode.next = this.dummyTail
            newNode.prev = this.dummyTail.prev
            this.dummyTail.prev.next = newNode
            this.dummyTail.prev = newNode
        }
    
        traversal() {
            let p = this.dummyHead.next
            const res = []
    
            while (p && p.next) {
                res.push(p.val)
                p = p.next
            }
    
            console.log(res)
        }
    }
    
    const dLinkedList = new DLinkedList()
    dLinkedList.append(1)
    dLinkedList.append(2)
    dLinkedList.append(3)
    dLinkedList.append(4)
    dLinkedList.append(5)
    dLinkedList.traversal() // [ 1, 2, 3, 4, 5]
    

    这里使用了虚拟尾节点之后,对于添加元素很方便,只要操作尾节点即可。

    循环链表

    循环链表就是最后一个节点指向头节点,这里暂时不做过多讲解。

    接下来我们看几个题目:

    例题

    2. 两数相加

    206. 反转链表

    141. 环形链表

    两数之和

    var addTwoNumbers = function(l1, l2) {
        // dummyHead虚拟头节点,用于保存合并后的链表
        const dummyHead = new ListNode(0)
        // 当前处理的指针,每次通过curr.next构造新节点
        let curr = dummyHead, carry = 0
    
        while (l1 || l2 || carry > 0) {
            const val1 = l1 ? l1.val : 0
            const val2 = l2 ? l2.val : 0
            const sum = val1 + val2 + carry
    
            curr.next = new ListNode(sum % 10)
            carry = Math.floor(sum / 10)
    
            l1 = l1 && l1.next
            l2 = l2 && l2.next
            curr = curr.next
        }
    
        return dummyHead.next
    };
    

    其实这题相加操作有很多类似题,比如字符串相加,也就是大数相加。有兴趣可以去做做。这里主要多了链表指针的相关处理。

    反转链表

    这题我们可以使用双指针,prev 为上一个节点,curr 为当前节点;然后每次将 curr.next 设置为 prev;以此循环,则实现了链表的反转。具体代码实现如下:

    var reverseList = function(head) {
        let prev = null, curr = head
    
        while (curr) {
        	// 需要保存 curr.next,不然进行不了下一步操作了
            const next = curr.next
            curr.next = prev
            prev = curr
            curr = next
        }
    
        return prev
    };
    

    环形链表

    这道题我们可以使用 Set 来解决,但是有另外一种更有意思的解法:快慢指针,快指针每次走两步,慢指针每次走一步,如果有环的话,快指针一定会追上慢指针。具体实现如下:

    var hasCycle = function(head) {
        let fast = head, slow = head
    
        while (fast && fast.next) {
            fast = fast.next.next
            slow = slow.next
    
            if (fast === slow) {
                return true
            }
        }
    
        return false
    };
    

    总结:链表操作其实将指针改来改去,我们要注意一些边界问题。同时注意利用虚拟节点,快慢指针等技巧优化代码。

    leetcode题解github地址:https://github.com/webpig/leetcode,之后会继续整理更多题解。欢迎大家一起交流。


    起源地下载网 » 数据结构与算法:链表

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元