算法

145 阅读8分钟

链表

链表有一系列节点组成,每个节点一般至少会包含两部分的信息:

(1)元素数据

(2)指向下一个元素的指针

链表的操作: 创建、插入、删除、输出

链表的优点: 不需要初始化容量,可以任意加减元素; 添加或者删除元素时只需要改变前后两个元素结点的指向,所以添加,删除很快;

缺点: 因为含有大量的指针域,占用空间较大; 查找元素需要遍历链表来查找,非常耗时。

适用场景: 数据量较小,需要频繁增加,删除操作的场景

实现代码如下:

	     class Node {
			  constructor(data) {
			  //数据
			    this.data = data;
			    //指向
			    this.next = null;
	 		 }
		}
		
		class LinkedList {
		  constructor() {
		    this.head = null;
		    this.tail = null;
		    this.length = 0;
		  }
		  
	  // 添加
	  append(data) {
	    // 先把内容传到node这个类里面赋值  最后拿到的结果是一个对象{data:data,next:null}
	    let node = new Node(data);
	    // 判断head是否是第一次添加  如果是的话直接赋值  
	    if (!this.head) {
	      this.head = node;
	      this.tail = node
	    } else {
	      // 如果不是  要改变指向next
	      this.tail.next = node
	      this.tail = node
	    }
	    // 每加一次让length  +1
	    this.length += 1;
	    return this;
	  }
	  
	  // 按位置删除
	  remove(position) {
	    let current = this.head
	    let index = 0;
	    let prev = null;
	    // 如果位置不存在
	    let message = '该数据不存在'
	    if (position < 0 || position > this.length - 1) {
	      return message;
	    }
	    // 如果删除的是第一个
	    if (position === 0) {
	      // 如果是第一个的话就让head的指向等于head的下一个指向
	      this.head = this.head.next
	    } else {
	      // 如果不是第一个  把上一个的指向指向要删除的那一项的下一个 也就是跳过被删除的这一项
	      while (index++ < position) {
	        // 保存上一个到prev
	        prev = current
	        // 保存被删除项
	        current = current.next
	      }
	      // 被删除的上一个元素的指向=被删除项的指向
	      prev.next = current.next
	      // 如果删除的是最后一项
	      if (position === this.length - 1) {
	        this.tail = prev
	      }
	    }
	    this.length -= 1;
	    return current.data;
	  }
	  
	  // 修改
	  updata(position, newdata) {
	    let message = '该数据不存在'
	    if (position < 0 || position > this.length - 1) {
	      return message;
	    }
	    let current = this.head;
	    let index = 0;
	    // 找到当前要修改的元素
	    while (index++ < position) {
	      current = current.next
	    }
	    // 修改内容
	    current.data = newdata
	    return this;
	  }
	  
	  // 查找
	  get(position) {
	    let message = '该数据不存在'
	    if (position < 0 || position > this.length - 1) {
	      return message;
	    }
	    let current = this.head;
	    let index = 0;
	    // 找到当前的元素
	    while (index++ < position) {
	      current = current.next
	    }
	    return current.data;
	  }
	  
	  // 插入
	  insert(position, data) {
	    let node = new Node(data)
	    let index = 0;
	    let prev = null;
	    let current = this.head;
	    // 判断是否是添加到第一个
	    if (position === 0) {
	      // 新元素的指向=原本第一个元素
	      node.next = this.head
	      this.head = node
	    } else {
	      while (index++ < position) {
	        // 保存上一个元素
	        prev = current
	        // 保存当前元素
	        current = current.next
	      }
	      // 上一个元素的指向=新添加的元素
	      prev.next = node
	      // 新添加的元素的指向=当前元素
	      node.next = current
	    }
	    // 如果插入的位置是最后的一个
	    if (position === this.length) {
	      this.tail = node
	    }
	    this.length += 1;
	    return this;
	  }
	  // 转数组
	  toArray() {
	    let arr = [];
	    let current = this.head
	    while (current) {
	      arr.push(current.data)
	      current = current.next
	    }
	    return arr;
	  }
	  // 转字符串
	  toString() {
	    let str = '';
	    let current = this.head
	    while (current) {
	      str += current.data + ' '
	      current = current.next
	    }
	    return str;
	  }
	  // 长度
	  size() {
	    return this.length
	  }
	  // 判断是否为空
	  isEmpty() {
	    return this.length === 0
	  }
	}

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。只可以从栈顶删除。

栈的特点:先进后出,后出先进

看下图:

栈的特性图
大白话的意思就是 最先进去的最后在删除,最后进去的最先删除。因为这就像个瓶子一样,你最后进来的肯定在最上面,你往出到东西也是先把最上面的倒出来,不可能上面的还没出来瓶底的先出来
栈的特性图
看下代码:

