JSのデータ構造とアルゴリズムのリンクリスト構造

リンクリスト構造を知る

配列に対するリンク リストの利点

  • メモリ空間は連続している必要がなく、コンピュータのメモリを最大限に活用して柔軟な動的メモリ管理を実現します。

  • リンクされたリストは作成時にサイズを決定する必要がなく、サイズは無制限に拡張できます。

  • データの挿入および削除時のリンク リストの時間計算量は O(1) に達する可能性があり、配列よりもはるかに効率的です。

配列と比較したリンクリストの欠点

  • リンクされたリスト内の要素にアクセスするには、最初から開始する必要があり、最初の要素をスキップして要素にアクセスすることはできません。

  • 添字を介して要素に直接アクセスすることはできず、対応する要素が見つかるまで先頭から 1 つずつアクセスする必要があります。

  • 次のノードに行くのは簡単ですが前のノードに戻るのは困難です

リンクされたリストと配列の実装メカニズムはまったく異なりますリンクされたリストの各要素は、要素自体を格納するノードと、次の要素を指す参照(一部の言語ではポインタまたは接続と呼ばれます) で構成されます。機関車と同様に、1 台の車両は乗客 (データ) を運び、ノードを介して別の車両に接続します。

  • head 属性は、リンクされたリストの最初のノードを指します。

  • リンクされたリストの最後のノードは null を指します。

  • リンクされたリストにノードがない場合、head は直接 null を指します。

リンクリストでの共通操作

  • append(element): リンクされたリストの末尾に新しい項目を追加します。

  • insert(position, element): リンクされたリストの特定の位置に新しい項目を挿入します。

  • get(position): 対応する位置にある要素を取得します。

  • IndexOf(element): リンクされたリスト内の要素のインデックスを返します。リンクされたリストに要素がない場合は -1 を返します。

  • update(position, element): 特定の位置にある要素を変更します。

  • RemoveAt(position): リンクされたリストの特定の位置から項目を削除します。

  • 削除(要素): リンクされたリストから項目を削除します。

  • isEmpty(): リンク リストに要素が含まれていない場合は trun を返し、リンク リストの長さが 0 より大きい場合は false を返します。

  • size(): 配列の長さ属性と同様に、リンクされたリストに含まれる要素の数を返します。

  • toString(): リンクされたリスト項目は Node クラスを使用するため、JavaScript オブジェクトから継承されたデフォルトの toString メソッドを書き換えて、要素の値のみを出力するようにする必要があります。

通常、position の値は、position が指す位置にある次のノードを示します。位置の値がインデックスの値と等しい場合 (位置 = インデックス = 1 など)、両方とも Node2 を表します。

jsは一方向リンクリストクラスをカプセル化します

単一リンクリストクラスを作成する

まず一方向リンク リスト クラス Linklist を作成し、基本属性を追加してから、一方向リンク リストの共通メソッドを実装します。

    // 封装单向链表类
    function LinkList(){
      // 封装一个内部类:节点类
      function Node(data){
        this.data = data;
        this.next = null;
      }

      // 属性
      // 属性head指向链表的第一个节点
      this.head = null;
      this.length = 0;
      }

append(element)追加方法

      // 一.实现append方法
      LinkList.prototype.append = data => {
        //1.创建新节点
        let newNode = new Node(data)

        //2.添加新节点
        //情况1:判断是否添加的是第一个节点
        if(this.length == 0){
          this.head = newNode
        //情况2:节点数大于1,在链表的最后添加新节点  
        }else {              
          //让变量current指向第一个节点
          let current = this.head
          //当current.next(下一个节点不为空)不为空时,一直循环,直到current指向最后一个节点
          while (current.next){
            current = current.next
          }
          // 最后节点的next指向新的节点
          current.next = newNode
        }
        //3.添加完新结点之后length+1
        this.length += 1
      }

テストコード

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.测试append方法
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    console.log(list);  

詳細なプロセス

まず、current が最初のノードを指すようにします。

