前端编程应该了解的数据结构——链表

链表顾名思义就是一种链式结构,画出草图

 由图可见,每个节点链接成一条链。每个节点都由自身的数据和指向下一个节点的引用组成。

通过数组认识到链表

数组是我们常用的一种数据存储容器,但是数组也有很多缺点:

1、数组的创建通常需要申请一段连续的内存空间(一整块的内存), 并且大小是固定的(大多数编程语言数组都是固定的), 所以当当前数组不能满足容量需求时, 需要扩容。

2、而且在数组开头或中间位置插入数据的成本很高, 需要进行大量元素的位移。

针对于数组的缺点,我们引入一种数据结构——链表

相对于数组, 链表有一些优点:

1、内存空间不是比是连续的. 可以充分利用计算机的内存. 实现灵活的内存动态管理。

2、链表不必在创建时就确定大小, 并且大小可以无限的延伸下去。

3、链表在插入和删除数据时, 时间复杂度可以达到O(1). 相对数组效率高很多。

当然相比于数组,数组还是有一些优点,数组对成员的访问可以从选择的下标开始;而链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素);无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的位置。

链表的常见操作

创建节点类和链表类

节点类有两个属性,自身数据和下个节点的引用

链表类有两个属性,头部和链表长度

class Newnode {
    constructor(el) {
        this.data = el
        this.next = null
    }
}
class Linklist {
    constructor() {
        this.head = null
        this.length = 0
    }
}

在链表类中封装各种方法:

append(element):向列表尾部添加一个新的项

代码实现:

  append(i) {
        //创建数据节点
        let newnode = new Newnode(i)
        //为空时就是头部
        if (this.head == null) {
            this.head = newnode
        } else {
            let current = this.head
            while (current.next) { //检测到下一个为空时,找到最后一个,跳出循环
                current = current.next
            }
            current.next = newnode
        }
        this.length++
    }

insert(position,element):向列表的特定位置插入一个新的项。

代码实现:

 insert(position, el) {
        //位置是否合法
        if (position < 0 || position > this.length || !Number.isInteger(position)) {
            return false
        }
        let newnode = new Newnode(el)
        //position==0时
        if (position == 0) {
            if (this.head == null) { //链表为空时
                this.head = newnode
            } else {
                //不为空时
                newnode.next = this.head
                this.head = newnode //设为头部
            }
            this.length++
        } else if (position == this.length) {
            //在尾部
            append(el)
        } else {
            //在任意位置
            let current = this.head
            let index = 0
            while (index < position - 1) {
                current = current.next //得到前一个
                index++
            }
            newnode.next = current.next
            current.next = newnode
            this.length++
        }


    }

removeAt(position):从列表的特定位置移除一项 。

代码实现:

 removeAt(position) {
        //位置是否合法
        if (position < 0 || position > this.length || !Number.isInteger(position)) {
            return false
        }
        //空时
        if (this.head == null) {
            return
        } else {
            if (position == 0) {
                let current = this.head
                this.head = current.next
            } else {
                //其他位置时
                let current = this.head
                let index = 0
                while (index < position - 1) {
                    current = current.next
                    index++
                }
                current.next = current.next.next
            }
            this.length--
        }
    }

