JavaScript數據結構之 —— 05雙向鏈表

雙向鏈表和普通鏈表的區別在於,在鏈表中,一個節點只有鏈向下一個節點的鏈接,而在雙向鏈表中,鏈接是雙向的:一個鏈向下一個元素,另一個鏈向前一個元素,如下圖所示
在這裏插入圖片描述

雙向鏈表簡介

鏈表結構總結
單向鏈表有head和next兩個屬性,雙向鏈表有head、tail、next、prev四個屬性。處理好它們的指向,相當於將它們正確地連接在一起,這樣就組成了一條鏈,這就是簡單鏈表的實現

刪除節點的原理
只要沒有引用指向該對象,無論該對象是否有引用指向其他對象,該對象都會被回收(刪除)。

雙向鏈表
既可以從頭遍歷到尾,又可以從尾遍歷到頭。也就是說鏈表連接的過程是雙向的,它的實現原理是:一個節點既有向前連接的引用,也有一個向後連接的引用。

雙向鏈表的缺點
每次在插入或刪除某個節點時,都需要處理四個引用,而不是兩個,實現起來會困難些;
相對於單向鏈表,所佔內存空間更大一些;
但是,相對於雙向鏈表的便利性而言,這些缺點微不足道。

雙向鏈表的結構
雙向鏈表不僅有head指針指向第一個節點,而且有tail指針指向最後一個節點;
每一個節點由三部分組成:item儲存數據、prev指向前一個節點、next指向後一個節點;
雙向鏈表的第一個節點的prev指向null;
雙向鏈表的最後一個節點的next指向null;

雙向鏈表常見的操作(方法)

  • append(element):向鏈表尾部添加一個新的項;
  • inset(position,element):向鏈表的特定位置插入一個新的項;
  • get(element):獲取對應位置的元素;
  • indexOf(element):返回元素在鏈表中的索引,如果鏈表中沒有元素就返回-1;
  • update(position,element):修改某個位置的元素;
  • removeAt(position):從鏈表的特定位置移除一項;
  • isEmpty():如果鏈表中不包含任何元素,返回trun,如果鏈表長度大於0則返回false;
  • size():返回鏈表包含的元素個數,與數組的length屬性類似;
  • toString():由於鏈表項使用了Node類,就需要重寫繼承自JavaScript對象默認的toString方法,讓其只輸出元素的值;
  • forwardString():返回正向遍歷節點字符串形式;
  • backwordString():返回反向遍歷的節點的字符串形式;

參數中凡是有position的都要進行越界判斷。

封裝雙向鏈表

創建雙向鏈表類

//封裝雙向鏈表類
function DoubleLinklist(){
  //封裝內部類:節點類
  function Node(data){
    this.data = data
    this.prev = null
    this.next = null
  }

  //屬性
  this.head = null
  this.tail ==null
  this.length = 0
}

append 方法

//append方法
DoubleLinklist.prototype.append = data => {
  //1.根據data創建新節點
  let newNode = new Node(data)

  //2.添加節點
  //情況1:添加的是第一個節點
  if (this.length == 0) {
    this.tail = newNode
    this.head = newNode 
  //情況2:添加的不是第一個節點
  }else {
    newNode.prev = this.tail
    this.tail.next = newNode
    this.tail = newNode
  }

  //3.length+1
  this.length += 1
}

在這裏插入圖片描述
在這裏插入圖片描述

toString 方法

//將鏈表轉變爲字符串形式
  //一.toString方法
  DoubleLinklist.prototype.toString = () => {
    return this.backwardString()
  }

  //二.forwardString方法 —— 從後向前
  DoubleLinklist.prototype.forwardString = () => {
    //1.定義變量
    let current =this.tail
    let resultString = ""

    //2.依次向前遍歷,獲取每一個節點
    while (current) {
      resultString += current.data + "--"
      current = current.prev 
    }
    return resultString
  }

  //三.backwardString方法 —— 從前向後
  DoubleLinklist.prototype.backwardString = () => {
    //1.定義變量
    let current = this.head
    let resultString = ""

    //2.依次向後遍歷,獲取每一個節點
    while (current) {
      resultString += current.data + "--"
      current = current.next
    }
    return resultString
  }

insert 方法

//insert方法
 DoubleLinklist.prototype.insert = (position, data) => {
    //1.越界判斷
    if (position < 0 || position > this.length) return false

    //2.根據data創建新的節點
    let newNode = new Node(data)

    //3.插入新節點
    //原鏈表爲空
      //情況1:插入的newNode是第一個節點
    if (this.length == 0) {
      this.head = newNode
      this.tail = newNode
      
    //原鏈表不爲空
    }else {
      //情況2:position == 0
      if (position == 0) {
        this.head.prev = newNode
        newNode.next = this.head
        this.head = newNode
        
      //情況3:position == this.length 
      } else if(position == this.length){
        this.tail.next = newNode
        newNode.prev = this.tail
        this.tail = newNode
        
        //情況4:0 < position < this.length
      }else{
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //修改pos位置前後節點變量的指向
        newNode.next = current
        newNode.prev = current.prev
        current.prev.next = newNode
        current.prev = newNode
      }
    }
    //4.length+1
    this.length += 1
    return true//返回true表示插入成功
  }

