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


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

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

    通过数组认识到链表

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

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

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

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

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

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

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

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

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

    链表的常见操作

    创建节点类和链表类

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

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

    1. class Newnode {
    2. constructor(el) {
    3. this.data = el
    4. this.next = null
    5. }
    6. }
    7. class Linklist {
    8. constructor() {
    9. this.head = null
    10. this.length = 0
    11. }
    12. }

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

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

    代码实现:

    1. append(i) {
    2. //创建数据节点
    3. let newnode = new Newnode(i)
    4. //为空时就是头部
    5. if (this.head == null) {
    6. this.head = newnode
    7. } else {
    8. let current = this.head
    9. while (current.next) { //检测到下一个为空时,找到最后一个,跳出循环
    10. current = current.next
    11. }
    12. current.next = newnode
    13. }
    14. this.length++
    15. }

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

    代码实现:

    1. insert(position, el) {
    2. //位置是否合法
    3. if (position < 0 || position > this.length || !Number.isInteger(position)) {
    4. return false
    5. }
    6. let newnode = new Newnode(el)
    7. //position==0时
    8. if (position == 0) {
    9. if (this.head == null) { //链表为空时
    10. this.head = newnode
    11. } else {
    12. //不为空时
    13. newnode.next = this.head
    14. this.head = newnode //设为头部
    15. }
    16. this.length++
    17. } else if (position == this.length) {
    18. //在尾部
    19. append(el)
    20. } else {
    21. //在任意位置
    22. let current = this.head
    23. let index = 0
    24. while (index < position - 1) {
    25. current = current.next //得到前一个
    26. index++
    27. }
    28. newnode.next = current.next
    29. current.next = newnode
    30. this.length++
    31. }
    32. }

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

    代码实现:

    1. removeAt(position) {
    2. //位置是否合法
    3. if (position < 0 || position > this.length || !Number.isInteger(position)) {
    4. return false
    5. }
    6. //空时
    7. if (this.head == null) {
    8. return
    9. } else {
    10. if (position == 0) {
    11. let current = this.head
    12. this.head = current.next
    13. } else {
    14. //其他位置时
    15. let current = this.head
    16. let index = 0
    17. while (index < position - 1) {
    18. current = current.next
    19. index++
    20. }
    21. current.next = current.next.next
    22. }
    23. this.length--
    24. }
    25. }

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

    代码实现:

    1. indexof(el){
    2. let current = this.head
    3. let index=0
    4. while(index<this.length){
    5. if (current.data==el) {
    6. return index
    7. }else{
    8. current=current.next
    9. index++
    10. }
    11. }
    12. return -1
    13. }

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

    代码实现:

    1. remove(el){
    2. let index = this.indexof(el)
    3. this.removeAt(index)
    4. }

    toString()转换为字符串。

    代码实现:

    1. toString(){
    2. let current=this.head
    3. let index=0
    4. let str=""
    5. while(index<this.length){
    6. str+="-"+current.data
    7. index++
    8. current=current.next
    9. }
    10. str= str.substr(1)
    11. return str
    12. }

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

    双向链表

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

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

    双向链表也有一些缺陷:

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

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

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

    画出结构图:

     

    双向链表的应用

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

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

    代码实现:

    1. class Newnode{
    2. constructor(el){
    3. this.data=el
    4. this.prev=null
    5. this.next=null
    6. }
    7. }
    8. class DoubleLinklist{
    9. constructor(){
    10. this.head=null
    11. this.tail=null
    12. this.length=0
    13. }
    14. }

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

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

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

    1. //尾部添加节点
    2. append(el){
    3. let newnode=new Newnode(el)
    4. //链表为空时
    5. if (this.length==0) {
    6. this.head=newnode
    7. this.tail=newnode
    8. }else{
    9. //不为空时
    10. this.tail.next=newnode
    11. newnode.prev=this.tail
    12. this.tail=newnode //将新增的节点指向新节点
    13. }
    14. this.length++
    15. }

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

    1. insert(position,el){
    2. //判断position是否符合
    3. if (position < 0 || position > this.length || !Number.isInteger(position)) {
    4. return false
    5. }
    6. let newnode=new Newnode(el)
    7. if (position==0) {
    8. if (this.length==0) {
    9. //链表为空时
    10. this.head=newnode
    11. this.tail=newnode
    12. }else{
    13. //不为空时
    14. this.head.prev=newnode
    15. newnode.next=this.head
    16. this.head=newnode
    17. }
    18. }else if(position==this.length-1){
    19. this.tail.next=newnode
    20. newnode.prev=this.tail
    21. this.tail=newnode
    22. }else{
    23. // 任意位置添加
    24. let current=this.head
    25. let index=0
    26. while(index1){
    27. current=current.next
    28. index++
    29. }
    30. newnode.prev=current
    31. newnode.next=current.next
    32. current.next=newnode
    33. newnode.next.prev=newnode
    34. }
    35. this.length++
    36. }

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

    1. removeAt(position) {
    2. //位置是否合法
    3. if (position < 0 || position > this.length || !Number.isInteger(position)) {
    4. return false
    5. }
    6. //空时
    7. if (this.length == 0) {
    8. return
    9. } else {
    10. if (position == 0) {
    11. //链首
    12. if (this.length==1) {
    13. this.head=null
    14. this.tail=null
    15. }else{
    16. let current = this.head
    17. current.next.prev=null
    18. this.head = current.next
    19. }
    20. }else if(position=this.length-1) {
    21. //链尾
    22. let current = this.tail
    23. current.prev.next=null
    24. this.tail=current.prev
    25. }
    26. else {
    27. //其他位置时
    28. let current = this.head
    29. let index = 0
    30. while (index < position - 1) {
    31. current = current.next
    32. index++
    33. }
    34. current.next = current.next.next
    35. current.next.prev=current
    36. }
    37. this.length--
    38. }
    39. }

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

    1. indexof(el){
    2. let current = this.head
    3. let index=0
    4. while(index<this.length){
    5. if (current.data==el) {
    6. return index
    7. }else{
    8. current=current.next
    9. index++
    10. }
    11. }
    12. return -1
    13. }

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

    1. remove(el){
    2. let index=this.indexof(el)
    3. this.removeAt(index)
    4. }

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

    1. // 正向
    2. toStringAfter(){
    3. let current=this.head
    4. let index=0
    5. let str=""
    6. while(index<this.length){
    7. str+="-"+current.data
    8. index++
    9. current=current.next
    10. }
    11. str= str.substr(1)
    12. return str
    13. }
    14. //逆向
    15. toStringPrevious(){
    16. let current=this.tail
    17. let index=this.length-1
    18. let str=""
    19. while(index>=0){
    20. str+="-"+current.data
    21. index--
    22. current=current.prev
    23. }
    24. str= str.substr(1)
    25. return str
    26. }

    循环链表

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

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

    图解:

     

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

    图:

     

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

  • 相关阅读:
    在Ubuntu18.04安装适合jdk8的eclipse
    【OSPF Loading、FULL状态与display ospf peer brief命令、OSPF的数据库讲解】
    移动端表格分页uni-app
    【PL理论】(24) C- 语言:有块的作用域 | 更新的语法 | 新的语义域 | 环境 vs. 内存
    【工业设计】产品的第一印象是什么?
    SpringCloudAlibaba系列微服务搭建笔记二-RestTemplate+Ribbon
    EPICS stream模块使用示例 -- 基于字符串协议的通信
    高德地图开发实战案例:实现信息弹出框的富文本展示效果
    论文阅读 (100):Simple Black-box Adversarial Attacks (2019ICML)
    java计算机毕业设计ssm+Java EE陕西农产品网络交易平台-农产品和特产商城(源码+系统+mysql数据库+Lw文档)
  • 原文地址:https://blog.csdn.net/m0_59345890/article/details/126484468