数据结构——链表——双向链表

127 阅读3分钟

链表:用于存储数据的线性结构

特点:

  • 链表中的元素在内存中不必是连续的空间.
  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成.

与数组相比较:

优点:

  • 内存空间不是比是连续的. 可以充分利用计算机的内存. 实现灵活的内存动态管理.
  • 链表不必在创建时就确定大小, 并且大小可以无限的延伸下去.
  • 链表在插入和删除数据时, 时间复杂度可以达到O(1). 相对数组效率高很多.

缺点:

  • 链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素).
  • 无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的位置.

双向链表:

  • 既可以从头遍历到尾, 又可以从尾遍历到头

  • 也就是链表相连的过程是双向的. 那么它的实现原理, 你能猜到吗?

  • 一个节点既有向前连接的引用, 也有一个向后连接的引用.

  • 双向链表可以有效的解决单向链表中提到的问题.

  • 双向链表有什么缺点呢?

    • 每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 也就是实现起来要困难一些
    • 并且相当于单向链表, 必然占用内存空间更大一些.
    • 但是这些缺点和我们使用起来的方便程度相比, 是微不足道的.

图解:

1.当链表中还没有节点时,设置头结点指向null,尾结点指向null,链表长度为0

创建空链表:

class LinkList{
    constructor(){
    //头结点为空(头结点指向为空)
        this.head=null
       //尾结点为空(尾结点指向为空)
       this.tail=null
        //链表长度为0
        this.length=0
    }   
 }

image.png

2.当链表中有节点时,头结点指向第一个节点,尾结点指向null。(注意:这里的节点需要自己创建)

创建节点元素:

封装一个构造函数,用于保存元素、指向上一个元素及指向下一个元素

class Dnode{
        // 封装一个构造函数,用于保存元素、元素的上一个引用及元素的下一个引用
        constructor(data){
            // 指向上一个元素节点
            this.prev=null
            // 存放数据
            this.data=data
            // 指向下一个节点(引用)
            this.next=null
        }
    }

image.png

对链表进行操作(在链表中添加方法):

1.向链表最后添加元素 append(ele)

  append(ele) {
            // 创建新节点
            let newnode = new Dnode(ele);
            // 判断是否为空链表
            if (this.length == 0) {
                //头结点指向新节点 
                this.head = newnode
                // 尾结点指向新节点
                this.tail=newnode
            } else {
                // 非空链表
                // 新节点的上一个引用指向上一个节点
            newnode.prev=this.tail
            // 断开原来的指向,重新指向新节点
            this.tail.next=newnode;
            this.tail=newnode;

            }
            //    添加了元素,length长度就+1
            this.length++
        }

图解:

1.this.length=0

image.png 2.this.length!=0

image.png

2.向链表中的特定位置插入一个新的节点 insert(position,ele)

insert(position, ele) {
            // 判断位置是否合法
            if (position < 0 || position > this.length || !Number.isInteger(position)) {
                return false
            }
            let newnode = new Dnode(ele)
            console.log(newnode)
            if (position == 0) {
                if (this.length == 0) {
                    // 空链表
                    this.head = newnode
                    this.tail = newnode
                } else {
                    
                    newnode.next=this.head
                    this.head.prev=newnode
                    this.head=newnode
                    

                }
                this.length++
            } else if (position == this.length) {
                this.append(ele)
            } 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
                current.next.prev = newnode
                this.length++
            }
        }

图解:

1.position=0,this.length=0

image.png

2.position=0,this.length!=0

image.png

3.position=this.length,使用append(ele)方法

4.0<position&&position<this.length

image.png

image.png

3.从链表的特定位置移除一项 removeAt(position)

图解:

1.当position=0,this.length=1

image.png

2.position=0,this.length!=1

 removeAt(position) {
            // 判断位置是否合法
            if (position < 0 || position >=this.length || !Number.isInteger(position)) {
                return false
            }
            if (position == 0) {
                if (this.length == 1) {
                    this.head = null
                    this.tail = null
                } else {
                    this.head = this.head.next
                    this.head.prev = null
                }
                
            } else if (position == this.length - 1) {
                this.tail = this.tail.prev
                this.tail.next = null
            } else {
                let current = this.head
                let index = 0
                while (index < position ) {
                    index++;
                    current = current.next;
                }
                console.log(index)
                console.log(current,1111)
                console.log(current.prev,222);
                console.log(current.next,3333);
                
                current.prev.next=current.next;
                console.log(current)
                current.next.prev=current.prev;
                // console.log(current)
                // console.log(list)
            }
            this.length--
        }

image.png

3.position=this.length-1

image.png

4.0<position&&position<this.length-1

image.png

image.png

4.返回元素在链表中的索引,如果链表中没有该元素则返回-1. indexOf(ele)

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

5.从链表中移除指定元素 remove(ele)

 remove(ele){
            removeAt(indexOf(ele))
        }

6.如果链表中不包含任何元素,返回true,如果链表长度大于0,则返回false。 isEmpty()

       isEmpty() {
            if (this.length == 0) {
                return true
            } else {
                return false
            }
        }

