链表

本篇博客的所有代码都整理到自己的github库中,以便大家查看及学习。

本篇文章主要内容有:

  • 链表数据结构
  • 向链表添加元素
  • 从链表移除元素
  • 使用 linkList
  • 双向链表
  • 循环链表
  • 排序链表
  • 通过链表实现栈

1. 链表的数据结构

链表存储有序的元素集合,链表中的元素在内存中并不是连续放置的,每个元素都是有一个存储元素本身的节点和一个指向下一个元素的引用(这个引用也称为指针或链接)组成。

链表.png

通过这幅图,我们可以更清晰理解链表的数据结构。也从这个图中可以看到,当添加或者移除元素的时候,不需要移动其他的元素,可以改变指针的指向。需要注意的是,链表的表头(head)指向链表第一个元素。

现实生活中链表的例子也比较常见,最常见的就是火车。

2. 创建链表

接下来,我们将会创建一个链表的骨架

function defaultEquals(a, b) {
    return a === b;
}

class LinkList{
    constructor(equalsFn = defaultEquals){
        this.count = 0
        this.head = undefined
        this.equalsFn = equalsFn
    }
}

// 创建链表中的节点
class Node {
    constructor(element){
        this.element = element
        this.next = undefined
    }
}

接下来我们会实现以下方法

  • push(elements) : 向链表尾部添加数据
  • insert(element,position) : 向链表中特定位置插入数据
  • getElementAt(index) : 返回指定位置的元素
  • remove(element) : 从链表中移除某一元素
  • indexOf(element) : 返回元素在链表中的索引值,如果没有找到,则返回-1
  • removeAt(position) : 移除链表特定位置的值
  • isEmpty() : 返回链表是否为空
  • size() : 返回链表元素的个数
  • toString(): 返回表示整个链表的字符串,由于链表中使用了Node类,需要重新定义指输出value值的元素方法

xuxieban.png

这里需要注意的是: 链表的最后一个节点的下一个元素始终都是undefinednull

function defaultEquals(a, b) {
    return a === b;
}

class LinkList{
    constructor(equalsFn = defaultEquals){
        this.count = 0
        this.head = undefined
        this.equalsFn = equalsFn
    }
    push(element){
        const node = new Node(element) // 创建一个节点
        let current
        if (this.head == null) { // 判断链表是否为空 ,如果链表为空,则链表表头指针指向新的节点
            this.head = node
        } else { // 否则,通过遍历的方式将
            current  = this.head
            while (current.next != null) {
                current = current.next
            }
            current.next = node
        }
        this.count++
    }
    insert(element,position){
        const node = new Node(element)
        // 向链表尾部插入值
        if (position == this.count) {
            this.push(element)
        } else {
            // 向链表头部插入值
            if (position == 0) {
                const current = this.head
                this.head = node
                this.head.next = current
                this.count++
            }
            // 向链表中间插入值
            if (position > 0 && position < this.count) {
                let prev 
                let current = this.head
                for (let i = 0; i < position; i++) {
                    prev = current
                    current = current.next
                }
                prev.next = node
                node.next = current
                this.count++
            }
        }
    }
    getElementAt(index){
        if (index <= this.size() && index >= 0) {
           let current = this.head
           for (let i = 0; i < index && current.next != null; i++) {
              current = current.next
           }
           return current.element
        }
        return undefined 
    }
    remove(element){
       const index = this.indexOf(element)
       this.removeAt(index)
    }
    removeAt(index){
        // 检查越界值
        if (index >= 0 && index < this.count) { // 合法值
            let current = this.head
            if (index == 0) {
                this.head = current.next
            } else {
                let prev  // 定义一个值 将下一项值的next
                for (let i = 0; i < index; i++) {
                    prev = current
                    current = current.next
                }
                prev.next = current.next
            }
            this.count --
            return current.element
        }
        return undefined
    }
    indexOf(element){
        let current = this.head
        for (let i = 0; i < this.size() && current != null; i++) {
            if (this.equalsFn(element,current.element)) {
              return i  
            }
            current = current.next
        }
        return -1

    }
    size(){
        return this.count
    }
    toString(){
        let resultString = ''
        let current = this.head
        if (this.head != null) {
            resultString = current.element
            while (current.next != null) { 
                resultString = `${resultString},${current.next.element}` 
                current = current.next
            }
        }
        return resultString
    }
    isEmpty(){
        return this.size() === 0
    }
}

// 创建链表中的节点
class Node {
    constructor(element){
        this.element = element
        this.next = undefined
    }
}

var linkList = new LinkList()

linkList.push(1)

linkList.push(2)

linkList.push(3)

linkList.push(4)

/** 
LinkList {
    count: 4,
    head: Node { element: 1, next: Node { element: 2, next: [Node] } },
    equalsFn: [Function: defaultEquals]
} 
**/

console.log(linkList.toString()) // 1,2,3,4

console.log(linkList.indexOf(2))  // 1

console.log(linkList.indexOf(5)) // -1

linkList.remove(2)

console.log(linkList.toString()) // 1,3,4

console.log(linkList.size()) // 3

linkList.insert(2,0) 

console.log(linkList)

console.log(linkList.toString())  // 2,1,3,4

linkList.insert(5,2)

console.log(linkList.toString()) // 2,1,5,3,4

linkList.insert(6,5)

console.log(linkList.toString()) // 2,1,5,3,4,6

这里的重点是理解代码中的添加和移除元素

3. 双向链表

双向链表和普通链表的区别在于在链表中一个节点只有链向下一个节点的链接,而双向链表中,链接是双向的,一个链接指向下一个节点的链接,一个链接指向上一个节点的链接。如下图所示

双向链表.jpg

