数据结构与算法——链表

链表分为单向链表和双向链表

1.单向链表

链表1.png
链表2.png

上面两张图都描绘了单项链表的结构:链表头+各元素节点。
单向链表中的元素包含一个存储元素本身的节点(data)和指向下一个元素的引用(next)。

2.链表与数组的优缺点比较

链表和数组一样,都是可以用于存储一系列元素的线性数据结构。
数组的缺点:
创建一个数组,需要申请一段连续的内存空间,并且大小固定(大部分编程语言都是固定的),所以当前数组内存不能满足容量需求的情况,需要进行数组的扩容。而且在数组中插入元素的成本很高,需要进行大量的元素位移。
链表的优势:
链表中的元素在内存中不必是连续的内存空间,可以充分利用计算机的内存,实现灵活的内存动态管理;
链表在创建时可以不用确定大小,其大小可以无限的延伸下去;
链表在插入元素和删除元素时,时间复杂度可以达到O(1),相对数组效率很高。
链表的缺点:
访问链表中的元素时,需要从头开始访问,也就是不能跳过第一个元素直接去访问其他元素。

3.单向链表的封装

class Node{
            constructor(element){
                this.element = element
                //刚开始创建的第一个节点next是为空的,因为并没有下一个节点
                this.next = null
            }
        }

        class linkList{
            constructor(){
                this.head = null
                this.length = 0
            }

            append(element){ //链表中添加节点
                const newNode = new Node(element)

                if(!this.head){ //没有链表头,就把链表头指向要添加进来的节点
                    this.head = newNode
                }else{ //有了链表头,从链表头开始,看每个节点的next是否有引用
                    let current = this.head
                    while(current.next){ //有引用,则一个个往下查找,直至没有引用
                        current = current.next
                    }
                    current.next = newNode //没有引用,就把要添加的元素作为引用
                }
                this.length++
            }

            insert(element,position){ //链表中指定位置前插入节点
                const newNode = new Node(element)
                if(position <0 || position > this.length -1) return null
                // if(position === 0){
                if(position === 0){
                    if(!this.head){ //如果插入前,链表中啥也没有
                        this.head = newNode
                    }else{
                        newNode.next = this.head //插入的节点的next指向下一个节点
                        this.head = newNode //this.head指向插入的节点
                    }
                }else{
                    let index = 0
                    let previous = null
                    let current = this.head
                    while(index++ < position){
                        previous = current
                        current = current.next
                    }
                    previous.next = newNode //前一个节点指向插入的节点
                    newNode.next = current //插入的节点的next指向在position那个位置的节点
                }
                this.length++
            }

            get(position){ //查找链表中的某个节点
                if(position <0 || position > this.length -1) return null
                let index = 0
                let current = this.head
                while(index++ < position){
                    current = current.next
                }
                return current.element
            }

            indexOf(element){ //返回某个节点的索引值
                let current = this.head
                let index = 0
                while(current){
                    if(current.element === element){
                        return index
                    }
                    index++
                    current = current.next
                }
                return -1 //没有这个节点的时候返回-1
            }

            removeAt(position){ //根据position删除某个指定节点
                if(position < 0|| position > this.length -1) return null
                let current = this.head
                let previous = null
                let index = 0
                if(position === 0){ //删除第一个,直接让表头指向第二个节点,第一个节点没有了引用就会被垃圾回收
                    this.head = current.next
                }
                while(index++ < position){
                    previous = current
                    current = current.next
                }
                previous.next = current.next
                this.length--
                return current.element //返回被移除的节点
            }

            update(element,position){ //修改某节点,返回被修改的节点
                //1.先把指定位置的节点删除
                const res = this.removeAt(position)

                //2.再把新节点插入进去
                this.insert(element,position)
                return res
            }

            remove(element){ //根据节点element删除节点
                //1.获取要被删除节点的索引值
                let index = this.indexOf(element)

                if(index === -1) return null
                this.removeAt(index)
            }

            isEmpty(){
                return this.length === 0
            }

            size(){
                return this.length
            }
        }

        //测试封装的方法
        const linklist = new linkList()
        //append(element)
        linklist.append('hh')
        linklist.append('gg')
        linklist.append('kk')
        console.log(linklist)
        //insert(element,position)
        linklist.insert('pp',1)
        console.log(linklist)
        //get(position)
        console.log(linklist.get(2)) //gg
        console.log(linklist.get(4)) //null
        //indexOf(element)
        console.log(linklist.indexOf('kk'))//3
        //removeAt(position)
        console.log(linklist.removeAt(3))
        console.log(linklist)
        //update(element,position)
        console.log(linklist.update('io',1))
        console.log(linklist)
        //remove(element)
        linklist.remove('gg')
        console.log(linklist)

