鏈表與變相鏈表的實現

前言

鏈表做爲一種數據結構,它存放着有序元素的集合。元素與元素之間經過指針鏈接,所以在鏈表中添加或刪除元素只須要修改指針的指向便可,執行速度相比數組有獲得顯著的提高。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

寫在最後

  • 文中若有錯誤,歡迎在評論區指正,若是這篇文章幫到了你,歡迎點贊和關注😊
  • 本文首發於掘金,未經許可禁止轉載💌
相關文章
相關標籤/搜索