function Stack() {
  this.items = []
  //添加
  Stack.prototype.push = (data) => {
    this.items.push(data)
  }
  //删除
  Stack.prototype.pop = () => {
    return this.items.pop()
  }
  //长度
  Stack.prototype.size = () => {
    return this.items.length
  }
  //是否为空
  Stack.prototype.isEmpty = () => {
    return this.items.length === 0
  }
  //转字符串
  Stack.prototype.tostring = () => {
    let str = '';
    for (let i = 0; i < this.items.length; i++) {
      str += this.items[i] + ' '
    }
    return str;
  }
}

队列

队列跟栈很相似,不同的是队列的特性,所以删除方法不一样 使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。

队列特性:先进先出,后进后出

看下示意图: 大白话的意思就是 谁是最先进去的就先删掉谁,最后进去的就最后删除

特性图
代码如下:

function Queue() {
  this.items = []
  //添加
  Queue.prototype.push = (data) => {
    this.items.push(data)
  }
  //删除
  Queue.prototype.shift = () => {
    return this.items.shift()
  }
  //长度
  Queue.prototype.size = () => {
    return this.items.length
  }
  //是否为空
  Queue.prototype.ispty = () => {
    return this.items.length === 0
  }
  //转字符串
  Queue.prototype.tostring = () => {
    let str = '';
    for (let i = 0; i < this.items.length; i++) {
      str += this.items[i] + ' '
    }
    return str;
  }
}

二叉树

好处:

1、可以快速找出最大值、最小值

2、中序遍历 二叉树每个节点有三个值:left(左边节点)key(本身)right(右边节点) 看下代码:

	class Node {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
}

添加节点代码:

		class BinarySearchTree {
  constructor() {
    this.root = null;
  }
  insert(key) {
    let node = new Node(key);
    // 判断是不是第一次添加
    if (!this.root) {
      this.root = node
    } else {
      this._insert(this.root, node)
    }
  }
  _insert(node, newNode) {
    // 判断父节点是否小于根节点  小于就放左边  大于||等于就放右边
    if (node.key > newNode.key) {
      // 判断左边有没有节点
      if (!node.left) {
        // 没有就赋值
        node.left = newNode
      } else {
        // 有的话在调用判断方法继续判断
        this._insert(node.left, newNode)
      }
    } else {
      // 判断右边有没有节点
      if (!node.right) {
        // 没有就赋值
        node.right = newNode
      } else {
        // 有的话在调用判断方法继续判断
        this._insert(node.right, newNode)
      }
    }
  }
}

最大值、最小值

	  // 最大值
  max() {
    let currNode = this.root
    while (currNode.right) {
      currNode = currNode.right
    }
    return currNode.key
  }

  // 最小值
  min() {
    let currNode = this.root
    while (currNode.left) {
      currNode = currNode.left
    }
    return currNode.key
  }

查找

  // 两个方法  建议用循环方法  因为递归方法调用方法自身次数过多会溢出、报错
  search(key) {
    // 循环判断
    let currNode = this.root
    while (currNode) {
  // 判断根节点和key  如果大于在右边找 如果小于在左边找  等于就直接返回找到了
      if (currNode.key > key) {
        if (currNode.left) {
          currNode = currNode.left
        } else {
          return false
        }
      } else if (currNode.key < key) {
        if (currNode.right) {
          currNode = currNode.right
        } else {
          return false
        }
      } else {
        return currNode.key === key
      }
    }

    // 递归判断调用
    // return this._search(currNode, key)
  }
·//递归查找方法
  // _search(currNode, key) {
  // 判断是否为空,为空直接返回false
  //   if (!currNode) {
  //     return false;
  //   }
  // 判断是不是小于  小于的话调用自身方法从左边找
  //   if (currNode.key > key) {
  //     return this._search(currNode.left, key)
  //   } else if (currNode.key < key) {
  // 判断是不是大于  大于的话调用自身方法从右边找
  //     return this._search(currNode.right, key)
  //   } else {
  // 如果相等返回true
  //     return currNode.key === key
  //   }
  // }

中序遍历

中序遍历就是按照节点上的key值以升序遍历BTS上的左右节点 // 排序

  middleSort() {
    let arr = []
    let currNode = this.root
    return this._middleSort(currNode, arr)
  }
  _middleSort(currNode, arr) {
    if (currNode) {
      this._middleSort(currNode.left, arr)
      
      arr.push(currNode.key)
      
      this._middleSort(currNode.right, arr)
    }
    return arr;
  }

先序遍历

先序遍历是从根节点开始遍历,先遍历左节点在遍历右节点