while ループを使用して現在のノードが最後のノードを指すようにし、最後に current.next = newNode を通じて最後のノードが新しいノード newNode を指すようにします。

toString()

      // 实现toString方法
      LinkList.prototype.toString = () => {
        // 1.定义变量
        let current = this.head
        let listString = ""

        // 2.循环获取一个个的节点
        while(current){ 
          listString += current.data + " "
          current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点
        }
        return  listString
      }

テストコード

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    
    //3.测试toString方法
    console.log(list.toString());

挿入(位置,要素)

      // 实现insert方法
      LinkList.prototype.insert = (position, data) => {
      //理解positon的含义:position=0表示新界点插入后要成为第1个节点,
      //position=2表示新界点插入后要成为第3个节点
      //1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的length
        if(position < 0 || position > this.length){
          return false
        }
        //2.根据data创建newNode
        let newNode = new Node(data)

        //3.插入新节点
        //情况1:插入位置position=0
        if(position == 0){
          // 让新节点指向第一个节点
          newNode.next = this.head
          // 让head指向新节点
          this.head = newNode
        //情况2:插入位置position>0(该情况包含position=length)
        } else{
          let index = 0
          let previous = null
          let current = this.head
          //步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)
          while(index++ < position){
          //步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点
            previous = current
            current = current.next
          }
          // 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点
          newNode.next = current
          //步骤4:通过变量previous,使position位置的前一个节点指向newNode
          previous.next = newNode
          /*
            启示:
            1.我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点(替身使者);
            比如current指向节点3,想要节点3指向节点4只需要:current.next = 4即可。
            2.两个节点间是双向的,想要节点2的前一个节点为节点1,可以通过:1.next=2,来实现;
          */
        }
        //4.新节点插入后要length+1
        this.length += 1;

        return true
      }

テストコード

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
    
    //3.测试insert方法
    list.insert(0, '在链表最前面插入节点');
    list.insert(2, '在链表中第二个节点后插入节点');
    list.insert(5, '在链表最后插入节点');
    alert(list);
    console.log(list);

詳細なプロセス:

inset メソッドの実装プロセス: 挿入されたノードの位置に応じて、いくつかの状況に分けることができます。

ケース 1: 位置 = 0 :

パス: newNode.next = this.head、接続 1 を確立します。

パス: this.head = newNode, 接続 2 を確立します; (最初に接続 2 を確立できません。そうでない場合、this.head は Node1 を指さなくなります)

ケース 2: 位置 > 0 :

まず、pos = X の位置にある前のノードと次のノードをそれぞれ指すように、previous と current の 2 つの変数を定義します。

次に、 newNode.next = current; によってポイントを 3 に変更します。

最後に、previous.next = newNode; によってポイントを 4 に変更します。

ケース 2 の特殊なケース: 位置 = 長さ:

ケース 2 には、current と newNode.next が両方とも null を指す pos = length のケースも含まれており、接続 3 と接続 4 を確立する方法はケース 2 と同じです。

取得(位置)

      //实现get方法
      LinkList.prototype.get = (position) => {
        //1.越界判断
        // 当position = length时,取到的是null所以0 =< position < length
        if(position < 0 || position >= this.length){
          return null
        }
        //2.获取指定的positon位置的后一个节点的data
        //同样使用一个变量间接操作节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        return current.data
      }

テストコード

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    

    //3.测试get方法
    console.log(list.get(0));
    console.log(list.get(1));

詳細なプロセス:

get方法的实现过程:以获取position = 2为例,如下图所示:

首先使current指向第一个节点,此时index=0;

通过while循环使current循环指向下一个节点,注意循环终止的条件index++ < position,即当index=position时停止循环,此时循环了1次,current指向第二个节点(Node2),最后通过current.data返回Node2节点的数据;