4.双向链表

单向链表内节点的连接是单向的,也就是只能从头遍历到尾部,实现的原理是前一个节点包含有下一个节点的引用。但是,这种单向的连接很明显的缺点就是不能返回到前一个节点,而实际开发中,又经常需要回到上一节点。
所以,双向链表的一大优势就是在元素中额外添加了向前一节点的引用。
当然,这样的操作,会使得在往双向链表中插入或删除元素的时候,要处理四个引用的指向。而且,占用内存空间增加。不过,这些和使用的方便上来讲,不算什么。
双向链表示意图:


双向链表1.png
双向链表2.png

双向链表的特点:
(1)有一个head和tail分别指向链表的头部和尾部;
(2)每个节点包含三个部分:prev(前一节点的引用)、data(当前节点)、next(下一节点的引用);
(3)链表第一个节点的prev为null;
(4)链表的最后一个节点的next为null。

5.双向链表的封装

class doubleNode extends Node{
            constructor(element){
                super(element)
                this.prev = null
            }
        }

        class doubleLinkList extends linkList{
            constructor(){
                super()
                this.tail = null //双向链表的尾部
            }

            append(element){
                const newNode = new doubleNode(element)

                if(!this.head){ //1.没有节点的情况
                    this.head = newNode
                    this.tail = newNode
                }
                //2.有节点的情况
                this.tail.next = newNode //尾部节点的next指向新添加的节点
                newNode.prev = this.tail //同时新添加的节点的prev指向尾部的节点
                this.tail = newNode //最后,将this.tail指向新添加的节点

                //3.链表长度+1
                this.length++
            }

            insert(element,position){
                if(position < 0 && position > this.length -1) return null
                const newNode = new doubleNode(element)
                if(position === 0){
                    if(!this.head){
                        this.head = newNode
                        this.tail = newNode
                    }else{
                        newNode.next = this.head
                        this.head.prev = newNode
                        this.head = newNode
                    }
                }else{
                    let index = 0
                    let current = this.head
                    let previous = null
                    while(index++ < position){
                        previous = current
                        current = current.next
                    }
                    //找到position,交换节点
                    newNode.next = current
                    current.prev = newNode
                    previous.next = newNode
                    newNode.prev = previous
                }
                this.length++
            }

            //get():直接继承父类的get()

            //indexOf():直接继承父类的indexOf()

            removeAt(position){
                if(position < 0 || position > this.length - 1) return null

                let current = this.head
                if(position === 0){ //删除第一个的情况
                    if(this.length === 1){ //假如链表当前只有一个节点
                        this.head = null
                        this.tail = null
                    }else{ //链表有多个节点
                        this.head = current.next
                        current.next.prev = null
                        current.next = null
                    }
                }else if(position === this.length -1){ //删除最后一个的情况
                    current = this.tail
                    this.tail.prev.next = null
                    this.tail = this.tail.prev
                }else{
                    let index = 0
                    let previous = null
                    while(index++ < position){
                        previous = current
                        current = current.next
                    }
                    previous.next = current.next
                    current.next.prev = previous
                }
                this.length--
                return current.element //返回被删除的元素
            }

            //update(element,position):直接继承父类的update()
            
            //remove(element):直接继承父类的remove()

            //isEmpty():直接继承

            //size():直接继承
        }

const doublelinklist = new doubleLinkList()
        //append()
        doublelinklist.append('aa')
        doublelinklist.append('bb')
        doublelinklist.append('cc')
        doublelinklist.append('dd')
        console.log(doublelinklist)
        //insert()
        doublelinklist.insert('ee',0)
        //get()
        console.log(doublelinklist.get(2))
        //indexOf()
        console.log(doublelinklist.indexOf('dd'))
        //removeAt()
        console.log(doublelinklist.removeAt(0))
        //update()
        console.log(doublelinklist.update('ff',1))
        //remove(element)
        doublelinklist.remove('ff')
        console.log(doublelinklist)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352