JavaScript實現雙向鏈表


JavaScript實現雙向鏈表

一、雙向鏈表簡介

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

雙向鏈表的缺點:

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

雙向鏈表的結構:

image-20200227204728456

  • 雙向鏈表不僅有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():返回反向遍歷的節點的字符串形式;

二、封裝雙向鏈表類

2.0.創建雙向鏈表類

先創建雙向鏈表類DoubleLinklist,並添加基本屬性,再實現雙向鏈表的常用方法:

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

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

2.1.append(element)

代碼實現:

      //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
      }

過程詳解:

添加節點時分為多種情況:

  • 情況1:添加的是第一個節點:只需要讓head和tail都指向新節點即可;

image-20200228094847845

  • 情況2:添加的不是第一個節點,如下圖所示:只需要改變相關引用的指向即可。

    • 通過:newNode.prev = this.tail:建立指向1;
    • 通過:this.tail.next = newNode:建立指向2;
    • 通過:this.tail = newNode:建立指向3

    要注意改變變量指向的順序,最后修改tail指向,這樣未修改前tail始終指向原鏈表的最后一個節點。

image-20200228095048677

image-20200228095135301

測試代碼:

   //測試代碼
   //1.創建雙向鏈表
   let list = new DoubleLinklist()

    //2.測試append方法
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    console.log(list);

測試結果:

  • next方向:

image-20200305223911713

  • prev方向:

image-20200305224004626

2.2.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
      }

過程詳解:

三種獲取字符串的方法:toString()forwardString()backwardString()實現原理相似,僅以backWardString方法為例:

  • 定義current變量記錄當前指向的節點。首先讓current指向第一個節點,然后通過 current = current.next 依次向后遍歷。在while循環中以(current)作為條件遍歷鏈表,只要current != null就一直遍歷,由此可獲取鏈表所有節點的數據。

image-20200228100030713

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()
    
    //2.測試字符串方法   
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    console.log(list.toString());
    console.log(list.forwardString());
    console.log(list.backwardString());

測試結果:

image-20200305225437424

2.3.insert(position,element)

代碼實現:

      //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表示插入成功
      }

過程詳解:

插入節點可分為多種情況:

當原鏈表為空時

  • 情況1:插入的新節點是鏈表的第一個節點;只需要讓head和tail都指向newNode即可。

image-20200228102437899

當原鏈表不為空時

  • 情況2:當position == 0,即在鏈表的首部添加節點:如下圖所示:

image-20200228103942238

首先,通過:this.head.prev = newNode,改變指向1;

然后,通過:newNode.next = this.head,改變指向2;

最后,通過:this.head = newNode,改變指向3;

image-20200228110014565

  • 情況3:position == this.length,即在鏈表的尾部添加節點,如下圖所示:

image-20200228105207102

首先,通過:this.tail.next = newNode,改變指向1;(注意這里使用this.tail指向原鏈表最后一個節點,而不是this.head。因為當length>1時,this.head != this.tail。)

然后,通過:newNode.prev = this.tail,改變指向2;

最后,通過:this.tail = newNode,改變指向3;

image-20200228110745214

  • 情況4:0 < position < this.length,即在鏈表的中間插入新節點,假設在position = 1的位置插入,如下圖所示:

image-20200228112941682

首先,需要定義變量current按照之前的思路,通過while循環找到position位置的后一個節點,循環結束后index = position

image-20200228113257650

如下圖所示:當position = 1時,current就指向了Node2。這樣操作current就等同於間接地操作Node2,還可以通過current.prev間接獲取Node1。得到了newNode的前一個節點和后一個節點就可以通過改變它們的prev和next變量的指向來插入newNode了。

image-20200228120701923

通過:newNode.next = current,改變指向1;

通過:newNode.prev = current.prev,改變指向2;

通過:current.prev.next = newNode,改變指向3;

注意必須最后才修改current.prev的指向,不然就無法通過current.prev獲取需要操作的Node1了。

通過:current.prev = current,改變指向4;

image-20200228124931441

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()

	//2.測試insert方法
    list.insert(0, '插入鏈表的第一個元素')
    list.insert(0, '在鏈表首部插入元素')
    list.insert(1, '在鏈表中間插入元素')
    list.insert(3, '在鏈表尾部插入元素')
    console.log(list);
    alert(list)

測試結果:

image-20200228130649724

image-20200228130748735

2.4.get(position)

代碼實現:

      //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
      }

過程詳解:

定義兩個變量current和index,按照之前的思路通過while循環遍歷分別獲取當前節點和對應的索引值index,直到找到需要獲取的position位置后的一個節點,此時index = pos =x,然后return current.data即可。

如果鏈表的節點數量很多時,這種查找方式效率不高,改進方法為:

一定要通過this.length來獲取鏈表的節點數否則就會報錯。

  • 當this.length / 2 > position:從頭(head)開始遍歷;
  • 當this.length / 2 < position:從尾(tail)開始遍歷;

image-20200228144005347

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()
    
  	//2.測試get方法
    list.append('a')
    list.append('b')
    list.append('b1')
    list.append('b2')
    list.append('b3')
    list.append('b4')
    list.append('b5')
    list.append('b6')
    list.append('b7')
    console.log(list.get(0));
    console.log(list.get(7));

測試結果:

image-20200228145413524

2.5.indexOf(element)

代碼實現:

      //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
      } 

過程詳解:

以(current)作為條件,通過while循環遍歷鏈表中的所有節點(停止條件為current = null)。在遍歷每個節點時將current指向的當前節點的data和傳入的data進行比較即可。