7.返回链表包含的元素个数。与数组的length属性类似。 size()

    size() {
            return this.length
        }

8.遍历

1.正向遍历:

   toAfterString() {
            let current = this.head
            let index = 0
            let res = ""
            while (index < this.length) {
                res+= "-" + current.data
                current = current.next
                index++
            }
            return res.slice(1)
        }

2.反向遍历:

   toBeforeStrong(){
            let current=this.tail
            let index=this.length-1
            let res=""
            while(index>=0){
                res += "-" + current.data
                current = current.next
                index--
            }
            return res.slice(1)
        }
        
        
        
        
        
        
        

完整代码:

       <script>
    class Dnode {
        // 封装一个构造函数,用于保存元素、元素的上一个引用及元素的下一个引用
        constructor(data) {
            // 指向上一个元素节点
            this.prev = null
            // 存放数据
            this.data = data
            // 指向下一个节点(引用)
            this.next = null
        }
    }
    class DoubleLinkList {
        constructor() {
            //头结点为空(头结点指向为空)
            this.head = null
            //尾结点为空(尾结点指向为空)
            this.tail = null
            //链表长度为0
            this.length = 0
        }
        // 1.append()  向链表最后添加元素
        append(ele) {
            // 创建新节点
            let newnode = new Dnode(ele);
            // 判断是否为空链表
            if (this.length == 0) {
                //头结点指向新节点 
                this.head = newnode
                // 尾结点指向新节点
                this.tail = newnode
            } else {
                // 非空链表
                // 新节点的上一个引用指向上一个节点
                newnode.prev = this.tail
                // 断开原来的指向,重新指向新节点
                this.tail.next = newnode;
                this.tail = newnode;

            }
            //    添加了元素,length长度就+1
            this.length++
        }

        // 2.向链表中的指定位置插入元素
        insert(position, ele) {
            // 判断位置是否合法
            if (position < 0 || position > this.length || !Number.isInteger(position)) {
                return false
            }
            let newnode = new Dnode(ele)
            console.log(newnode)
            if (position == 0) {
                if (this.length == 0) {
                    // 空链表
                    this.head = newnode
                    this.tail = newnode
                } else {
                    
                    newnode.next=this.head
                    this.head.prev=newnode
                    this.head=newnode
                    

                }
                this.length++
            } else if (position == this.length) {
                this.append(ele)
            } 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
                current.next.prev = newnode
                this.length++
            }
        }
        // 3.移除指定位置的元素
        removeAt(position) {
            // 判断位置是否合法
            if (position < 0 || position >=this.length || !Number.isInteger(position)) {
                return false
            }
            if (position == 0) {
                if (this.length == 1) {
                    this.head = null
                    this.tail = null
                } else {
                    this.head = this.head.next
                    this.head.prev = null
                }
                
            } else if (position == this.length - 1) {
                this.tail = this.tail.prev
                this.tail.next = null
            } else {
                let current = this.head
                let index = 0
                while (index < position ) {
                    index++;
                    current = current.next;
                }
                console.log(index)
                console.log(current,1111)
                console.log(current.prev,222);
                console.log(current.next,3333);
                
                current.prev.next=current.next;
                console.log(current)
                current.next.prev=current.prev;
                // console.log(current)
                // console.log(list)
            }
            this.length--
        }
        // 4.查找指定元素的位置
        indexOf(ele) {
            let current = this.head
            let index = 0
            while (index < this.length-1) {
                if (current.data == ele) {
                    return index
                } else {
                    current = current.next
                    index++
                }
            }
            return -1
        }
        // 5.从链表中移除指定元素 remove(ele)
        remove(ele) {
            let index=this.indexOf(ele)
            console.log(index)
            this.removeAt(index)
        }
        // 6.如果链表中不包含任何元素,返回true,如果链表长度大于0,则返回false。 isEmpty()
        isEmpty() {
            if (this.length == 0) {
                return true
            } else {
                return false
            }
        }
        // 7.返回链表包含的元素个数。与数组的length属性类似。 size()
        size() {
            return this.length
        }
        //    8.遍历
        // 正向遍历
        toAfterString() {
            let current = this.head
            let index = 0
            let res = ""
            while (index < this.length) {
                res+= "-" + current.data
                current = current.next
                index++
            }
            return res.slice(1)
        }
        // 反向遍历
        toBeforeStrong(){
            let current=this.tail
            let index=this.length-1
            let res=""
            while(index>=0){
                res += "-" + current.data
                current = current.next
                index--
            }
            return res.slice(1)
        }


    }
    // 测试代码
    let list = new DoubleLinkList()
    for (let i = 0; i < 4; i++) {
        list.append(i)
    }
    list.insert(0, "hello")
    // list.insert(2, "smg")
    console.log(list.toAfterString())
    list.remove(2)
    
    // list.removeAt(4)
    
    // console.log(list)
    
    console.log(list.toAfterString())
</script>