目录
堆
节点结构:
data
var items = []
插入:
this.push = function (element) {
items.push(element)
}
删除:
this.pop = function () {
return items.pop()
}
查找:
this.peek = function () {
return items[items.length - 1]
}
队列
节点结构:
data
var items = []
插入:
this.enqueue = function (element) {
items.push(element)
}
删除:
this.dequeue = function () {
return items.shift()
}
查找:
this.front = function () {
return items[0]
}
优先级队列
节点结构:
data,priority
function QueueElement(element, priority) {
this.element = element
this.priority = priority
}
插入:
this.enqueue = function (element, priority) {
// 1.根据传入的元素, 创建新的QueueElement
var queueElement = new QueueElement(element, priority)
// 2.获取传入元素应该在正确的位置
if (this.isEmpty()) {
items.push(queueElement)
} else {
var added = false
for (var i = 0; i < items.length; i++) {
// 注意: 我们这里是数字越小, 优先级越高
if (queueElement.priority < items[i].priority) {
items.splice(i, 0, queueElement)
added = true
break
}
}
// 遍历完所有的元素, 优先级都大于新插入的元素时, 就插入到最后
if (!added) {
items.push(queueElement)
}
}
链表
节点结构:
data,next
function Node(element) {
this.element = element
this.next = null
}
链表结构:
node,length,head,api
function Node(element) {
this.element = element
this.next = null
}
// 链表中的属性
this.length = 0
this.head = null
插入:
// 根据下标插入元素
LinkedList.prototype.insert = function (position, element) {
// 1.检测越界问题: 越界插入失败
if (position < 0 || position > this.length) return false
// 2.定义变量, 保存信息
var newNode = new Node(element)
var current = this.head
var previous = null
index = 0
// 3.判断是否列表是否在第一个位置插入
if (position == 0) {
newNode.next = current
this.head = newNode
} else {
while (index++ < position) {
previous = current
current = current.next
}
newNode.next = current
previous.next = newNode
}
// 4.length+1
this.length++
return true
}
删除:
// 根据位置移除节点
LinkedList.prototype.removeAt = function (position) {
// 1.检测越界问题: 越界移除失败, 返回null
if (position < 0 || position >= this.length) return null
// 2.定义变量, 保存信息
var current = this.head
var previous = null
var index = 0
// 3.判断是否是移除第一项
if (position === 0) {
this.head = current.next
} else {
while (index++ < position) {
previous = current
current = current.next
}
previous.next = current.next
}
// 4.length-1
this.length--
// 5.返回移除的数据
return current.element
}
查找:
// 根据元素获取链表中的位置
LinkedList.prototype.indexOf = function (element) {
// 1.定义变量, 保存信息
var current = this.head
index = 0
// 2.找到元素所在的位置
while (current) {
if (current.element === element) {
return index
}
index++
current = current.next
}
// 3.来到这个位置, 说明没有找到, 则返回-1
return -1
}
补充:
1、查找,添加等方法多借用新定义current来完成遍历等操作
2、删除,添加操作:借助current和previou两个指针分别指向插入位置和插入之前的位置
双向链表
节点结构:
data,next,pre
function Node(element) {
this.element = element
this.next = null
this.prev = null // 新添加的
}
双向链表结构:
node,head,tail,length,api
// 创建节点构造函数
function Node(element) {
this.element = element
this.next = null
this.prev = null // 新添加的
}
// 定义属性
this.length = 0
this.head = null
this.tail = null // 新添加的
插入:
DoublyLinkedList.prototype.insert = function (position, element) {
// 1.判断越界的问题
if (position < 0 || position > this.length) return false
// 2.创建新的节点
var newNode = new Node(element)
// 3.判断插入的位置
if (position === 0) { // 在第一个位置插入数据
// 判断链表是否为空
if (this.head == null) {
this.head = newNode
this.tail = newNode
} else {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
}
} else if (position === this.length) { // 插入到最后的情况
// 思考: 这种情况是否需要判断链表为空的情况呢? 答案是不需要, 为什么?
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
} else { // 在中间位置插入数据
// 定义属性
var index = 0
var current = this.head
var previous = null
// 查找正确的位置
while (index++ < position) {
previous = current
current = current.next
}
// 交换节点的指向顺序
newNode.next = current
newNode.prev = previous
current.prev = newNode
previous.next = newNode
}
// 4.length+1
this.length++
return true
}
删除:
// 根据位置删除对应的元素
DoublyLinkedList.prototype.removeAt = function (position) {
// 1.判断越界的问题
if (position < 0 || position >= this.length) return null
// 2.判断移除的位置
var current = this.head
if (position === 0) {
if (this.length == 1) {
this.head = null
this.tail = null
} else {
this.head = this.head.next
this.head.prev = null
}
} else if (position === this.length -1) {
current = this.tail
this.tail = this.tail.prev
this.tail.next = null
} else {
var index = 0
var previous = null
while (index++ < position) {
previous = current
current = current.next
}
previous.next = current.next
current.next.prev = previous
}
// 3.length-1
this.length--
return current.element
}
查找:
// 根据元素获取在链表中的位置
DoublyLinkedList.prototype.indexOf = function (element) {
// 1.定义变量保存信息
var current = this.head
var index = 0
// 2.查找正确的信息
while (current) {
if (current.element === element) {
return index
}
index++
current = current.next
}
// 3.来到这个位置, 说明没有找到, 则返回-1
return -1
}
补充:
1、双向链表的插入与删除都需要考虑空,第一个,最后一个,中间四种情况
2、链表此类一般先借用head,tail进行操作最后改正head
3、插入删除要考虑四根指针
哈希表
特点
1、提供极快的插入删除,查找,操作。
2、数据无序,key值不重复
哈希化
将大数字转换成数组范围内下标的过成,被称为哈希化
哈希函数
哈希化的代码实现放在一个函数中,此函数被称为哈希函数
哈希表
最终将数据插入到的数组,对整个结构的封装我们称之为哈希表
相当于可以将数组的内容与下标相关联,通过查找下标的方式来查找内容
哈希冲突
1、链地址法
若有重复,将重复的数据插入到链表的首段或末端
2、开放地址法
寻找空白来存放
1、线性探测
若重复,下标x+1,,x+2 ,...
2、二次探测
若重复,下标x+1^2 ,x+2^2,....
3、再次哈希
把关键字用另一个哈希函数再次哈希化得到步长,x+hash(x)
哈希函数的实现
得到hashcode,来确定要放在哪里
function hashFunc(str, max) {
// 1.初始化hashCode的值
var hashCode = 0
// 2.霍纳算法, 来计算hashCode的数值
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i)
}
// 3.取模运算
hashCode = hashCode % max
return hashCode
}
//max:数组最大长度
哈希表结构
this.storage = [] //存放数据的数组
this.count = 0 //占了多少位置
this.limit = 8 //哈希表的大小
插入:
// 插入数据方法
HashTable.prototype.put = function (key, value) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)
// 2.取出数组(也可以使用链表)
// 数组中放置数据的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ] [ [k,v] ] ]
var bucket = this.storage[index]
// 3.判断这个数组是否存在
if (bucket === undefined) {
// 3.1创建桶
bucket = []
this.storage[index] = bucket
}
// 4.判断是新增还是修改原来的值.
var override = false
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
tuple[1] = value
override = true
}
}
// 5.如果是新增, 前一步没有覆盖
if (!override) {
bucket.push([key, value])
this.count++
if (this.count > this.limit * 0.75) {
var primeNum = this.getPrime(this.limit * 2)
this.resize(primeNum)
}
}
}
删除:
// 删除数据
HashTable.prototype.remove = function (key) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)
// 2.获取对应的bucket
var bucket = this.storage[index]
// 3.判断同是否为null, 为null则说明没有对应的数据
if (bucket == null) {
return null
}
// 4.遍历bucket, 寻找对应的数据
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
bucket.splice(i, 1)
this.count--
// 缩小数组的容量
if (this.limit > 7 && this.count < this.limit * 0.25) {
var primeNum = this.getPrime(Math.floor(this.limit / 2))
this.resize(primeNum)
}
}
return tuple[1]
}
// 5.来到该位置, 说明没有对应的数据, 那么返回null
return null
}
查找:
// 获取存放的数据
HashTable.prototype.get = function (key) {
// 1.获取key对应的index
var index = this.hashFunc(key, this.limit)
// 2.获取对应的bucket
var bucket = this.storage[index]
// 3.如果bucket为null, 那么说明这个位置没有数据
if (bucket == null) {
return null
}
// 4.有bucket, 判断是否有对应的key
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
if (tuple[0] === key) {
return tuple[1]
}
}
// 5.没有找到, return null
return null
}
哈希扩容
// 哈希表扩容
HashTable.prototype.resize = function (newLimit) {
// 1.保存旧的数组内容
var oldStorage = this.storage
// 2.重置属性
this.limit = newLimit
this.count = 0
this.storage = []
// 3.遍历旧数组中的所有数据项, 并且重新插入到哈希表中
oldStorage.forEach(function (bucket) {
// 1.bucket为null, 说明这里面没有数据
if (bucket == null) {
return
}
// 2.bucket中有数据, 那么将里面的数据重新哈希化插入
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i]
this.put(tuple[0], tuple[1])
}
}).bind(this)
}
补充:
哈希表算法大多按步骤
1、由key获取index
2、据index获取bucket,判断是否存在
3、线性查找
二叉查找树
特点:
左子树>根节点>右子树
节点结构:
function Node(key) {
this.key = key
this.left = null
this.right = null
}
插入:
二叉树方法基本都用上了递归。
// 向树中插入数据
BinarySerachTree.prototype.insert = function (key) {
// 1.根据key创建对应的node
var newNode = new Node(key)
// 2.判断根节点是否有值
if (this.root === null) {
this.root = newNode
} else {
this.insertNode(this.root, newNode)
}
}
BinarySerachTree.prototype.insertNode = function (node, newNode) {
if (newNode.key < node.key) { // 1.准备向左子树插入数据
if (node.left === null) { // 1.1.node的左子树上没有内容
node.left = newNode
} else { // 1.2.node的左子树上已经有了内容
this.insertNode(node.left, newNode)
}
} else { // 2.准备向右子树插入数据
if (node.right === null) { // 2.1.node的右子树上没有内容
node.right = newNode
} else { // 2.2.node的右子树上有内容
this.insertNode(node.right, newNode)
}
}
}
遍历:
// 遍历方法
// 先序遍历
BinarySerachTree.prototype.preOrderTraversal = function (handler) {
this.preOrderTranversalNode(this.root, handler)
}
BinarySerachTree.prototype.preOrderTranversalNode = function (node, handler) {
if (node !== null) {
handler(node.key)
this.preOrderTranversalNode(node.left, handler)
this.preOrderTranversalNode(node.right, handler)
}
}
// 中序遍历
BinarySerachTree.prototype.inOrderTraversal = function (handler) {
this.inOrderTraversalNode(this.root, handler)
}
BinarySerachTree.prototype.inOrderTraversalNode = function (node, handler) {
if (node !== null) {
this.inOrderTraversalNode(node.left, handler)
handler(node.key)
this.inOrderTraversalNode(node.right, handler)
}
}
// 后续遍历
BinarySerachTree.prototype.postOrderTraversal = function (handler) {
this.postOrderTraversalNode(this.root, handler)
}
BinarySerachTree.prototype.postOrderTraversalNode = function (node, handler) {
if (node !== null) {
this.postOrderTraversalNode(node.left, handler)
this.postOrderTraversalNode(node.right, handler)
handler(node.key)
}
}
查找
与遍历不同,其为return
BinarySerachTree.prototype.search = function (key) {
return this.searchNode(this.root, key)
}
BinarySerachTree.prototype.searchNode = function (node, key) {
// 1.如果传入的node为null那么, 那么就退出递归
if (node === null) {
return false
}
// 2.判断node节点的值和传入的key大小
if (node.key > key) { // 2.1.传入的key较小, 向左边继续查找
return this.searchNode(node.left, key)
} else if (node.key < key) { // 2.2.传入的key较大, 向右边继续查找
return this.searchNode(node.right, key)
} else { // 2.3.相同, 说明找到了key
return true
}
}
删除:
思路:
- 找到删除的节点
- 叶节点:直接删除
- 有一个子节点的节点:
- 判断是该节点是有左子树还是有右子树(current.left==null)
- 判断该节点在父节点的左还是右(isLeft)
- 爷孙链接
- 有两个子节点的节点:
- 找前驱/后继(左子树最大值/右子树最小值)
- 循环找后继
- 若后继非删除子节点直接子节点,1、将删除节点右子树赋值给后继,2、将后继父节点与后继子节点链接
- 父节点与后继链接
- 将删除节点的左子树给后继
- 找前驱/后继(左子树最大值/右子树最小值)
需要三个变量:current parent isLeft
每一次都要考虑,根节点,左子树,右子树
// 删除结点
BinarySerachTree.prototype.remove = function (key) {
// 1.定义临时保存的变量
var current = this.root
var parent = this.root
var isLeftChild = true
// 2.开始查找节点
while (current.key !== key) {
parent = current
if (key < current.key) {
isLeftChild = true
current = current.left
} else {
isLeftChild = false
current = current.right
}
// 如果发现current已经指向null, 那么说明没有找到要删除的数据
if (current === null) return false
}
// 3.删除的结点是叶结点
if (current.left === null && current.right === null) {
if (current == this.root) {
this.root == null
} else if (isLeftChild) {
parent.left = null
} else {
parent.right = null
}
}
// 4.删除有一个子节点的节点
else if (current.right === null) {
if (current == this.root) {
this.root = current.left
} else if (isLeftChild) {
parent.left = current.left
} else {
parent.right = current.left
}
} else if (current.left === null) {
if (current == this.root) {
this.root = current.right
} else if (isLeftChild) {
parent.left = current.right
} else {
parent.right = current.right
}
}
// 5.删除有两个节点的节点
else {
// 1.获取后继节点
var successor = this.getSuccessor(current)
// 2.判断是否是根节点
if (current == this.root) {
this.root = successor
} else if (isLeftChild) {
parent.left = successor
} else {
parent.right = successor
}
// 3.将删除节点的左子树赋值给successor
successor.left = current.left
}
return true
}
// 找后继的方法
BinarySerachTree.prototype.getSuccessor = function (delNode) {
// 1.使用变量保存临时的节点
var successorParent = delNode
var successor = delNode
var current = delNode.right // 要从右子树开始找
// 2.寻找节点
while (current != null) {
successorParent = successor
successor = current
current = current.left
}
// 3.如果是删除图中15的情况, 还需要如下代码
if (successor != delNode.right) {
successorParent.left = successorParent.right
successor.right = delNode.right
}
}
-
哈夫曼树与编码
- 待补充
-
AVL树
- 待补充
-
B 树与 B+ 树
- 待补充
红黑树
待补充....
图
术语:
- 度:相邻顶点的数量
- 简单路径:不包含重复的顶点
- 回路:第一个顶点与最后一个顶点相同的路径
图结构
邻接矩阵
让每个节点和一个整数相关联,整数作为数组的下标值
用一个二维数组表示顶点之间的联系
邻接表
由图中每个顶点以及和顶点相邻的定点列表组成
结构:
// 属性
this.vertexes = [] // 存储顶点
this.adjList = new Dictionay() // 存储边
插入:
// 添加方法
//添加顶点
Graph.prototype.addVertex = function (v) {
this.vertexes.push(v)
this.adjList.set(v, [])
}
// 添加边
Graph.prototype.addEdge = function (v, w) {
this.adjList.get(v).push(w)
this.adjList.get(w).push(v)
}
遍历:
广度优搜索(BFS)
基于队列,入队列的顶点先被搜索
// 广度优先算法
Graph.prototype.bfs = function (v, handler) {
// 1.初始化颜色
var color = this.initializeColor()
// 2.创建队列
var queue = new Queue()
// 3.将传入的顶点放入队列中
queue.enqueue(v)
// 4.从队列中依次取出和放入数据
while (!queue.isEmpty()) {
// 4.1.从队列中取出数据
var qv = queue.dequeue()
// 4.2.获取qv相邻的所有顶点
var qAdj = this.adjList.get(qv)
// 4.3.将qv的颜色设置成灰色
color[qv] = "gray"
// 4.4.将qAdj的所有顶点依次压入队列中
for (var i = 0; i < qAdj.length; i++) {
var a = qAdj[i]
if (color[a] === "white") {
color[a] = "gray"
queue.enqueue(a)
}
}
// 4.5.因为qv已经探测完毕, 将qv设置成黑色
color[qv] = "black"
// 4.6.处理qv
if (handler) {
handler(qv)
}
}
深度优先搜索(DFS)
基于栈或递归,将顶点存入栈中,顶点是沿着路经被探索的,存在新的相邻顶点就访问
// dfs的递归调用方法
Graph.prototype.dfsVisit = function (u, color, handler) {
// 1.将u的颜色设置为灰色
color[u] = "gray"
// 2.处理u顶点
if (handler) {
handler(u)
}
// 3.u的所有邻接顶点的访问
var uAdj = this.adjList.get(u)
for (var i = 0; i < uAdj.length; i++) {
var w = uAdj[i]
if (color[w] === "white") {
this.dfsVisit(w, color, handler)
}
}
// 4.将u设置为黑色
color[u] = "black"
}
-
最短路径算法:Floyd,Dijkstra
- 待补充
-
最小生成树算法:Prim,Kruskal
- 待补充
排序
冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
代码
function bubbleSort(arr) {
var len = arr.length;
for ( var i = 0; i < len; i++) {
for ( var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { //相邻元素两两对比
var temp = arr[j+1]; //元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
总结
内层循环:执行冒泡操作(冒泡的过程随次数缩短)
外层循环:控制冒泡次数
选择排序
- 初始状态:无序区为R[1..n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- n-1趟结束,数组有序化了。
代码
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for ( var i = 0; i < len - 1; i++) {
minIndex = i;
for ( var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { //寻找最小的数
minIndex = j; //将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
总结
内层循环:寻找无序区中最小的数
外层循环:控制增加有序区数据(与最小交换)
快速排序
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;(通过两个指针实现)
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
代码
// 选择枢纽
ArrayList.prototype.median = function (left, right) {
// 1.求出中间的位置
var center = Math.floor((left + right) / 2)
// 2.判断并且进行交换
if (this.array[left] > this.array[center]) {
this.swap(left, center)
}
if (this.array[center] > this.array[right]) {
this.swap(center, right)
}
if (this.array[left] > this.array[right]) {
this.swap(left, right)
}
// 3.巧妙的操作: 将center移动到right - 1的位置.
this.swap(center, right - 1)
// 4.返回pivot
return this.array[right - 1]
}
// 快速排序实现
ArrayList.prototype.quickSort = function () {
this.quickSortRec(0, this.array.length - 1)
}
ArrayList.prototype.quickSortRec = function (left, right) {
// 0.递归结束条件
if (left >= right) return
// 1.获取枢纽
var pivot = this.median(left, right)
// 2.开始进行交换
var i = left
var j = right - 1
while (true) {
while (this.array[++i] < pivot) { }//1、左边碰见大于枢纽,停下
while (this.array[--j] > pivot) { }//2、右边碰见小于枢纽,停下
if (i < j) {
this.swap(i, j) //3、i<j交换
} else {
break
}
}
// 3.将枢纽放在正确的位置 //4、放正确位置
this.swap(i, right - 1)
// 4.递归调用左边
this.quickSortRec(left, i - 1)
this.quickSortRec(i + 1, right)
}
总结:
以分而治之为核心思想
希尔排序
- 选择增量(gap),将数据分组
- 对每组数据进行插入排序
- 逐渐减少增量,直至为1
代码
ArrayList.prototype.shellSort = function () {
// 1.获取数组的长度
var length = this.array.length
// 2.根据长度计算增量
var gap = Math.floor(length / 2)
// 3.增量不断变量小, 大于0就继续排序
while (gap > 0) {
// 4.实现插入排序
for (var i = gap; i < length; i++) { //获取数据加入有序,不同分组
// 交替插入
// 4.1.保存临时变量
var j = i //找位置的指针
var temp = this.array[i] //存起来
// 4.2.插入排序的内存循环
while (j > gap - 1 && this.array[j - gap] > temp) {
this.array[j] = this.array[j - gap] //元素后移
j -= gap //指针向前推进寻找合适位置
}
// 4.3.将选出的j位置设置为temp
this.array[j] = temp //根据指针插入
}
// 5.重新计算新的间隔
gap = Math.floor(gap / 2)
}
}
总结:
外层循环:控制分组增量(gap)逐渐减少,直至为1
中间循环:获取数据加入有序
内层循环:寻找位置
中外内共同完成插入排序
插入排序
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
代码
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for ( var i = 1; i < len; i++) {
preIndex = i - 1; //有序的边界,i为无需第一个
current = arr[i]; //先存起来
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex];
preIndex--; //用于查找位置的指针
}
arr[preIndex+1] = current; //,根据指针插入
}
return arr;
}
总结:
- 选择排序,先从无序区找最小加入有序区
- 插入排序,从无序区找一个,依次与有序区比较直至合适位置
外层循环:从第1个位置开始获取数据,向有序区插入
内层循环:与有序区的数据依次比较,找到合适位置(若大则后移)