链表与变相链表的实现

前言

链表做为一种数据结构,它存放着有序元素的集合。元素与元素之间经过指针链接,所以在链表中添加或删除元素只须要修改指针的指向便可,执行速度相比数组有获得显著的提高。node

现实生活中也有许多使用到链表的例子,例如兔子舞,每一个人勾肩搭背组合而成,其中人至关于链表中的元素,勾肩搭背的手至关于连接每一个人的指针,在队列中加入一我的,只须要找到想加入的点,断开链接,插入一我的再从新链接起来。git

本文将详解链表以及链表其余变相的实现思路并使用TypeScript将其实现,欢迎各位感兴趣的开发者阅读本文。github

链表的实现

本文主要讲解链表的代码实现,对链表还不是很了解的开发者能够移步个人另外一篇文章:数据结构:链表的基础知识web

链表与数组的区别

在实现链表以前,咱们先来看看数组与链表的区别都有哪些。数组

数组多是最经常使用的一种数据结构,每种语言都实现了数组,元素在内存中是连续存放的,所以数组提供了一个很是方便的[]方法来访问其元素。数据结构

链表存储有序元素的集合,链表中的元素在内存中并不是连续存放,每一个元素由一个存储元素自己的结点和一个指向下一个元素的指针组成,所以增长或删除链表内的元素只须要改变指针指向便可。编辑器

咱们来总结下链表与数组各自的优势:函数

链表的优势:元素经过指针链接,改变链表内的元素只须要找到元素改变其指针便可,所以数据须要频繁修改时,使用链表做为数据结构是最优解决方案。 数组的优势:元素连续存放在内存中,访问元素能够直接经过元素下标来访问,所以数据须要频繁查询时,使用数组做为其数据结构是最优解决方案。post

上面咱们总结了它们的优势,接下来咱们来看下它们各自的缺点:性能

链表的缺点:因为链表是经过指针链接的,咱们只能直接拿到链表头部的元素,要想访问其余元素须要从头遍历整个链表才能找到咱们想找的元素。所以数据须要频繁查询时,使用链表将拔苗助长。 数组的缺点:因为元素是连续存放在内存中的,改变数组内的元素时,须要调整其余元素的位置。所以数据须要频繁修改时,使用数组将拔苗助长。

实现思路

链表是由指针将元素链接到一块儿,根据链表的特性,咱们能够知道要实现一个链表必须必备如下方法:

  • 链表尾部添加元素
    • 声明一个结点变量,以添加的元素为参数,生成一个结点,将生成的结点赋值给接待你变量。
    • 判断链表头部元素是否为null,若是为null直接将链表头部赋值为结点变量
    • 从链表头部开始遍历链表内的元素,直至链表的下一个元素指向null
    • 向null区域追加结点变量
    • 链表长度自增
  • 移除链表指定位置的元素
    • 判断当前要删除的位置是否为链表头部的元素,若是为链表头部元素则将当前链表头部元素指向当前链表头部元素中的next元素
    • 从链表头部开始遍历链表内的元素,直至找到目标结点和目标结点的上一个结点
    • 将目标结点元素指向目标结点的下一个结点元素
    • 链表长度自减,返回当前删除的元素内容
  • 获取链表指定位置的元素
    • 声明一个变量,用于接收遍历到的结点,默认值为链表头部元素。
    • 从链表头部开始遍历元素,遍历至要获取的元素位置。
    • 返回遍历到的结点数据
  • 链表任意位置插入元素
    • 声明结点变量,将当前要插入的元素做为参数生成结点,将生成的结点赋值给结点变量
    • 判断要插入的元素位置是否为0,将结点变量的下一个元素指向链表的头部元素,链表头部元素赋值为结点变量
    • 获取要插入位置的上一个结点元素
    • 将结点变量的下一个元素指向目标结点
    • 将目标结点位置的元素赋值为结点变量
    • 链表长度自增,返回true
  • 根据元素获取该元素在链表中的位置
    • 声明一个变量用于接收遍历到的结点
    • 从链表头部开始遍历,判断当前遍历到的结点与目标结点是否相等
    • 若是相等,直接返回当前遍历的索引
    • 不然接收链表的下一个结点,继续执行遍历,直至遍历完链表中的全部元素为止。
    • 链表的全部元素遍历完成后,仍没有发现与目标结点匹配的元素,元素不存在返回-1
  • 移除链表中的指定元素
    • 获取目标元素在链表中的索引
    • 调用移除链表指定位置元素方法,将获取到的索引做为参数传给方法
  • 获取量表长度
    • 返回链表的长度便可
  • 判断链表是否为空
    • 调用获取链表长度方法,返回获取到的值
  • 获取链表头部元素
    • 返回当前链表头部元素
  • 获取链表中全部元素
    • 声明字符串对象变量,用于拼接获取到的元素
    • 声明一个元素变量用于接收获取到的元素
    • 变量链表内的全部元素
    • 字符串对象变量使用","拼接元素变量获取到的元素
    • 元素变量赋值其下一个元素,继续下一轮遍历。直至全部元素遍历完为至。