indexof(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1

代码实现:

 indexof(el){
        let current = this.head
        let index=0
        while(index<this.length){
            if (current.data==el) {
                return index
            }else{
                current=current.next
                index++
            }
        }
        return -1
    }

remove(element):从列表中移除一项。

代码实现:

remove(el){
       let index = this.indexof(el)
       this.removeAt(index)
    }

toString()转换为字符串。

代码实现:

  toString(){
        let current=this.head
        let index=0
        let str=""

        while(index<this.length){
            str+="-"+current.data
            index++
            current=current.next
        }
       str= str.substr(1)
        return str
    }

上述我们讲解的是单向链表,除了单向链表还有一个双向链表。

双向链表

双向链表顾名思义是双向的,即可从头遍历到尾, 又可以从尾遍历到头。一个节点既有向前连接的引用, 也有一个向后连接的引用。

其解决了单向链表只能单向遍历,无法回到前一个节点的问题。

双向链表也有一些缺陷:

1、每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个.。也就是实现起来要困难一些;

2、相当于单向链表, 必然占用内存空间更大一些。

3、但是这些缺点和我们使用起来的方便程度相比, 是微不足道的。

画出结构图:

 

双向链表的应用

封装双向链表类和数据节点类

相比单向链表,双向链表类增加了一个尾部属性tail,指向链表尾部;数据节点类增加了一个prev属性指向前一个节点的引用。

代码实现:

class Newnode{
    constructor(el){
        this.data=el
        this.prev=null
        this.next=null
    }
}
class DoubleLinklist{
    constructor(){
        this.head=null
        this.tail=null
        this.length=0
    }
}

在双向链表类中封装各种功能:

功能与单向链表相似,方法内部做相应调整。

appen(el):在链表尾部添加元素

 //尾部添加节点
    append(el){
        let newnode=new Newnode(el)
        //链表为空时
        if (this.length==0) {
            this.head=newnode
            this.tail=newnode
        }else{
            //不为空时
            this.tail.next=newnode
            newnode.prev=this.tail
            this.tail=newnode //将新增的节点指向新节点
        }
        this.length++
    }

insert(position,el):在指定位置添加元素

insert(position,el){
        //判断position是否符合
        if (position < 0 || position > this.length || !Number.isInteger(position)) {
            return false
        }
        let newnode=new Newnode(el)
        if (position==0) {
           if (this.length==0) {
                 //链表为空时
            this.head=newnode
            this.tail=newnode
           }else{
            //不为空时
                this.head.prev=newnode
                newnode.next=this.head
                this.head=newnode
           }
          
        }else if(position==this.length-1){
            this.tail.next=newnode
            newnode.prev=this.tail
            this.tail=newnode
           
        }else{
            // 任意位置添加
            let current=this.head
            let index=0
            while(index<position-1){
                current=current.next
                index++
            }
            
            newnode.prev=current
            newnode.next=current.next

            current.next=newnode
            newnode.next.prev=newnode
        }
        this.length++
    }

removeAt(position):移除指定位置的元素

 removeAt(position) {
        //位置是否合法
        if (position < 0 || position > this.length || !Number.isInteger(position)) {
            return false
        }
         //空时
         if (this.length == 0) {
            return
        } else {
            if (position == 0) {
                //链首
                if (this.length==1) {
                    this.head=null
                    this.tail=null
                }else{
                    let current = this.head
                current.next.prev=null
                this.head = current.next
                }
                
            }else if(position=this.length-1) {
                //链尾
                let current = this.tail
                current.prev.next=null
                this.tail=current.prev
            }
            
            else {
                //其他位置时
                let current = this.head
                let index = 0
                while (index < position - 1) {
                    current = current.next
                    index++
                }
                current.next = current.next.next
                current.next.prev=current
            }
            this.length--
        }
    }

indexof(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1

indexof(el){
        let current = this.head
        let index=0
        while(index<this.length){
            if (current.data==el) {
                return index
            }else{
                current=current.next
                index++
            }
        }
        return -1
    }
    

remove(element):从列表中移除一项。

  remove(el){
        let index=this.indexof(el)
        this.removeAt(index)
    }

toString()转换为字符串,可正向输出也可逆向输出。

  // 正向
    toStringAfter(){
        let current=this.head
        let index=0
        let str=""

        while(index<this.length){
            str+="-"+current.data
            index++
            current=current.next
        }
       str= str.substr(1)
        return str
    }

    //逆向
    toStringPrevious(){
        let current=this.tail
        let index=this.length-1
        let str=""

        while(index>=0){
            str+="-"+current.data
            index--
            current=current.prev
        }
       str= str.substr(1)
        return str
    }

循环链表

在单向链表和双向链表之后,还有单向循环链表和双向循环链表,简单的介绍一下差异:

单向循环链表与单向链表的区别在于,链表的最后一个节点的next不再指向null,而是指向this.head,形成一个循环。

图解:

 

双向循环链表 其与双向链表的区别在于this.tail的next指向this.head,不再指向null;this.head的prev指向this.tail,形成双重循环。

图:

 

在单向循环链表和双向循环链表中,封装增加和删除等方法,与不循环的时候原理也是相似的,这里就不再展示了。注意头尾处的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值