在這裏插入圖片描述

get 方法

//get方法
 DoubleLinklist.prototype.get = position => {
   //1.越界判斷
   if (position < 0 || position >= this.length) {//獲取元素時position不能等於length
     return null
   }

   //2.獲取元素 —— 遍歷優化
   let current = null
   let index = 0
   //this.length / 2 > position:從頭開始遍歷
   if ((this.length / 2) > position) {
     current = this.head
     while(index++ < position){
     current = current.next
   }
   //this.length / 2 =< position:從尾開始遍歷
   }else{
     current = this.tail
     index = this.length - 1
     while(index-- > position){
     current = current.prev
   }
   }
   return current.data
 }

indexOf 方法

//indexOf方法
  DoubleLinklist.prototype.indexOf = data => {
    //1.定義變量
    let current = this.head
    let index = 0

    //2.遍歷鏈表,查找與data相同的節點
    while(current){
      if (current.data == data) {
        return index
      }
      current = current.next
      index += 1
    }
    return -1
  } 

update 方法

//update方法
  DoubleLinklist.prototype.update = (position, newData) => {
    //1.越界判斷
    if (position < 0 || position >= this.length) {
      return false
    }

    //2.尋找正確的節點
    let current = this.head
    let index = 0
    //this.length / 2 > position:從頭開始遍歷
    if (this.length / 2 > position) {
      while(index++ < position){
      current = current.next
    }
    //this.length / 2 =< position:從尾開始遍歷
    }else{
      current = this.tail
      index = this.length - 1
      while (index -- > position) {
        current = current.prev
      }
    }

    //3.修改找到節點的data
    current.data = newData
    return true//表示成功修改
  }

removeAt 方法

//removeAt方法
 DoubleLinklist.prototype.removeAt = position => {
   //1.越界判斷
   if (position < 0 || position >= this.length) {
     return null
   }
   
   //2.刪除節點
   //當鏈表中length == 1
   //情況1:鏈表只有一個節點
   let current = this.head//定義在最上面方便以下各種情況返回current.data
   if (this.length == 1) {
     this.head = null
     this.tail = null
     
   //當鏈表中length > 1
   } else{
     //情況2:刪除第一個節點
     if (position == 0) {
       this.head.next.prev = null
       this.head = this.head.next
       
     //情況3:刪除最後一個節點
     }else if(position == this.length - 1){
       current = this.tail//該情況下返回被刪除的最後一個節點
       this.tail.prev.next = null
       this.tail = this.tail.prev
       
     }else{
     //情況4:刪除鏈表中間的節點
     // 沒有引用指向 current,它會自動銷燬
       let index = 0
       while(index++ < position){
         current = current.next
       }
       current.next.prev = current.prev
       current.prev.next = current.next
     }
   }

   //3.length -= 1
   this.length -= 1
   return current.data//返回被刪除節點的數據
 }

在這裏插入圖片描述

其他方法

  /*--------------------其他方法-------------------*/
//八.remove方法
DoubleLinklist.prototype.remove = data => {
  //1.根據data獲取下標值
  let index = this.indexOf(data)
  
  //2.根據index刪除對應位置的節點
  return this.removeAt(index)
}

//九.isEmpty方法
DoubleLinklist.prototype.isEmpty = () => {
  return this.length == 0
}

//十.size方法
DoubleLinklist.prototype.size = () => {
  return this.length
}

//十一.getHead方法:獲取鏈表的第一個元素
DoubleLinklist.prototype.getHead = () => {
  return this.head.data
}

//十二.getTail方法:獲取鏈表的最後一個元素
DoubleLinklist.prototype.getTail = () => {
  return this.tail.data
}

完整實現