实现代码

通过上述分析后,咱们知道了链表的实现思路,接下来咱们就将上述思路转化为代码:

  • 实现Node类,由于链表中每一个元素是经过结点的形式来存储的,所以咱们须要一个实现一个node类,为了便于复用咱们建立一个utils文件夹,将其放在里面。
  • 新建linked-list-models.ts文件用于存放链表的相关公用类,实现Node类
// 助手类: 用于表示链表中的第一个以及其余元素
export class Node<T>{  element: T;  next: any;  // 默认传一个元素进来  constructor (element: T) {  this.element = element;  this.next = undefined;  } } 复制代码
  • 新建Util.ts文件,用于存放一些经常使用函数,此处咱们实现一个默认验证函数,实现根据元素获取元素所在链表的位置时须要用到。
// 默认验证函数
export function defaultEquals(a: any,b: any) {  return a === b; } 复制代码
  • 新建LinkedList.ts文件,用于实现链表,在文件中导入咱们刚才写好的函数和类
// @ts-ignore
import {defaultEquals} from "../../utils/Util.ts"; // @ts-ignore import {Node} from "../../utils/linked-list-models.ts"; 复制代码
  • 在链表类内部的构造器中,定义实现链表所须要的变量。
// 定义验证函数要传的参数和返回结果
 interface equalsFnType<T> {  (a: T,b: T) : boolean;  }  // 声明链表内须要的变量并定义其类型  private count: number;  private next: any;  private equalsFn: equalsFnType<T>;  private head: any;   constructor(equalsFn = defaultEquals) {  // 初始化链表内部变量  this.count = 0;  this.next = undefined;  this.equalsFn = equalsFn;  this.head = null;  } 复制代码
  • 实现向链表末尾插入元素函数( push)
// 链表尾部添加元素
 push(element: T) {  // 声明结点变量,将元素看成参数传入生成结点  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;  }  // 此时已经获得了链表的最后一个元素(null),将链表的下一个元素赋值为结点变量。  current.next = node;  }  // 链表长度自增  this.count++;  } 复制代码
  • 实现移除链表指定位置元素函数( removeAt)
removeAt(index: number) {
 // 边界判断: 参数是否有效  if(index >= 0 && index < this.count){  // 获取当前链表头部元素  let current = this.head;  // 移除第一项  if(index === 0){  this.head = current.next;  }else{  // 获取目标参数上一个结点  let previous = this.getElementAt(index - 1);  // 当前结点指向目标结点  current = previous.next;  /**  * 目标结点元素已找到  * previous.next指向目标结点  * current.next指向undefined  * previous.next指向current.next即删除目标结点的元素  */  previous.next = current.next;  }  // 链表长度自减  this.count--;  // 返回当前删除的目标结点  return current.element  }  return undefined;  } 复制代码
  • 实现获取链表指定位置结点函数( getElementAt)
getElementAt(index: number) {
 // 参数校验  if(index >= 0 && index <= this.count){  // 获取链表头部元素  let current = this.head;  // 从链表头部遍历至目标结点位置  for (let i = 0; i < index && current!=null; i++){  // 当前结点指向下一个目标结点  current = current.next;  }  // 返回目标结点数据  return current;  }  return undefined;  } 复制代码
  • 实现向链表中插入元素函数( insert)
insert(element: T, index: number) {
 // 参数有效性判断  if(index >= 0 && index <= this.count){  // 声明结点变量,将当前要插入的元素做为参数生成结点  const node = new Node(element);  // 第一个位置添加元素  if(index === 0){  // 将结点变量(node)的下一个元素指向链表的头部元素  node.next = this.head;  // 链表头部元素赋值为结点变量  this.head = node;  }else {  // 获取目标结点的上一个结点  const previous = this.getElementAt(index - 1);  // 将结点变量的下一个元素指向目标结点  node.next = previous.next;  /**  * 此时node中当前结点为要插入的值  * next为原位置处的结点  * 所以将当前结点赋值为node,就完成告终点插入操做  */  previous.next = node;  }  // 链表长度自增  this.count++;  return true;  }  return false;  } 复制代码
  • 实现根据元素获取其在链表中的索引函数( indexOf)
indexOf(element: T) {
 // 获取链表顶部元素  let current = this.head;  // 遍历链表内的元素  for (let i = 0; i < this.count && current!=null; i++){  // 判断当前链表中的结点与目标结点是否相等  if (this.equalsFn(element,current.element)){  // 返回索引  return i;  }  // 当前结点指向下一个结点  current = current.next;  }  // 目标元素不存在  return -1;  } 复制代码
  • 实现移除链表指定元素函数( remove)
remove(element: T) {
 // 获取element的索引,移除索引位置的元素  this.removeAt(this.indexOf(element))  } 复制代码
  • 实现获取链表长度( size)、链表头部元素( getHead)、链表判空( isEmpty)
// 获取链表长度
 size() {  return this.count;  }   // 判断链表是否为空  isEmpty() {  return this.size() === 0;  }   // 获取链表头部元素  getHead() {  return this.head;  } 复制代码
  • 实现链表内全部元素转字符串函数
toString(){
 if (this.head == null){  return "";  }  let objString = `${this.head.element}`;  // 获取链表顶点的下一个结点  let current = this.head.next;  // 遍历链表中的全部结点  for (let i = 1; i < this.size() && current!=null; i++){  // 将当前结点的元素拼接到最终要生成的字符串对象中  objString = `${objString}, ${current.element}`;  // 当前结点指向链表的下一个元素  current = current.next;  }  return objString;  } 复制代码

完整代码请移步: LinkedList.ts

编写测试代码

链表实现后,接下来咱们来测试下链表中的每一个函数是否正常工做

const linkedList = new LinkedList();
linkedList.push(12); linkedList.push(13); linkedList.push(14); linkedList.push(15); linkedList.push(16); linkedList.push(17); linkedList.push(18); linkedList.push(19); // 移除索引为2的元素 linkedList.removeAt(2); // 获取0号元素 console.log(linkedList.getElementAt(0)); // 查找19在链表中的位置 console.log(linkedList.indexOf(19)); // 在2号位置添加22元素 linkedList.insert(22,2); // 获取链表中的全部元素 console.log(linkedList.toString()); 复制代码

完整代码请移步:LinkedListTest.js dd6b1322dce66557a21073763da0650c

双向链表的实现

链表有多种不一样的类型,双向链表就是其中一种,接下来咱们来说解双向链表的实现。

实现以前咱们先来看看双向链表与普通链表的区别

  • 链表内的的结点只能连接它的下一个结点
  • 双向链表的连接是双向的,双向链表内的结点一个连接下一个元素,另外一个连接上一个元素。

说完他们的区别后,咱们来看看双向链表的优势:双向链表相比普通链表多了一个指针,这个指针指向链表中元素的上一个元素,所以咱们能够从链表的尾部开始遍历元素对链表进行操做,假设咱们要删除链表中的某个元素,这个元素的位置靠近链表的末尾,咱们就能够从链表的末尾来找这个元素,而链表只能从其头部开始找这个元素,此时双向链表的性能相比链表会有很大的提高,由于它须要遍历的元素少,时间复杂度低。

实现思路

咱们拿双向链表和链表进行比对后发现,双向链表是在链表的基础上加多了一个指针(prev)的维护,所以咱们能够继承链表,重写与链表不一样的相关函数。

双向链表须要重写的函数有:尾部插入元素(push)、任意位置插入元素(insert)、任意位置移除元素(removeAt)。

接下来咱们来捋一下,上述须要重写函数的实现思路:

  • 尾部插入元素( push)
    • 建立双向链表辅助结点( node)
    • 判断链表的头部是否为空,若是为空将链表头部和尾部都指向node
    • 链表头部不为空时,将链表尾部结点中的 next指向node,将node结点中的 prev指向当前链表尾部元素(this.tail)。
    • 将当前链表尾部元素(this.tail)指向node
    • 链表长度自增
  • 链表任意位置插入元素( insert)
    • 函数须要的参数:要插入的结点( element),要插入的位置( index)
    • 对index参数进行有效性判断,index必须大于等于0且小于等于当前链表的长度,不然就返回undefined
    • 建立双向链表辅助结点( node
    • 声明链表元素辅助变量( current),默认指向当前链表头部( this.head)
    • 向链表中插入元素分为三种状况:链表头部(index = 0)、链表尾部(index = this.count)、其余位置
    • index = 0时,即向链表头部插入元素,分为两种状况
      • 链表头部为null,直接调用push函数便可
      • 链表头部不为null,将node结点中的next指向头部元素,current结点中的prev指向node,当前链表头部(this.head)指向node结点
    • index = this.count时,即向链表尾部插入元素
      • current指向当前链表尾部元素
      • current结点中的next指向node
      • node结点中的prev指向current
      • 当前链表尾部元素指向node
    • index为其余数字时,即向链表的其余位置插入元素
      • 声明previous变量,接收链表中要插入位置的元素
      • current指向previous中的next
      • node中的next指向current
      • previous中的nextnext指向node
      • current中的prev指向node
      • node中的prev指向previous
    • 链表长度自增,返回true。
  • 任意位置移除元素
    • 参数有效性判断,要删除的位置参数必须大于等于0且小于等于当前链表的长度
    • 声明链表元素辅助变量(current),默认指向链表头部
    • 移除链表中的元素分为三种状况:链表头部(index = 0)、链表尾部(index = this.count - 1)、其余位置
    • index = 0时,即删除链表头部元素
      • 当前头部元素指向指向current中的next
      • 判断链表长度是否为1,若是为1则将当前链表末尾元素指向undefined
      • 链表长度不为1,将链表头部中的prev指向undefined
    • idnex = this.count - 1时,即删除链表尾部元素
      • current指向当前链表的末尾元素
      • 当前链表的末尾元素指向current中的prev
      • 当前链表末尾元素中的next指向undefined
    • index为其余数字时,即删除链表其余位置元素
      • current指向当前要删除位置的元素
      • 声明previous变量,将其指向current中的prev
      • previous中的next指向current中的next
      • current中的next元素里的prev指向previous
    • 链表长度自减,返回当前要移除的元素

实现代码

咱们已经捋清了实现思路,接下来咱们将上述实现思路转换为代码:

实现双向链表以前,咱们须要对链表的辅助类进行修改。

  • 在linked-list-models.ts中添加DoublyNode类,继承Node类
export class DoublyNode<T> extends Node<T>{
 prev: any;   constructor(element: T, next?: any, prev?: any) {  // 调用Node类的构造函数  super(element,next);  // 新增prev属性,指向链表元素的上一个元素  this.prev = prev;  } } 复制代码
  • 新建DoublyLinkedList.ts文件,用于实现双向链表
  • 声明DoublyLinkedList类,继承LinkedList类
export default class DoublyLinkedList extends LinkedList{
 } 复制代码
  • 类内部的构造函数中声明实现双向链表须要使用的变量
private tail: any;
 constructor(equalsFn = defaultEquals) {  // 调用Node类的构造函数  super(equalsFn);  // 新增属性,用于指向链表的最后一个元素  this.tail = undefined;  } 复制代码
  • 重写链表尾部插入元素函数( push
push(element: T) {
 // 建立双向链表辅助结点  const node = new DoublyNode(element);  if (this.head == null){  // 链表头部为空,头部和尾部都指向node  this.head = node;  this.tail = node;  }else{  // 将链表尾部结点中的next指向node  this.tail.next = node;  // 将node结点中的prev指向当前链表尾部元素  node.prev = this.tail;  // 当前链表末尾元素指向node  this.tail = node;  }  // 链表长度自增  this.count++;  } 复制代码
  • 重写链表任意位置插入元素函数( insert)
insert(element: T, index: number) {
 // 参数有效性判断  if(index >=0 && index <= this.count){  // 建立结点  const node = new DoublyNode(element);  // 声明链表元素辅助变量(current),默认指向当前链表头部(this.head)  let current = this.head;   // 链表头部添加元素  if(index === 0){  // 链表头部为空  if(this.head == null){  // 调用push方法  this.push(element);  }else{  // 不为空,将node.next指向当前头部元素  node.next = this.head;  // 链表头部的元素结点中上一个位置指向node  current.prev = node;  // 头部元素指向node  this.head = node;  }  }else if(index === this.count){  // 链表尾部添加元素,链表元素辅助变量指向挡脸链表尾部元素  current = this.tail;  // 链表元素辅助变量结点中的下一个元素指向node  current.next = node;  // node结点中的prev指向current  node.prev = current;  // 当前链表尾部元素指向node  this.tail = node;  }else{  // 链表的其余位置插入元素  const previous = super.getElementAt(index - 1);  // 元素变量指向目标结点  current = previous.next;  // node的下一个指向目标结点位置的元素  node.next = current;  // 目标结点指向结点变量  previous.next = node;  // 目标结点的上一个结点指向结点变量  current.prev = node;  // 结点插入完毕,调整结点的上一个指针指向  node.prev = previous;  }  // 链表长度自增  this.count++;  // 返回true  return true;  }  return false  } 复制代码
  • 重写移除链表任意位置元素( removeAt)
removeAt(index: number): any {
 // 参数有效性判断  if(index >=0 && index < this.count){  // current变量指向链表头部  let current = this.head;  if(index === 0){  this.head = current.next;  if(this.count === 1){  // 链表长度为1,直接将链表的末尾元素指向设为undefined  this.tail = undefined;  }else{  // 将链表头部的上一个元素指向undefined  this.head.prev = undefined;  }  }else if(index === this.count - 1){  // 链表末尾移除元素  current = this.tail;  // 链表末尾元素指向其上一个元素  this.tail = current.prev;  // 链表末尾的下一个元素设为undefined  this.tail.next = undefined;  }else{  // 双向链表其余位置移除元素  current = super.getElementAt(index);  // 获取当前要移除元素的上一个元素  const previous = current.prev;  // 目标元素的下一个元素指向当前要移除元素的下一个元素  previous.next = current.next;  // 当前要移除元素的下一个元素指向要移除元素的上一个元素  current.next.prev= previous;  }  // 链表长度自减  this.count--;  // 返回当前要移除的元素  return current.element;  }  return undefined;  } 复制代码
  • 实现获取链表尾部元素( getTail)
getTail(){
 return this.tail;  } 复制代码
  • 重写清空函数( clear)
clear() {
 super.clear();  this.tail = undefined;  } 复制代码
  • 实现从链表尾部向链表头部获取链表内全部元素函数( inserseToString)
inverseToString() {
 if(this.tail == null){  return "";  }  let objString = `${this.tail.element}`;  // 获取链表尾部元素的上一个元素  let previous = this.tail.prev;  while (previous!=null){  // 将当前获取到的链表元素拼接至链表字符串对象中  objString = `${objString}, ${previous.element}`;  // 获取当前链表尾部元素的上一个元素  previous = previous.prev;  }  return objString;  } 复制代码

完整代码请移步:DoublyLinkedList.ts

编写测试代码

双向链表实现后,咱们测试下双线链表中的函数是否都正常工做。

const doublyLinkedList = new DoublyLinkedList();
// 双向链表尾部插入元素 doublyLinkedList.push(12); doublyLinkedList.push(14); doublyLinkedList.push(16); // 双向链表任意位置插入元素 doublyLinkedList.insert(13,1); doublyLinkedList.insert(11,0); doublyLinkedList.insert(14,4); //移除指定位置元素 doublyLinkedList.removeAt(4); doublyLinkedList.insert(15,4); // 删除链表中的元素 doublyLinkedList.remove(16); console.log(doublyLinkedList.toString()); // 获取链表尾部元素 console.log(doublyLinkedList.getTail()); console.log(doublyLinkedList.inverseToString());  // 获取链表长度 console.log("链表长度",doublyLinkedList.size()) doublyLinkedList.removeAt(4);  // 清空链表 doublyLinkedList.clear(); console.log(doublyLinkedList.isEmpty()); 复制代码

完整代码请移步:DoublyLinkedListTest.js 536d8e1471591301ce174edf14676e73

循环链表的实现

循环链表也属于链表的一种变体,它与链表的惟一区别在于,最后一个元素指向链表头部元素,并不是undefined。

实现思路

循环链表相对于链表,改动地方较少,在首、尾插入或删除元素时,须要更改其指针指向,所以咱们只须要继承链表,而后重写插入和移除方法便可。

  • 重写插入方法(insert)

    • 插入位置参数(index)有效性判断,index必须大于等于0且小于等于链表成都
    • 声明结点变量(node),将当前要插入的元素放进Node结点中
    • 声明链表元素变量(current),默认指向当前链表头部元素(this.head)
    • 向链表中插入元素有两种状况:链表头部插入元素、链表其余位置插入元素
    • index = 0,即链表头部插入元素,分为两种状况:
      • 链表头部为null时,则将当前头部元素指向node,node结点中的next指向当前头部元素。
      • 链表头部不为null时,将node中的next指向current
        • current指向链表的末尾元素
        • 链表头部指向node
        • current中的next指向链表头部
    • index不为0,即链表其余位置插入元素,与链表的写法一致
  • 重写移除方法(removeAt)

    • 移除位置参数(index)有效性判断,index必须大于等于0且小雨链表长度
    • 移除链表中的元素分为2种状况:链表头部移除元素、链表其余位置移除元素
    • index = 0,即链表头部移除元素,分为两种状况
      • 链表长度为1,直接将链表头部指向undefined
      • 链表长度不为1
        • 声明变量removed,用于保存链表头部元素,将其从循环链表中移除
        • current指向链表末尾元素
        • 链表头部指向链表头部元素中的next
        • 链表尾部元素中的next指向新的链表头部
        • 更新current的引用,将其指向removed,用于返回当前移除的元素值
    • index不为0,即链表其余位置插入元素,与链表的写法一致。

实现代码

咱们捋清思路后,将上述思路转化为代码

  • 新建CircularLinkedList.ts文件,用于实现循环链表类
  • 声明CircularLinkedList类并继承LinkedList
export default class CircularLinkedList<T> extends LinkedList<T>{
 } 复制代码
  • 构造器中初始化
constructor(equalsFn = defaultEquals) {
 super(equalsFn);  } 复制代码
  • 重写链表中插入元素函数( insert)
insert(element: T, index: number) {
 if(index >= 0 && index <= this.count){  // 声明结点变量  const node = new Node(element);  // 声明链表元素变量,默认指向当前链表头部元素  let current = this.head;  if(index === 0){  // 链表头部添加元素  if(this.head == null){  // 链表头部为空  this.head = node;  // 链表的最后一个结点指向链表头部  node.next = this.head;  }else{  // 链表头部不为空,node中的next指向当前链表头部  node.next = current;  // 确保最后一个元素指向新添加的元素,current指向当前元素的最后一个元素  current = this.getElementAt(this.size());  // 更新最后一个元素  this.head = node;  current.next = this.head;  }  }else{  // 链表其余位置插入元素  const previous = this.getElementAt(index - 1);  node.next = previous.next;  previous.next = node;  }  this.count++;  return true;  }  return false;  } 复制代码
  • 重写移除链表元素函数( removeAt)
removeAt(index: number): any {
 if(index >= 0 && index < this.count){  let current = this.head;  if (index === 0){  if(this.size() === 1){  //链表长度为1直接将链表头部指向undefined  this.head = undefined;  }else{  // 链表长度不为1,保存链表头部元素,将其从循环链表中移除  const removed = this.head;  // 链表元素变量指向链表尾部  current = this.getElementAt(this.size() - 1);  // 链表头部指向链表头部元素中的next  this.head = this.head.next;  // 链表尾部元素中的next指向新的链表头部  current.next = this.head;  // 更新链表元素的引用,用于返回当前移除的值  current = removed;  }  }else{  const previous = this.getElementAt(index - 1);  current = previous.next;  previous.next = current.next;  }  this.count--;  return current.element;  }  return undefined;  } 复制代码

完整代码请移步: CircularLinkedList.ts

编写测试代码

循环链表实现后,咱们来测试下上述代码是否正常运行

const circularLinkedList = new CircularLinkedList();
circularLinkedList.push(11); circularLinkedList.push(12); circularLinkedList.push(13); // 循环链表的0号位置插入元素 circularLinkedList.insert(10,0); console.log(circularLinkedList.toString()); // 获取链表的最后一个元素 console.log(circularLinkedList.getElementAt(3)) 复制代码

完整代码请移步: CircularLinkedListTest.js 0ce42f6ad2c17ca88cfc63d3557b1142

有序链表的实现

有序链表也属于链表的一种变相实现,它不一样于链表的是,插入链表的元素会经过一个元素比对函数,对要插入的元素和链表内的元素进行比较,将要插入的元素放到链表合适的位置。

实现思路

由于有序链表属于链表的一种变相,因此咱们能够继承链表,只须要重写链表的插入函数实现获取插入元素正确位置函数便可。

  • 实现获取插入元素正确位置函数( getIndexNextSortedElement
    • 声明链表元素变量,默认指向当前链表的头部元素
    • 遍历整个链表,直至找到须要插入的元素位置
    • 将要插入的元素和遍历到链表元素进行大小比较,计算出插入位置
    • 若是整个链表遍历完后,仍没找到合适的位置则直接返回链表的末尾位置
  • 重写插入元素函数( insert)
    • 若是链表为空则直接调用往链表的0号位置插入元素
    • 链表不为空,则调用getIndexNextSortedElement函数计算出正确的插入位置
    • 调用insert函数将元素插入正确的位置

实现代码

咱们有了实现思路,接下来咱们将上述思路转化为代码:

  • 新建OrderedList.ts文件,用于实现有序链表
  • 声明OrderedList类,继承LinkedList类
export default class OrderedList<T> extends LinkedList<T>{
 } 复制代码
  • 声明Compare对象和defaultCompare函数,调用时若是不传比较函数时,用于比较要插入元素和链表中元素的大小
const Compare = {
 LESS_THAN: -1,  BIGGER_THAN: 1 }  // 比较两个元素大小,若是a < b则返回-1,不然返回1 function defaultCompare(a: any, b: any) {  if(a === b){  return 0;  }  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN; } 复制代码
  • 在构造器,初始化相关变量
constructor(equalsFn = defaultEquals, compareFn = defaultCompare) {
 super(equalsFn);  this.compareFn = compareFn;  } 复制代码
  • 实现获取插入元素正确位置函数( getIndexNextSortedElement)
getIndexNextSortedElement(element: T) {
 let current = this.head;  let i = 0;  // 遍历整个链表,直至找到须要插入元素的位置  for (; i < this.size() && current; i++) {  // 用compareFn函数比较传入构造函数的元素  const comp = this.compareFn(element, current.element);  // 要插入小于current的元素时,咱们就找到了插入元素的位置  if (comp === Compare.LESS_THAN) {  return i;  }  // 继续下一轮遍历  current = current.next;  }  // 迭代完全部的元素没有找到符合条件的,则返回链表的最后一个元素位置  return i;  } 复制代码
  • 重写插入函数(insert)
insert(element: T, index: number = 0): boolean {
 if(this.isEmpty()){  // 链表为空直接调用父级的insert方法往0号元素插入元素  return super.insert(element, 0);  }  // 链表不为空,获取插入元素的正确位置  const pos = this.getIndexNextSortedElement(element);  // 获得位置后调用父级的插入方法往正确位置插入元素  return super.insert(element,pos);  } 复制代码

完整代码请移步: OrderedList.ts

编写测试代码

咱们来测试下上面写的有序链表内的函数是否都正常工做

const orderedList = new OrderedList();
orderedList.insert(12); orderedList.insert(11); orderedList.insert(18); orderedList.insert(1); console.log(orderedList.toString()); 复制代码

完整代码请移步:OrderedListTest.js d6a345119fd37e316c0d2253fc5bd5dd

写在最后

  • 文中若有错误,欢迎在评论区指正,若是这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于掘金,未经许可禁止转载💌
相关文章
相关标签/搜索