middleSort() {
    let arr = []
    let currNode = this.root
    return this._middleSort(currNode, arr)
  }
  _middleSort(currNode, arr) {
    if (currNode) {
    //中序、先序、后序代码最大的不同就是push的这句代码的位置
    
   	  arr.push(currNode.key)
      this._middleSort(currNode.left, arr)
      this._middleSort(currNode.right, arr)
    }
    return arr;
  }

后序遍历

后序遍历是先访问叶子节点,从左子树到右子树,再到根节点

middleSort() {
    let arr = []
    let currNode = this.root
    return this._middleSort(currNode, arr)
  }
  _middleSort(currNode, arr) {
    if (currNode) {
    //中序、先序、后序代码最大的不同就是push的这句代码的位置
      this._middleSort(currNode.left, arr)
      this._middleSort(currNode.right, arr) 
      
      arr.push(currNode.key)
      
    }
    return arr;
  }

删除

removeNode(key) {
    let node = this.root
    return this._removeNode(node, key)
  }

  _removeNode(node, key) {
    if (node === null) return null;

    if (key < node.key) {
      node.left = this._removeNode(node.left, key);
      return node;
    }
    else if (key > node.key) {
      node.right = this._removeNode(node.right, key);
      return node;
    }
    else {
      // 第一种情况:一个叶子节点(没有子节点)
      if (node.left === null && node.right === null) {
        node = null;
        return node;
      }
      // 第二种情况:只包含一个子节点
      if (node.left === null) {
        node = node.right;
        return node;
      }
      else if (node.right === null) {
        node = node.left;
        return node;
      }

      // 第三种情况:有两个子节点
      let aux = this.minNode(node.right);
      node.key = aux.key;
      node.right = this._removeNode(node.right, aux.key);
      return node;
    }
  };

  minNode(node) {
    if (node === null) return null;

    while (node && node.left !== null) {
      node = node.left;
    }
    return node;
  };

二叉树先序、中序、后序示意图

排序

常用的三个排序方法:冒泡排序、选择排序、快速排序

1. 冒泡排序(BubbleSort)

原理: 1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。 2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。 3.针对所有的元素重复以上的步骤,除了最后一个。 4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 5.冒泡排序比较稳定。

冒泡排序
代码:

class ArrayList {
  constructor() {
    this.arr = []
  }
  //随机数组
  random() {
    for (let i = 0; i < 5; i++) {
      this.arr.push(Math.ceil(Math.random() * 100))
    }
    return this.arr;
  }
  //替换位置方法
  swap(n, m) {
    let num = this.arr[m]
    this.arr[m] = this.arr[n]
    this.arr[n] = num
  }

  // 冒泡排序
  BubbleSort() {
    for (let j = this.arr.length; j >= 0; j--) {
      for (let i = 0; i < j - 1; i++) {
        if (this.arr[i] > this.arr[i + 1]) {
          this.swap(i, i + 1)
        }
      }
    }
    return this.arr;
  }
}

2.选择排序(SelectionSort)

原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

选择排序

代码:

class ArrayList {
  constructor() {
    this.arr = []
  }
  //随机数组
  random() {
    for (let i = 0; i < 5; i++) {
      this.arr.push(Math.ceil(Math.random() * 100))
    }
    return this.arr;
  }
  //替换位置方法
  swap(n, m) {
    let num = this.arr[m]
    this.arr[m] = this.arr[n]
    this.arr[n] = num
  }

// 选择排序
  SelectionSort() {
    for (let j = 0; j < this.arr.length; j++) {
      let min = j;
      for (let i = j + 1; i < this.arr.length; i++) {
        if (this.arr[min] > this.arr[i]) {
          this.swap(min, i)
        }
      }
    }
    return this.arr;
  }
}

3.快速排序(Quicksort)

原理:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序
代码:

class ArrayList {
  constructor() {
    this.arr = []
  }
  //随机数组
  random() {
    for (let i = 0; i < 5; i++) {
      this.arr.push(Math.ceil(Math.random() * 100))
    }
    return this.arr;
  }

	// 快速排序
  Quicksort() {
    return this._quick(this.arr)
  }
  _quick(arr) {
  //定义三个数据
    let left = []
    let center = []
    let right = [];
    //获取中间值下标
    let num = Math.ceil(arr.length / 2)
    //如果数组为空了,就返回arr
    if (arr.length === 0) {
      return arr;
    }
    //循环判断  小于num放在left,相等的放在center,大于num放在right
    for (let i = 0; i < arr.length; i++) {
      if (arr[i] < arr[num]) {
        left.push(arr[i])
      } else if (arr[i] > arr[num]) {
        right.push(arr[i])
      } else {
        center.push(arr[i])
      }
    }
    //递归调用
    return [...this._quick(left), ...center, ...this._quick(right)]
  }
}