//封裝雙向鏈表
function DoubleLinklist(){
  //封裝內部類:節點類
  function Node(data){
    this.data = data
    this.prev = null
    this.next = null
  }

  //屬性
  this.head = null
  this.tail ==null
  this.length = 0

  //常見的操作:方法
  //一.append方法
  DoubleLinklist.prototype.append = data => {
    //1.根據data創建新節點
    let newNode = new Node(data)

    //2.添加節點
    //情況1:添加的是第一個節點
    if (this.length == 0) {
      this.tail = newNode
      this.head = newNode 
    //情況2:添加的不是第一個節點
    }else {
      newNode.prev = this.tail
      this.tail.next = newNode
      this.tail = newNode
    }

    //3.length+1
    this.length += 1
  }

  //二.將鏈表轉變爲字符串形式
  //2.1.toString方法
  DoubleLinklist.prototype.toString = () => {
    return this.backwardString()
  }

  //2.2.forwardString方法
  DoubleLinklist.prototype.forwardString = () => {
    //1.定義變量
    let current =this.tail
    let resultString = ""

    //2.依次向前遍歷,獲取每一個節點
    while (current) {
      resultString += current.data + "--"
      current = current.prev 
    }
    return resultString
  }

  //2.3.backwardString方法
  DoubleLinklist.prototype.backwardString = () => {
    //1.定義變量
    let current = this.head
    let resultString = ""

    //2.依次向後遍歷,獲取每一個節點
    while (current) {
      resultString += current.data + "--"
      current = current.next
    }
    return resultString
  }

  //三.insert方法
  DoubleLinklist.prototype.insert = (position, data) => {
    //1.越界判斷
    if (position < 0 || position > this.length) return false

    //2.根據data創建新的節點
    let newNode = new Node(data)

    //3.插入新節點
    //原鏈表爲空
      //情況1:插入的newNode是第一個節點
    if (this.length == 0) {
      this.head = newNode
      this.tail = newNode
    //原鏈表不爲空
    }else {
      //情況2:position == 0
      if (position == 0) {
        this.head.prev = newNode
        newNode.next = this.head
        this.head = newNode
      //情況3:position == this.length 
      } else if(position == this.length){
        this.tail.next = newNode
        newNode.prev = this.tail
        this.tail = newNode
        //情況4:0 < position < this.length
      }else{
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //修改pos位置前後節點變量的指向
        newNode.next = current
        newNode.prev = current.prev
        current.prev.next = newNode
        current.prev = newNode
      }
    }
    //4.length+1
    this.length += 1
    return true//返回true表示插入成功
  }

  //四.get方法
  DoubleLinklist.prototype.get = position => {
    //1.越界判斷
    if (position < 0 || position >= this.length) {//獲取元素時position不能等於length
      return null
    }

    //2.獲取元素
    let current = null
    let index = 0
    //this.length / 2 > position:從頭開始遍歷
    if ((this.length / 2) > position) {
      current = this.head
      while(index++ < position){
      current = current.next
    }
    //this.length / 2 =< position:從尾開始遍歷
    }else{
      current = this.tail
      index = this.length - 1
      while(index-- > position){
      current = current.prev
    }
    }
    return current.data
  }

  //五.indexOf方法
  DoubleLinklist.prototype.indexOf = data => {
    //1.定義變量
    let current = this.head
    let index = 0

    //2.遍歷鏈表,查找與data相同的節點
    while(current){
      if (current.data == data) {
        return index
      }
      current = current.next
      index += 1
    }
    return -1
  } 

  //六.update方法
  DoubleLinklist.prototype.update = (position, newData) => {
    //1.越界判斷
    if (position < 0 || position >= this.length) {
      return false
    }

    //2.尋找正確的節點
    let current = this.head
    let index = 0
    //this.length / 2 > position:從頭開始遍歷
    if (this.length / 2 > position) {
      while(index++ < position){
      current = current.next
    }
    //this.length / 2 =< position:從尾開始遍歷
    }else{
      current = this.tail
      index = this.length - 1
      while (index -- > position) {
        current = current.prev
      }
    }

    //3.修改找到節點的data
    current.data = newData
    return true//表示成功修改
  }

  //七.removeAt方法
  DoubleLinklist.prototype.removeAt = position => {
    //1.越界判斷
    if (position < 0 || position >= this.length) {
      return null
    }
    
    //2.刪除節點
    //當鏈表中length == 1
    //情況1:鏈表只有一個節點
    let current = this.head//定義在最上面方便以下各種情況返回current.data
    if (this.length == 1) {
      this.head = null
      this.tail = null
    //當鏈表中length > 1
    } else{
      //情況2:刪除第一個節點
      if (position == 0) {
        this.head.next.prev = null
        this.head = this.head.next
      //情況3:刪除最後一個節點
      }else if(position == this.length - 1){
        current = this.tail//該情況下返回被刪除的最後一個節點
        this.tail.prev.next = null
        this.tail = this.tail.prev
      }else{
      //情況4:刪除鏈表中間的節點
        let index = 0
        while(index++ < position){
          current = current.next
        }
        current.next.prev = current.prev
        current.prev.next = current.next
      }
    }

    //3.length -= 1
    this.length -= 1
    return current.data//返回被刪除節點的數據
  }
  /*--------------------其他方法-------------------*/
  //八.remove方法
  DoubleLinklist.prototype.remove = data => {
    //1.根據data獲取下標值
    let index = this.indexOf(data)
    
    //2.根據index刪除對應位置的節點
    return this.removeAt(index)
  }

  //九.isEmpty方法
  DoubleLinklist.prototype.isEmpty = () => {
    return this.length == 0
  }

  //十.size方法
  DoubleLinklist.prototype.size = () => {
    return this.length
  }

  //十一.getHead方法:獲取鏈表的第一個元素
  DoubleLinklist.prototype.getHead = () => {
    return this.head.data
  }

  //十二.getTail方法:獲取鏈表的最後一個元素
  DoubleLinklist.prototype.getTail = () => {
    return this.tail.data
  }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章