image-20200228150427485

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()
    
    //2.測試indexOf方法
    list.append('a')
    list.append('b')
    list.append('c')
    console.log(list.indexOf('a'));
    console.log(list.indexOf('c'));

測試結果:

image-20200228150612681

2.7.update(position,element)

代碼實現:

     //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//表示成功修改
      }

過程詳解:

以(index++ < position)為條件,通過while循環遍歷鏈表中的節點(停止條件為index = position)。循環結束后,current指向需要修改的節點。

image-20200228152136284

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()
    
    //2.測試update方法
    list.append('a')
    list.append('b')
    console.log(list.update(1, 'c'));
    console.log(list);

測試結果:

image-20200228151340638

2.8.removeAt(position)

代碼實現:

     //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//返回被刪除節點的數據
      }

過程詳解:

刪除節點時有多種情況:

當鏈表的length = 1時

  • 情況1:刪除鏈表中的所有節點:只需要讓鏈表的head和tail指向null即可。

image-20200228153331976

當鏈表的length > 1時

  • 情況2:刪除鏈表中的第一個節點:

    通過:this.head.next.prev = null,改變指向1;

    通過:this.head = this.head.next,改變指向2;

    雖然Node1有引用指向其它節點,但是沒有引用指向Node1,那么Node1會被自動回收。

image-20200228162347115

  • 情況3:刪除鏈表中的最后一個節點:

    通過:this.tail.prev.next = null,修改指向1;

    通過:this.tail = this.tail.prev,修改指向2;

image-20200228161946691

  • 情況4:刪除鏈表中間的節點:

通過while循環找到需要刪除的節點,比如position = x,那么需要刪除的節點就是Node(x+1),如下圖所示:

image-20200228161648125

通過:current.next.prev = current.prev,修改指向1;

通過:current.prev.next = current.next,修改指向2;

這樣就沒有引用指向Node(x+1)了(current雖指向Node(x+1),但current時臨時變量,該方法執行完就會被銷毀),隨后Node(x+1)就會被自動刪除。

image-20200228162415044

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()	
	
	//2.測試removeAt方法
    list.append('a')
    list.append('b')
    list.append('c')
    console.log(list.removeAt(1));
    console.log(list);

測試結果:

image-20200228163935060

2.9.其他方法

其他方法包括:remove(element)、isEmpty()、size()、getHead()、getTail()

代碼實現:

  /*--------------------其他方法-------------------*/
  //八.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
  }

測試代碼:

    //測試代碼
    //1.創建雙向鏈表
    let list = new DoubleLinklist()	

/*------------其他方法的測試--------------*/
    list.append('a')
    list.append('b')
    list.append('c')
    list.append('d')
    //remove方法
    console.log(list.remove('a'));
    console.log(list);
    //isEmpty方法
    console.log(list.isEmpty());
    //size方法
    console.log(list.size());
    //getHead方法
    console.log(list.getHead());
    //getTead方法
    console.log(list.getTail());

測試結果:

image-20200228165845014

2.10.完整實現

//封裝雙向鏈表
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
  }

}

三、鏈表結構總結

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

在實際開發中鏈表使用得非常多,比如Java中的LinkList就是雙向鏈表。

3.1.注意點

  • 在鏈表中current = current.next 可以從左往右看,看成是current --> current.next,即current指向current的下一個節點。
  • 刪除節點的原理:只要沒有引用指向該對象,無論該對象是否有引用指向其他對象,該對象都會被回收(刪除)。
  • 參數中凡是有position的都要進行越界判斷。

3.2.鏈表的增刪改查

以雙向鏈表為例:鏈表的增刪改查無非就是獲取鏈表中相應的節點改變其中的prev和next兩個變量的指向

  • 情況一:只需要headtail兩個變量就可以獲取需要操作的變量(這里指的是能夠輕松獲取,當然你想通過head.next.next...或tail.prev.prev...來獲取想要的節點也可以),在這種情況下鏈表的長度length:0 <= length <=2

  • 情況二:不能靠tail和head來獲取到需要操作的變量時,可采用while循環遍歷的方式,找到需要操作的節點:

image-20200228113257650

在這種情況下,如果我們想要在鏈表的position = x的位置插入新節點,那么可以通過current獲取position的后一個節點Node(x+1),通過current.prev獲取position位置的前一個節點Node(x);之后修改Node(x+1)和Node(x)中的prev和next兩個變量的指向即可在pos=x 的位置插入新節點。

image-20200228133450822

3.3.修改鏈表引用指向

應先修改newNode引用的指向,再修改其他引用

  • 情況1:通過head和tail引用就能獲取需要操作的節點時,最后更改head或tail變量的指向(因為它們分別指向鏈表的第一個和最后一個節點,獲取其他節點時可能需要用到它們)。
  • 情況2:使用current獲取到需要操作的節點時,最后更改curren.next或current.prev的指向。因為current.next和current.prev表示的是Node(x+2)和Node(x)這兩個節點,如下圖所示,一旦變更它們的指向就無法獲取Node(x)或Node(x+2)了,

image-20200228133725909

3.4.遍歷鏈表

積累兩種遍歷思路

  • 獲取指定的position = x 位置的后一個節點和索引值:

image-20200228144005347

image-20200228113257650

循環結束后index = position = x,變量current就指向了Node(x+1),變量index的值為Node(x+1)的索引值x。

  • 遍歷鏈表中的所有節點:

image-20200228132334514

image-20200228145930043

當current.next = null時停止循環,此時current指向鏈表的最后一個節點。

參考資料:JavaScript數據結構與算法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM