js单链表的插入、遍历、反转、归并排序

427 阅读2分钟
/**
 * 单链表
 * 1)直接插入--无序单链表
 * 2)按顺序插入--有序单链表
 * 3)遍历
 * 4)反转
 * 5)归并排序
 */

//定义节点
class ListNode {
  constructor(val) {
    this.val = val
    this.next = null
  }
}

//定义单链表
class SingleLinkList {
  constructor() {
    this.head = null
  }

  //单纯插入
  insert(val) {
    const node = new ListNode(val)
    if (!this.head) {
      this.head = node
    } else {
      let curr = this.head
      while (curr.next) {
        curr = curr.next
      }
      curr.next = node
    }
  }

  //按顺序插入
  insertSort(val) {
    const node = new ListNode(val)
    if (!this.head) {
      this.head = node
    } else {
      let curr = this.head,
        pre = curr
      //用curr.next非空来保证curr非空
      //如果不约束curr.next非空,那么下一次循环的curr可能就是空的,也就不存在curr.val之说
      while (curr.next && curr.val < val) {
        pre = curr
        curr = curr.next
      }
      if (curr.val < val) {
        //插入到curr后面,不需要判断是不是头节点,直接插入即可
        node.next = curr.next
        curr.next = node
      } else {
        //插入到curr之前,要判断是不是头节点
        if (pre === curr) {
          //curr是头结点,则node成为头结点
          node.next = curr
          this.head = node
        } else {
          //curr非头节点,node插入到pre和curr之间
          node.next = curr
          pre.next = node
        }
      }
    }
  }

  //遍历
  traverse(head = this.head) {
    const arr = []
    if (!head) return arr
    while (head) {
      arr.push(head.val)
      head = head.next
    }
    return arr
  }

  //反转单链表
  reverse(head = this.head) {
    if (!head || !head.next) return true
    let pre = null, //当前节点的前驱
      temp = null, //当前节点的后继
      curr = head //当前节点
    while (curr) {
      temp = curr.next //缓存当前节点的后继
      curr.next = pre //后继指向前驱
      pre = curr //pre游标后移一位
      curr = temp //curr游标后移一位
    }
    this.head = pre
    temp = null
    return true
  }

  //链表的归并排序
  sortLinkList(head = this.head) {
    const dummyHead = new ListNode(0)
    dummyHead.next = head
    let p = head,
      length = 0 //链表的长度

    //获得链表的长度
    while (p) {
      ++length
      p = p.next
    }

    for (let size = 1; size < length; size <<= 1) {
      let curr = dummyHead.next,
        tail = dummyHead

      while (curr) {
        let left = curr, //当前链表完整
          //right为切掉size个节点之后的链表,left变为size长度的链表
          right = SingleLinkList.cut(left, size)
        //curr为right切掉size个节点之后的链表,供下个循环使用,right变为size个长度的链表
        curr = SingleLinkList.cut(right, size)
        tail.next = SingleLinkList.merge(left, right)

        while (tail.next) {
          //让tail始终等于dummyHead的最后一个节点
          tail = tail.next
        }
      }
    }

    return (this.head = dummyHead.next)
  }

  //切除head链表前n个节点
  static cut(head, n) {
    let p = head
    while (--n && p) {
      //使p等于第n个node
      p = p.next
    }
    if (!p) return null
    let next = p.next
    p.next = null
    return next
  }

  //双路归并
  static merge(l1, l2) {
    let dummyHead = new ListNode(0),
      p = dummyHead
    while (l1 && l2) {
      if (l1.val < l2.val) {
        p.next = l1
        p = p.next
        l1 = l1.next
      } else {
        p.next = l2
        p = p.next
        l2 = l2.next
      }
    }
    p.next = l1 ? l1 : l2
    return dummyHead.next
  }
}

//for test
;(() => {
  const arr = [9, 2, 1, 3, 4, 6, 0, 10, 36, 45, 22, 31]
  const linkList = new SingleLinkList()
  console.log('----直接插入----')
  arr.forEach(e => linkList.insert(e))
  // console.log('----按顺序插入----')
  // arr.forEach(e => linkList.insertSort(e))
  console.log('----遍历单链表----')
  console.log(linkList.traverse())
  // console.log('----反转单链表----')
  // console.log(linkList.reverse())
  console.log('----归并排序----')
  linkList.sortLinkList()
  console.log('----遍历单链表----')
  console.log(linkList.traverse())
})()