数据结构之单向链表(一)

单向链表结构

链表属于线性表的一类,与数组不同的是,单向链表是链式存储,其地址不一定连续,大致结构如图单向链表结构
链表存储的是一个一个的节点(Node),节点之间通过next属性相连,要存储的值存于value中,而链表通过head属性将这些节点串联起来

接口

通过学习,链表应该具有对外暴露以下接口

interface List<E> {
  clear: () => void // 清空链表
  add(element: E, index?: number): void // 在指定位置添加元素,默认为最后
  set(index: number, element: E): E // 设置特定位置的值
  isEmpty: () => boolean// 链表是否为空
  get: (index: number) => E// 获取特定位置的值
  remove: (index: number) => E // 删除指定位置的元素
  contains: (element: E) => boolean // 是否包含某个元素
  indexOf(element: E): number  // 获取元素的位置
}

实现

创建单向链表类

单向链表类应该具有以下属性

  • size 链表的长度
  • head 指向第一个 node

同时实现了 上面的List接口

class SingleLinkList<E> implements List<E> {
  #size: number = 0
  #head: Node<E> | null = null
  clear() {}
  add(element: E, index: number = this.#size){}
  set(index: number, element: E): E {return element}
  isEmpty(){return false}
  get(index: number): E{return (1 as E)}
  remove(index:number):E {{return (1 as E)}}
  contains (element: E) {return false}
  indexOf(element: E): number {return 0}
}

#es2022新增语法用于创建一个pravite属性,所以一会还会多两个getter用于访问这两个属性

创建节点类
class Node<E> {
  constructor(public value: E, public next: Node<E> | null) {}
}
具体方法实现
clear

clear是用来清空链表的,所以很简单,直接将sizehead初始化即可

 clear() {
    this.#head = null
    this.#size = 0
  }
isEmpty

isEmpty是用来判断链表是否为空,所以直接判断 size是否为0即可

isEmpty() {
    return this.#size === 0
  }
add

add方法用来向链表中添加元素,在只传入了一个参数的情况下,默认将元素存为链表最后,即为索引值等于size
首先要明确add方法的作用:

  1. 创建新节点
  2. 插入到指定位置

add方法涉及到了索引值,所以我们需要对传入的参数进行边界判断,由于是向链表添加元素,所以条件如下

index < 0 && index > size  // 满足这个条件就说明超出了范围

为了方便以后用到索引的方法进行判断,我将其抽离成了一个私有方法

#checkRangeForAdd(index: number) {
    if (index < 0 || index > this.#size) {
      throw new Error(`${index} out of range`)
    }
  }

我对于add方法的思路如下

  1. 首先判断index是不是等于0,如果等于0就将head指向新节点,而新节点的next就指向原来的head
  2. 如果不是0的位置,那么我们就可以获取传入索引节点的前一个元素,并且将他的next指向新节点,再将它原来的next节点作为新节点的next
  add(element: E, index: number = this.#size) {
    this.#checkRangeForAdd(index)
    if (index === 0) {
      //如果是第一个元素,就将head指向新元素就好
      this.#head = new Node<E>(element, this.#head)
    } else {
      // 如果不是第一个元素,就获取插入位置的前一个元素
      let preNode = this.#node(index - 1)
      // 将前一个元素的next作为新节点的next
      let newNode = new Node<E>(element, preNode.next)
      // 将前一个节点的next指向新节点
      preNode.next = newNode
    }
    this.#size++
  }
   /**
   * 获取索引位置的节点
   * @param index 索引
   */
  #node(index: number) {
    this.#checkRange(index) // 边界判断
    let node = this.#head as Node<E> // 拿到head节点
    for (let i = 0; i < index; i++) {
      node = node.next as Node<E>
    }
    return node
  }

node方法是我用来根据索引获取节点的方法,他从head节点开始遍历所有节点,直到索引满足要求,就返回节点

get / set

get用来获取特定位置的值,有了上面的node方法后这俩就很简单了

  set(index: number, element: E): E {
    const node = this.#node(index) // 获取当前节点
    const oldValue = node.value // 获取旧值
    node.value = element
    return oldValue
  }
   get(index: number): E {
    return this.#node(index).value
  }

结束语

剩余的方法在之后实现,数据结构还是太难了呀
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值