indexOf(element)

      //实现indexOf方法
      LinkList.prototype.indexOf = data => {
        //1.定义变量
        let current = this.head
        let index = 0

        //2.开始查找:只要current不指向null就一直循环
        while(current){
          if(current.data == data){
            return index
          }
          current = current.next
          index += 1
        } 

        //3.遍历完链表没有找到,返回-1
        return -1
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    
    
    //3.测试indexOf方法
    console.log(list.indexOf('aaa'));
    console.log(list.indexOf('ccc'));

过程详解:

indexOf方法的实现过程:

使用变量current记录当前指向的节点,使用变量index记录当前节点的索引值(注意index = node数-1):

update(position,element)

      //实现update方法
      LinkList.prototype.update = (position, newData) => {
        //1.越界判断
        //因为被修改的节点不能为null,所以position不能等于length
        if(position < 0 || position >= this.length){
          return false
        }
        //2.查找正确的节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //3.将position位置的后一个节点的data修改成newData
        current.data = newData
        //返回true表示修改成功
        return true
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')    
    
    //3.测试update方法
    list.update(0, '修改第一个节点')
    list.update(1, '修改第二个节点')
    console.log(list);
    console.log(list.update(3, '能修改么'));

removeAt(position)

      //实现removeAt方法
      LinkList.prototype.removeAt = position => {
        //1.越界判断
        if (position < 0 || position >= this.length) {//position不能为length
          return null
        }
        //2.删除元素
        //情况1:position = 0时(删除第一个节点)
        let current = this.head
        if (position ==0 ) {
        //情况2:position > 0时
          this.head = this.head.next
        }else{
          let index = 0
          let previous = null
          while (index++ < position) {
            previous = current
            current = current.next
          }
          //循环结束后,current指向position后一个节点,previous指向current前一个节点
          //再使前一个节点的next指向current的next即可
          previous.next = current.next
        }
        //3,length-1
        this.length -= 1

        //返回被删除节点的data,为此current定义在最上面
        return current.data
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')
  
  //3.测试removeAt方法
    console.log(list.removeAt(0));
    console.log(list.removeAt(0));
    console.log(list);

过程详解:

removeAt方法的实现过程:删除节点时存在多种情况:

情况1:position = 0,即移除第一个节点(Node1)。

通过:this.head = this.head.next,改变指向1即可;

虽然Node1的next仍指向Node2,但是没有引用指向Node1,则Node1会被垃圾回收器自动回收,所以不用处理Node1指向Node2的引用next。

情况2:positon > 0,比如pos = 2即移除第三个节点(Node3)。

注意:position = length时position后一个节点为null不能删除,因此position != length;

首先,定义两个变量previous和curent分别指向需要删除位置pos = x的前一个节点和后一个节点;

然后,通过:previous.next = current.next,改变指向1即可;

随后,没有引用指向Node3,Node3就会被自动回收,至此成功删除Node3 。

remove(element)、isEmpty()、size()

/*-------------其他方法的实现--------------*/
      //一.实现remove方法
      LinkList.prototype.remove = (data) => {
        //1.获取data在列表中的位置
        let position = this.indexOf(data)
        //2.根据位置信息,删除结点
        return this.removeAt(position)
      }

      //二.实现isEmpty方法
      LinkList.prototype.isEmpty = () => {
        return this.length == 0
      }

      //三.实现size方法
      LinkList.prototype.size = () => {
        return this.length
      }

测试代码

    //测试代码
    //1.创建LinkList
    let list = new LinkList()
    
    //2.插入数据
    list.append('aaa')
    list.append('bbb')
    list.append('ccc')

/*---------------其他方法测试----------------*/
      //remove方法
      console.log(list.remove('aaa'));
      console.log(list);
      //isEmpty方法
      console.log(list.isEmpty());
      //size方法
      console.log(list.size());

完整实现

    // 封装链表类
    function LinkList(){
      // 封装一个内部类:节点类
      function Node(data){
        this.data = data;
        this.next = null;
      }

      // 属性
      // 属性head指向链表的第一个节点
      this.head = null;
      this.length = 0;

      // 一.实现append方法
      LinkList.prototype.append = data => {
        //1.创建新节点
        let newNode = new Node(data)

        //2.添加新节点
        //情况1:只有一个节点时候
        if(this.length == 0){
          this.head = newNode
        //情况2:节点数大于1,在链表的最后添加新节点  
        }else {              
          //让变量current指向第一个节点
          let current = this.head
          //当current.next(下一个节点不为空)不为空时,一直循环,直到current指向最后一个节点
          while (current.next){
            current = current.next
          }
          // 最后节点的next指向新的节点
          current.next = newNode
        }
        //3.添加完新结点之后length+1
        this.length += 1
      }

      // 二.实现toString方法
      LinkList.prototype.toString = () => {
        // 1.定义变量
        let current = this.head
        let listString = ""

        // 2.循环获取一个个的节点
        while(current){ 
          listString += current.data + " "
          current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点
        }
        return  listString
      }

      // 三.实现insert方法
      LinkList.prototype.insert = (position, data) => {
      //理解positon的含义:position=0表示新界点插入后要成为第1个节点,position=2表示新界点插入后要成为第3个节点
        //1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的length
        if(position < 0 || position > this.length){
          return false
        }
        //2.根据data创建newNode
        let newNode = new Node(data)

        //3.插入新节点
        //情况1:插入位置position=0
        if(position == 0){
          // 让新节点指向第一个节点
          newNode.next = this.head
          // 让head指向新节点
          this.head = newNode
        //情况2:插入位置position>0(该情况包含position=length)
        } else{
          let index = 0
          let previous = null
          let current = this.head
          //步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)
          while(index++ < position){
          //步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点
            previous = current
            current = current.next
          }
          // 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点
          newNode.next = current
          //步骤4:通过变量previous,使position位置的前一个节点指向newNode
          previous.next = newNode
          
        //我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点;
        }
        //4.新节点插入后要length+1
        this.length += 1;

        return true
      }

      //四.实现get方法
      LinkList.prototype.get = (position) => {
        //1.越界判断
        // 当position = length时,取到的是null所以0 =< position < length
        if(position < 0 || position >= this.length){
          return null
        }
        //2.获取指定的positon位置的后一个节点的data
        //同样使用一个变量间接操作节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        return current.data
      }

      //五.实现indexOf方法
      LinkList.prototype.indexOf = data => {
        //1.定义变量
        let current = this.head
        let index = 0

        //2.开始查找:只要current不指向null就一直循环
        while(current){
          if(current.data == data){
            return index
          }
          current = current.next
          index += 1
        } 

        //3.遍历完链表没有找到,返回-1
        return -1
      }

      //六.实现update方法
      LinkList.prototype.update = (position, newData) => {
        //1.越界判断
        //因为被修改的节点不能为null,所以position不能等于length
        if(position < 0 || position >= this.length){
          return false
        }
        //2.查找正确的节点
        let current = this.head
        let index = 0
        while(index++ < position){
          current = current.next
        }
        //3.将position位置的后一个节点的data修改成newData
        current.data = newData
        //返回true表示修改成功
        return true
      }

      //七.实现removeAt方法
      LinkList.prototype.removeAt = position => {
        //1.越界判断
        if (position < 0 || position >= this.length) {
          return null
        }
        //2.删除元素
        //情况1:position = 0时(删除第一个节点)
        let current = this.head
        if (position ==0 ) {
        //情况2:position > 0时
          this.head = this.head.next
        }else{
          let index = 0
          let previous = null
          while (index++ < position) {
            previous = current
            current = current.next
          }
          //循环结束后,current指向position后一个节点,previous指向current前一个节点
          //再使前一个节点的next指向current的next即可
          previous.next = current.next
        }
        //3,length-1
        this.length -= 1

        //返回被删除节点的data,为此current定义在最上面
        return current.data
      }

/*-------------其他方法的实现--------------*/
      //八.实现remove方法
      LinkList.prototype.remove = (data) => {
        //1.获取data在列表中的位置
        let position = this.indexOf(data)
        //2.根据位置信息,删除结点
        return this.removeAt(position)
      }

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

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

おすすめ

転載: blog.csdn.net/weixin_52479225/article/details/129501120