JS专题系列之手摸手彻底了解链表操作

前言

1、什么是链表?

官方解释:

链表是一种非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现,常见的链表有: 单向链表、双向链表、循环链表、反向链表

通俗解释:

链表其实就是一个俄罗斯套娃,一层套着一层,拿掉第一层可以找到第二层,....依次类推

0.png

2、数组特点

  • 数组是顺序存储结构
  • 数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间,比如看电影时,为了保证10个人能坐在一起,必须提前订好10个连续的位置。这样的好处就是能保证10个人可以在一起。但是这样的缺点是,如果来的人不够10个,那么剩下的位置就浪费了。如果临时有多来了个人,那么10个就不够用了
  • 插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。删除数据时,这个数据后面的数据都要往前移动 ,比如原来去了5个人,然后后来又去了一个人要坐在第三个位置上,那么第三个到第五个都要往后移动一个位子,将第三个位置留给新来的人。 当这个人走了的时候,因为他们要连在一起的,所以他后面几个人要往前移动一个位置,把这个空位补上
  • 随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据

3、链表特点

  • 在内存中可以存在任何地方,不要求连续
  • 每一个数据都保存了下一个数据的内存地址,通过这个地址找到下一个数据
  • 链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充
  • 查找数据时效率低,因为不具有随机访问性,所以访问某个位置的数据都要从第一个数据开始访问,然后根据第一个数据保存的下一个数据的地址找到第二个数据,以此类推。 要找到第三个人,必须从第一个人开始问起

4、总结:

数组:

1、随机访问性强

2、查找速度快

链表:

1、插入删除速度快

2、内存利用率高

3、拓展很灵活

一、单向链表

1.png

特点:

  • 用一组任意的内存空间去存储数据元素(这里的内存空间可以是连续的,也可以是不连续的)
  • 每个节点(node)都由数据本身和一个指向后续节点的指针组成
  • 整个链表的存取必须从头指针开始,头指针指向第一个节点
  • 最后一个节点的指针指向空(NULL)

链表中的主要操作:

  • 创建节点
  • 插入节点
  • 查找节点
  • 删除节点
1、创建节点
class ListNode {
    constructor(data) {
      this.data = data;
      this.next = null;
    }
  }
2、尾部插入节点

尾部插入节点我们只需要考虑2点

  • 链表中head是否存在如果不存在代表插入的节点是第一个
  • 如果存在则则找最后一个节点,将最后一个节点的next指向插入的节点
// 尾部插入   this.tail的作用就是用来保存最后一个节点
append(data) {
    let listNode = new ListNode(data);
    if (this.head) {
        this.tail.next = listNode;
        this.tail = listNode;
    } else {
        this.head = listNode;
        this.tail = listNode;
    }
    this.length++;
    return true;
}
3、获取节点

此方法很重要,因为删除和添加都需要用到此方法.另外链表的缺陷也在此方法中体现了出来

  • 如果index等于0则直接返回head
  • 如果index不等于0则需要通过while循环找到对应的节点
getNode(index) {
    if (index < 0 || index > this.length) {
        return false;
    } else {
        if (index === 0) {
            return this.head;
        } else {
            let current_index = 0;
            let current_node = this.head;
            while (current_index < index) {
                current_index += 1;
                current_node = current_node.next;
            }
            return current_node;
        }
    }
}

4、插入节点
  • 如果index的值等于this.length的值则代表需要插入到最后,直接调用尾部插入即可
  • 如果index等于0则需要将head节点赋值给插入节点的next,插入节点赋值给head
  • 如果index不等于0则需要获取到插入位置的prevNodenextNode,然后将插入节点的next等于nextNode,prevNode的next等于插入节点
2.png
insert(index, data) {
    if (index < 0 || index > this.length) {
        return fase;
    } else if (index === this.length) {
        this.append(data);
    } else {
        let listNode = new ListNode(data);
        if (index === 0) {
            listNode.next = this.head;
            this.head = listNode;
        } else {
            const nextNode = this.getNode(index);
            const prevNode = this.getNode(index - 1);
            listNode.next = nextNode;
            prevNode.next = listNode;
        }

        this.length += 1;
        return true;
    }
}
5、删除节点
  • 如果index等于0则将head值改为head.next
  • 如果index不等于0则需要获取到删除节点的prevNode和nextNode,将nextNode赋值给prevNode的next
3.png
remove(index) {
    if (index < 0 || index > this.length) {
        return false;
    } else if (index === 0) {
        this.head = this.head.next;
        this.length -= 1;
    } else {
        const nextNode = this.getNode(index).next;
        const prevNode = this.getNode(index - 1);
        prevNode.next = nextNode;
        this.length -= 1;
    }
}
6、获取节点数据

同理获取节点