我们这次在链表的基础上扩展一下双向链表的方法

class DoubleLinkList extends LinkList {
    constructor(equalsFn = defaultEquals){
        super(equalsFn)
        this.tail = undefined
    }
}

这里的defaultEquals方法在创建链表时有定义

双向链表中的DoublyNode也是在链表中Node的基础上继承扩展的

class DoublyNode {
    constructor(element){
        super(element,next)
        this.prev = undefined
    }
}

4.双向链表的方法

创建的双向链表的类中的注释方法可以理解为上述方法的第二种方法。双向链表中重点掌握在任意位置插入和移除元素就可以了。

class DoubleLinkList extends LinkList {
    constructor(equalsFn = defaultEquals){
        super(equalsFn)
        this.tail = undefined
    }
    push(element){
        const node = new DoublyNode(element)
        if (this.head == null) {
            this.head = node
            this.tail = node
        } else {
            node.prev = this.tail
            this.tail.next = node
            this.tail = node
        }
    }
    insert(element,index){
        if (index == this.count) { // 在链表尾部追加
            this.push(element)
        } else {
            const node = new DoublyNode(element)
            // 在链表头部追加
            if (index == 0) {
                if (this.head == null) { // 链表为空
                    this.head = node
                    this.tail = node
                } else {
                    const firstNode = this.head
                    this.head = node
                    firstNode.prev = node
                    node.next = firstNode 
                }
            }
            // 在链表中间插入值,
            const prevItem = this.getElementAt(index -1) // 拿到的前一项
            let current = prevItem.next
            prevItem.next = node
            node.next = current
            node.prev = prevItem
            current.prev = node

            this.count++
            return true
            // let current = this.head
            // let prev
            // for (let i = 0; i < index && index < this.count; i++) {
            //    prev = current
            //    current = current.next
            // }
            // prev.next = node
            // node.next = current
        }
        return false
    }
    indexOf(element){
        if (this.head == null) {
            return -1
        }
        let current = this.head
        let index = 0
        while (current.next != null) {
            if (this.equalsFn(current.element , element)) {
               return index
            }
            index ++
            current = current.next
        }
    }
    // getElementAt(index){ //查找所在位置的元素,和链表中的方法一样
    //     if (this.head == null) {
    //         return undefined
    //     }
    //     let current = this.head
    //     let prev
    //     for (let i = 0; i < index; i++) {
    //         current = current.next
    //     }
    //     return current
    // }
    removeAt(index){ // 从任意位置移除元素
        if (index >= 0 && index < this.count) {
            let current 
            if (index == 0) { // 移除双向链表头部的元素
                current = this.head
                this.head = current.next
            } else if (index == this.count - 1) { // 移除双向链表尾部的元素
                current = this.tail
                this.tail = current.prev
                this.tail.next = undefined
            } else { // 移除链表中间的元素,可用getElementAt()的方法
                let current = this.getElementAt(index )
                let prevItem = current.prev
                prevItem.next = current.next
                current.next.prev = prevItem
                // let prev
                // for (let i = 0; i < index ; i++) {
                //     prev = current
                //     current = current.next
                // }
                // prev.next = current.next
                // current.next.prev = prev
            }
            this.count --
            return current.element
        }
        return undefined
    }
    getHead(){
        return this.head
    }
    getTail(){
        return this.tail
    }
    clear(){
        super.clear()
        this.tail = undefined
    }
    toString(){}
    inverseToString(){}
}

5. 循环链表

循环链表和链表唯一的区别就在于 最后一个元素指向下一个元素的指针,最后一个元素的tail.next指向的是第一个元素(head),如下图所示,看图理解会更容易一些

循环链表.jpg

接下来我们创建一个循环链表的类circleLinkList,circleLinkList类仍是继承基础链表的某些方法

import LinkList from './Linklist'
import DoublyNode from './DoublyNode'
import { defaultEquals } from '../utils/index'
class CircleLinkList extends LinkList{
    constructor(equals = defaultEquals){
        super(equals)
    }
    push(element){
        const node = new DoublyNode(element)
        if (this.head == null) {
            this.head = node
        } else {
            let current = this.getElementAt(this.size() - 1)
            current.next = node
            this.tail = node
            node.next = this.head
        }
    }
    insert(element,index){
       const node = new DoublyNode(element)
       if (index > 0 && index <= this.count) {
            if (this.head == null) {
                this.head = node 
                node.prev = this.head
            } else {
                if (index == this.count) { // 最后一个元素
                    let lastItem = this.getElementAt(this.count - 1)
                   lastItem.next = node
                   node.prev = this.head
                   node.next = undefined
                } else {
                    // 插入到链表中间位置
                    let prevItem = this.getElementAt(index - 1)
                    let current = prevItem.next
                    prevItem.next = node
                    node.prev = prevItem
                    node.next = current
                }
            }
            this.count ++ 
            return true
       }
       return false
         
    }
    removeAt(index){
        if (this.head = null) {
            return undefined
        }
        let prevItem = this.getElementAt(index -1) // 前一个元素
        const result = prevItem.next
        prevItem.next = result.next
        result.prev = prevItem
        return result
    }
}

6. 有序链表

有序链表是值保持元素有序的链表结构,除了使用排序的算法外,还可以将元素插入到正确的位置确保链表的有序性

这里不再创建有序链表类了,有需要的可以自己尝试创建一下。

7. 通过链表表示栈

用双向链表更容易表示栈的数据结构,双向数据链表可以直接获取头尾元素,减少过程的消耗,他的时间复杂度和原始的Stack类实现相同,为O(1)

posted @ 2022-03-05 10:41  前端加油站  阅读(68)  评论(0编辑  收藏  举报