get(index) {
    if (index < 0 || index > this.length) {
        return false;
    } else {
        if (index === 0) {
            return this.head.data;
        } else {
            let current_index = 0;
            let current_node = this.head;
            while (current_index < index) {
                current_index += 1;
                current_node = current_node.next;
            }
            return current_node.data;
        }
    }
}
7、完整版代码
class ListNode {
    constructor(data) {
      this.data = data;
      this.next = null;
    }
  }

  class List {
    constructor() {
      this.head = null;
      this.tail = null;
      this.length = 0;
    }
    /*
        尾部插入
    */
    append(data) {
      let listNode = new ListNode(data);
      if (this.head) {
        this.tail.next = listNode;
        this.tail = listNode;
      } else {
        this.head = listNode;
        this.tail = listNode;
      }
      this.length++;
      return true;
    }

    /*
        获取节点
    */
    getNode(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else {
        if (index === 0) {
          return this.head;
        } else {
          let current_index = 0;
          let current_node = this.head;
          while (current_index < index) {
            current_index += 1;
            current_node = current_node.next;
          }
          return current_node;
        }
      }
    }
    /*
        插入
    */
    insert(index, data) {
      if (index < 0 || index > this.length) {
        return fase;
      } else if (index === this.length) {
        this.append(data);
      } else {
        let listNode = new ListNode(data);
        if (index === 0) {
          listNode.next = this.head;
          this.head = listNode;
        } else {
          const nextNode = this.getNode(index);
          const prevNode = this.getNode(index - 1);
          listNode.next = nextNode;
          prevInsertNode.next = listNode;
        }

        this.length += 1;
        return true;
      }
    }

    /*
        移除
    */
    remove(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else if (index === 0) {
        this.head = this.head.next;
        this.length -= 1;
      } else {
        const nextNode = this.getNode(index).next;
        const prevNode = this.getNode(index - 1);
        prevNode.next = nextNode;
        this.length -= 1;
      }
    }

    /*
        获取节点数据
    */
    get(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else {
        if (index === 0) {
          return this.head.data;
        } else {
          let current_index = 0;
          let current_node = this.head;
          while (current_index < index) {
            current_index += 1;
            current_node = current_node.next;
          }
          return current_node.data;
        }
      }
    }
  }

二、双向链表

双向链表其实就是多了一个指向上一个节点的指针,不管删除还是添加时刻记得改变prev的指向即可.其他逻辑和单向链表一致

1、完整代码
 class ListNode {
    constructor(data) {
      this.data = data;
      this.next = null;
      this.prev = null;
    }
  }

  class List {
    constructor() {
      this.head = null;
      this.tail = null;
      this.length = 0;
    }
    /*
        尾部插入
    */
    append(data) {
      let listNode = new ListNode(data);
      if (this.head) {
        this.tail.next = listNode;
        listNode.prev = this.tail;
        this.tail = listNode;
      } else {
        this.head = listNode;
        this.tail = listNode;
      }
      this.length++;
      return true;
    }

    /*
        获取节点
    */
    getNode(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else {
        if (index === 0) {
          return this.head;
        } else {
          let current_index = 0;
          let current_node = this.head;
          while (current_index < index) {
            current_index += 1;
            current_node = current_node.next;
          }
          return current_node;
        }
      }
    }
    /*
        插入
    */
    insert(index, data) {
      if (index < 0 || index > this.length) {
        return fase;
      } else if (index === this.length) {
        this.append(data);
      } else {
        let listNode = new ListNode(data);
        if (index === 0) {
          listNode.next = this.head;
          this.head.prev = listNode;
          this.head = listNode;
        } else {
          const nextNode = this.getNode(index);
          const prevNode = this.getNode(index - 1);
          listNode.next = nextNode;
          listNode.prev = prevNode;
          nextNode.prev = listNode;
          prevNode.next = listNode;
        }

        this.length += 1;
        return true;
      }
    }

    /*
        移除
    */
    remove(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else if (index === 0) {
        this.head.next.prev = null;
        this.head = this.head.next;
        this.length -= 1;
      } else {
        const nextNode = this.getNode(index).next;
        const prevNode = this.getNode(index - 1);
        prevNode.next = nextNode;
        nextNode.prev = prevNode;
        this.length -= 1;
      }
    }

    /*
        获取节点数据
    */
    get(index) {
      if (index < 0 || index > this.length) {
        return false;
      } else {
        if (index === 0) {
          return this.head.data;
        } else {
          let current_index = 0;
          let current_node = this.head;
          while (current_index < index) {
            current_index += 1;
            current_node = current_node.next;
          }
          return current_node.data;
        }
      }
    }
  }

三、反转链表

什么是反转链表? 假设套娃的顺序是1<2<3<4,那么反转也就是4<3<2<1。实现反转的方法有2种

  • 迭代法:就是需要定义三个节点指针,一个指向当前节点,一个指向前面一个节点,一个指向后面一个节点,反转就是说,当前节点的next指针指向前面一个节点
  • 递归:递归方法就是你不会反转当前链表,让递归方法先帮你反转当前节点的下一个节点开始的单链表,把反转后的头节点返回。你再将当前头节点连接到返回头节点的尾部
4.png
一、迭代法
class Node {
    constructor(data) {
      this.data = data;
      this.next = null;
    }
  }
  let node1 = new Node(1);
  let node2 = new Node(2);
  let node3 = new Node(3);
  let node4 = new Node(4);
  node1.next = node2;
  node2.next = node3;
  node3.next = node4;
  node4.next = null;
// 核心
  function reverse(node) {
    if (!node) {
      return null;
    }
    let curr_node = node;
    let pre_node = null;
    while (curr_node) {
      // 获取下一个节点,用来更改current_node的值,遍历所有的节点
      let next_node = curr_node.next;
      // 将当前节点的next指向上一次的current_node,如果第一次遍历则指向null
      curr_node.next = pre_node;
      // 保存当前current_node的节点
      pre_node = curr_node;
      // 进行下一次遍历
      curr_node = next_node;
    }
    return pre_node;
  }


  let node = reverse(node1);
  console.log(node);
5.png
二、递归
function reverse_digui(node) {
    if (!node) {
      return null;
    }
    if (node.next == null) {
      return node;
    }
    let new_head = reverse_digui(node.next);
    node.next.next = node;
    node.next = null;
    return new_head;
  }

你可能感兴趣的:(JS专题系列之手摸手彻底了解链表操作)