-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 135 KB
/
content.json
1
[{"title":"JavaScript 散列表","date":"2020-07-06T12:28:23.000Z","path":"2020/07/06/2020-07-06-JavaScript-散列表/","text":"拉链法实现散列表12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758/** * 拉链法实现散列表 主要模拟散列表 链表用Object模拟 */class HashTable { constructor(size = 100) { this.size = size this.hashTable = new Array(this.size) } put(key, value) { const hashKey = this.hash(key) if (!this.hashTable[hashKey]) { this.hashTable[hashKey] = { [key]: value, } } else { this.hashTable[hashKey][key] = value } } get(key) { const hashKey = this.hash(key) if (!this.hashTable[hashKey]) { return -1 } return this.hashTable[hashKey][key] } delete(key) { const hashKey = this.hash(key) if (!this.hashTable[hashKey]) { return false } else { if (this.hashTable[hashKey][key] !== undefined) { delete this.hashTable[hashKey][key] return true } else { return false } } } hash(key) { return key % this.size } display() { this.hashTable.forEach((obj) => { Object.keys(obj).forEach((key) => { console.log('key: ' + key + ' : ' + obj[key]) }) }) }}const hashTable = new HashTable(10)for (let i = 0; i < 20; i++) { hashTable.put(i, 'value: ' + i)}hashTable.display() 寻址法实现散列表(线性探测)1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071/** * 开放寻址法 线性探测 */class HashTable { constructor(size = 100) { this.size = size this.hashTable = new Array(this.size) } put(key, value) { let hashKey = this.hash(key) if (!this.hashTable[hashKey]) { this.hashTable[hashKey] = { key, value } return true } while (this.hashTable[hashKey] !== undefined) { hashKey++ } if (hashKey < this.size) { this.hashTable[hashKey] = { key, value } return true } return false } get(key) { let hashKey = this.hash(key) if (!this.hashTable[hashKey]) { return -1 } while (this.hashTable[hashKey] !== undefined) { if (this.hashTable[hashKey].key === key) { return this.hashTable[hashKey].key } hashKey++ } return -1 } delete(key) { let hashKey = this.hash(key) if (!this.hashTable[hashKey]) { return false } else { while (this.hashTable[hashKey] !== undefined) { if (this.hashTable[hashKey].key === key) { delete this.hashTable[hashKey] return true } hashKey++ } return false } } hash(key) { return key % this.size } display() { this.hashTable.forEach((obj) => { console.log('key: ' + obj.key + ' : ' + obj.value) }) }}const hashTable = new HashTable(10)hashTable.put(1, 'value: ' + 1)hashTable.put(11, 'value: ' + 11)hashTable.put(21, 'value: ' + 21)console.log(hashTable.get(21))console.log(hashTable.delete(21))console.log(hashTable)hashTable.display() 实现一个 LRU 缓存淘汰算法 第一种实现 存的 key 查询会慢第二种存 Node 查询会很快 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216/** * 节点 */class Node { constructor(value) { this.value = value this.prev = null this.next = null }}/** * 双向链表 */class LinkedList { constructor() { const head = new Node() const tail = new Node() head.next = tail tail.prev = head this.head = head this.tail = tail } movetoHead(value) { const node = this.findNode(value) // 删除当前位置节点 const nodeNext = node.next nodeNext.prev = node.prev node.prev.next = nodeNext // 放到头 this.addToHead(node.value) } addToHead(value) { const node = new Node(value) const nextNode = this.head.next node.next = nextNode node.prev = this.head nextNode.prev = node this.head.next = node } popTail() { const node = this.tail.prev const nodePrev = node.prev nodePrev.next = this.tail this.tail.prev = nodePrev return node.value } findNode(value) { let node = this.head while (node.next) { if (node.value === value) { return node } node = node.next } return -1 }}/** * @param {number} capacity */var LRUCache = function (capacity) { this.size = 0 this.capacity = capacity this.map = new Map() this.linkedList = new LinkedList()}/** * @param {number} key * @return {number} */LRUCache.prototype.get = function (key) { if (this.map.has(key)) { this.linkedList.movetoHead(key) return this.map.get(key) } else { return -1 }}/** * @param {number} key * @param {number} value * @return {void} */LRUCache.prototype.put = function (key, value) { if (this.map.has(key)) { this.linkedList.movetoHead(key) this.map.set(key, value) } else { if (this.size >= this.capacity) { // 删掉尾部 const tailKey = this.linkedList.popTail() this.map.delete(tailKey) // 插入新的头部 this.linkedList.addToHead(key) this.map.set(key, value) } else { // 插入新的头部 this.linkedList.addToHead(key) this.map.set(key, value) this.size++ } }}/** * Your LRUCache object will be instantiated and called as such: * var obj = new LRUCache(capacity) * var param_1 = obj.get(key) * obj.put(key,value) */class ListNode { constructor(key, value) { this.key = key this.value = value this.next = null this.prev = null }}class LRUCache { constructor(capacity) { this.capacity = capacity this.hashTable = {} this.count = 0 this.dummyHead = new ListNode() this.dummyTail = new ListNode() this.dummyHead.next = this.dummyTail this.dummyTail.prev = this.dummyHead } get(key) { let node = this.hashTable[key] if (node == null) return -1 this.moveToHead(node) return node.value } put(key, value) { let node = this.hashTable[key] if (node == null) { let newNode = new ListNode(key, value) this.hashTable[key] = newNode this.addToHead(newNode) this.count++ if (this.count > this.capacity) { this.removeLRUItem() } } else { node.value = value this.moveToHead(node) } } moveToHead(node) { this.removeFromList(node) this.addToHead(node) } removeFromList(node) { let tempForPrev = node.prev let tempForNext = node.next tempForPrev.next = tempForNext tempForNext.prev = tempForPrev } addToHead(node) { node.prev = this.dummyHead node.next = this.dummyHead.next this.dummyHead.next.prev = node this.dummyHead.next = node } removeLRUItem() { let tail = this.popTail() delete this.hashTable[tail.key] this.count-- } popTail() { let tailItem = this.dummyTail.prev this.removeFromList(tailItem) return tailItem }}var cache = new LRUCache(2 /* 缓存容量 */)cache.put(1, 1)console.log('cache', cache)cache.put(2, 2)console.log('cache', cache)// 返回 1console.log('返回 1', cache.get(1))console.log('cache', cache)cache.put(3, 3) // 该操作会使得关键字 2 作废console.log('cache', cache)console.log('返回 -1', cache.get(2))console.log('cache', cache)cache.put(4, 4) // 该操作会使得关键字 1 作废console.log('cache', cache)cache.get(1) // 返回 -1 (未找到)console.log('cache', cache)cache.get(3) // 返回 3console.log('cache', cache)cache.get(4) // 返回 4","tags":[]},{"title":"JavaScript 二分查找","date":"2020-07-05T11:40:53.000Z","path":"2020/07/05/2020-07-05-JavaScript-二分查找/","text":"有序数组二分查找12345678910111213141516171819202122/** * 二分查找 * @param {Array<Number>} array * @param {Number} num */function binarySearch(array, num) { let left = 0 let right = array.length - 1 while (right >= left) { let middle = Math.floor((left + right) / 2) if (array[middle] === num) { return middle } else if (array[middle] > num) { right = middle - 1 } else { left = middle + 1 } } return -1}console.log(binarySearch([1, 2, 3, 4, 4, 5], 3)) 实现模糊二分查找算法(比如大于等于给定值的第一个元素)123456789101112131415161718192021222324/** * 实现模糊二分查找算法(比如大于等于给定值的第一个元素) * @param {Array<Number>} array * @param {Number} num */function binarySearch(array, num) { let left = 0 let right = array.length - 1 while (right >= left) { let middle = Math.floor((left + right) / 2) if (array[middle] >= num) { if (middle === 0 || array[middle - 1] < num) { return middle } else { right = middle - 1 } } else { left = middle + 1 } } return -1}console.log(binarySearch([5], 4))","tags":[]},{"title":"JavaScript 排序","date":"2020-07-05T04:50:58.000Z","path":"2020/07/05/2020-07-05-JavaScript-排序/","text":"冒泡12345678910111213141516171819/** * 冒泡排序 * @param {Array<Number>} array * 空间 O(1) 时间 O(n^2) */function bubbleSort(array) { for (let i = 0; i < array.length; i++) { const flag = false for (let j = 0; j < array.length - i - 1; j++) { if (array[j] > array[j + 1]) { flag = true ;[array[j], array[j + 1]] = [array[j + 1], array[j]] } } if (!flag) break } return array}console.log(bubbleSort([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0])) 插入123456789101112131415161718192021/** * 插入排序 * @param {Array<Number>} array * 空间 O(1) 时间 O(n^2) */function insertSort(array) { for (let i = 1; i < array.length; i++) { const num = array[i] let j = i for (; j >= 0; j--) { if (num < array[j - 1]) { array[j] = array[j - 1] } else { break } } array[j] = num } return array}console.log(insertSort([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0])) 选择1234567891011121314151617181920/** * 选择排序 * @param {Array<Number>} array * 空间 O(1) 时间 O(n^2) */function selectSort(array) { for (let i = 0; i < array.length; i++) { let minIndex = i for (let j = i + 1; j < array.length; j++) { if (array[minIndex] > array[j]) { minIndex = j } } if (i !== minIndex) { ;[array[i], array[minIndex]] = [array[minIndex], array[i]] } } return array}console.log(selectSort([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0])) 归并12345678910111213141516171819202122232425262728293031323334/** * 归并排序 * @param {Array<Number>} array * O(n) O(nlogn) */function mergeSort(array) { if (!array || !array.length) { return [] } if (array.length === 1) { return array } let mid = Math.floor(array.length / 2) return merge(mergeSort(array.slice(0, mid)), mergeSort(array.slice(mid)))}/** * 合并两个数组 * @param {Array<Number>} arr1 * @param {Array<Number>} arr2 */function merge(arr1, arr2) { let i = arr1.length - 1 let j = arr2.length - 1 let index = i + j + 1 const arr = new Array(i + j + 2) while (i >= 0 && j >= 0) { // 注意--符号在后面,表示先进行计算再减1,这种缩写缩短了代码 arr[index--] = arr1[i] > arr2[j] ? arr1[i--] : arr2[j--] } const tempArr = i >= 0 ? arr1.slice(0, i + 1) : arr2.slice(0, j + 1) const array = tempArr.concat(arr.slice(i + 1 || j + 1)) return array}console.log(mergeSort([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0])) 快速12345678910111213141516171819202122232425262728293031/** * 快速排序 * @param {Array<Number>} array * O(1) O(nlogn) */function quickSort(array, start, end) { if (start >= end) { return } // 分区下标 const pivotIndex = partition(array, start, end) quickSort(array, start, pivotIndex - 1) quickSort(array, pivotIndex + 1, end) return array}// 分区函数function partition(array, start, end) { const pivot = array[end] let i = start for (let j = start; j < end; j++) { if (array[j] < pivot) { if (i !== j) { ;[array[i], array[j]] = [array[j], array[i]] } i++ } } ;[array[i], array[end]] = [array[end], array[i]] return i}console.log(quickSort([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0], 0, 10)) O(n) 查找数组 k 大元素1234567891011121314151617181920212223242526272829303132/** * 找到 K 大元素 * @param {Array<Number>} array * @return {number} */function findK(array, k) { const len = array.length - 1 let pivotIndex = partition(array, 0, len) while (pivotIndex + 1 !== k) { if (pivotIndex + 1 > k) { pivotIndex = partition(array, 0, pivotIndex - 1) } else { pivotIndex = partition(array, pivotIndex + 1, len) } } return array[pivotIndex]}function partition(array, start, end) { const pivot = array[end] let i = start for (let j = start; j < end; j++) { if (array[i] > pivot) { ;[array[i], array[j]] = [array[j], array[i]] i++ } } ;[array[i], array[end]] = [array[end], array[i]] return i}console.log(findK([3, 4, 3, 3, 1, 2, 6, 6, 0, 1, 0], 3))","tags":[]},{"title":"JavaScript 递归","date":"2020-06-22T12:58:20.000Z","path":"2020/06/22/2020-06-22-JavaScript-递归/","text":"编程实现斐波那契数列求值 f(n)=f(n-1)+f(n-2) 123456789101112const fibonacci = (function () { const memory = {} return function (n) { if (n < 2) { return n } if (!memory[n]) { memory[n] = fibonacci(n - 1) + fibonacci(n - 2) } return memory[n] }})() 编程实现求阶乘 n! 123456function fn(n) { if (n < 2) { return n } return n * fn(n - 1)} 编程实现一组数据集合的全排列 12345678910111213141516function permute(list = []) { const result = [] if (list.length < 2) { return list } // 先假设知道了之前的全排列,然后向里面插值 const preRes = permute(list.slice(0, list.length - 1)) const str = list[list.length - 1] for (let i = 0; i < preRes.length; i++) { const ele = preRes[i] for (let j = 0; j <= ele.length; j++) { result.push([...ele.slice(0, j), str, ...ele.slice(j)]) } } return result}","tags":[]},{"title":"JavaScript 实现队列","date":"2020-06-15T03:19:20.000Z","path":"2020/06/15/2020-06-15-JavaScript-实现队列/","text":"顺序队列123456789101112131415161718192021222324class Queue { constructor() { this.list = [] } put (data) { this.list.push(data) } poll() { return this.list.shift() } get size() { return this.list.length }}const queue = new Queue()for (let index = 0; index < 10; index++) { queue.put(index)}for (let index = 0; index < 10; index++) { console.log(queue.poll())} 链式队列1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798class Node { constructor (value) { this.value = value this.next = null }}class LinkedList { constructor () { this.length = 0 this.head = new Node('head') } // 新增 add (value) { this.insert(this.length, value) } // 插入 insert (index, value) { // 越界 可以添加到队尾 if (index > this.length || index < 0) { return false } const preNode = index === 0 ? this.head : this.find(index - 1) const newNode = new Node(value) newNode.next = preNode.next preNode.next = newNode this.length++ return true } // 查找 find (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return null } let i = 0 let node = this.head.next while (i++ < index) { node = node.next } return node } // 删除 delete (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return false } const node = index === 0 ? this.head : this.find(index - 1) node.next = node.next ? node.next.next : null this.length-- return node } // 检查越界 checkRange (index) { return index + 1 > this.length || index < 0 } // 遍历显示所有节点 display () { let currentNode = this.head.next // 忽略头指针的值 while (currentNode !== null) { console.log(currentNode.value) currentNode = currentNode.next } } get size() { return this.length }}class Queue { constructor() { this.list = new LinkedList() } put (data) { this.list.add(data) } poll() { return this.list.delete(0) } get size() { return this.list.size }}const queue = new Queue()for (let index = 0; index < 10; index++) { queue.put(index)}for (let index = 0; index < 10; index++) { console.log(queue.poll())} 循环队列1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556/** * 循环队列 浪费一个空间 */class Queue { constructor(num = 5) { this.MAX_SIZE = num + 1 this.list = new Array(this.MAX_SIZE) this.front = 0 this.rear = 0 } put (data) { // 队列满条件 if ((this.rear + 1) % this.MAX_SIZE === this.front) { return false } this.list[this.rear] = data this.rear = (this.rear + 1) % this.MAX_SIZE return true } poll() { if (this.size === 0) { return -1 } const data = this.list[this.front] this.front = (this.front + 1) % this.MAX_SIZE return data } get size() { return (this.rear - this.front + this.MAX_SIZE) % this.MAX_SIZE } isEmpty() { return this.rear === this.front }}const queue = new Queue(5)for (let index = 0; index < 5; index++) { console.log(queue.put(index))}for (let index = 0; index < 2; index++) { console.log(queue.poll())}for (let index = 10; index < 13; index++) { console.log(queue.put(index))}for (let index = 0; index < 6; index++) { console.log(queue.poll())}","tags":[]},{"title":"JavaScript 实现栈","date":"2020-06-10T12:39:32.000Z","path":"2020/06/10/2020-06-10-JavaScript-实现栈/","text":"数组实现 顺序栈123456789101112131415161718192021222324class Stack { constructor() { this.list = [] } push (data) { this.list.push(data) } pop() { return this.list.pop() }}const stack = new Stack()for (let index = 0; index < 10; index++) { stack.push(index)}for (let index = 0; index < 10; index++) { console.log(stack.pop())} 链表实现 链式栈123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293class Node { constructor (value) { this.value = value this.next = null }}class LinkedList { constructor () { this.length = 0 this.head = new Node('head') } // 新增 add (value) { this.insert(this.length, value) } // 插入 insert (index, value) { // 越界 可以添加到队尾 if (index > this.length || index < 0) { return false } const preNode = index === 0 ? this.head : this.find(index - 1) const newNode = new Node(value) newNode.next = preNode.next preNode.next = newNode this.length++ return true } // 查找 find (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return null } let i = 0 let node = this.head.next while (i++ < index) { node = node.next } return node } // 删除 delete (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return false } const node = index === 0 ? this.head : this.find(index - 1) node.next = node.next ? node.next.next : null this.length-- return true } // 检查越界 checkRange (index) { return index + 1 > this.length || index < 0 } // 遍历显示所有节点 display () { let currentNode = this.head.next // 忽略头指针的值 while (currentNode !== null) { console.log(currentNode.value) currentNode = currentNode.next } }}class Stack { constructor() { this.linkList = new LinkedList() } // 链表存放到最后位置 时间复杂度更改 因此我们直接插入第一位 push (data) { this.linkList.insert(0, data) } pop() { const node = this.linkList.find(0) this.linkList.delete(0) return node }}const stack = new Stack()for (let index = 0; index < 10; index++) { stack.push(index)}for (let index = 0; index < 10; index++) { console.log(stack.pop())} 浏览器前进后退123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657class Stack { constructor() { this.list = [] } push (data) { this.list.push(data) } pop() { return this.list.pop() } get size() { return this.list.length }}const forward = new Stack()const backward = new Stack()for (let index = 0; index < 10; index++) { backward.push(index)}function go() { if (!forward.size) { console.log('没有前路了') return } backward.push(forward.pop())}function back() { if (!backward.size) { console.log('没有退路了') return } forward.push(backward.pop())}back()go()back()go()back()go()back()go()back()go()back()back()back()console.log(backward, forward)","tags":[]},{"title":"JavaScript 实现链表","date":"2020-05-20T02:34:14.000Z","path":"2020/05/20/2020-05-20JavaScript-实现链表/","text":"12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182class Node { constructor (value) { this.value = value this.next = null }}class LinkedList { constructor () { this.length = 0 this.head = new Node('head') } // 新增 add (value) { this.insert(this.length, value) } // 插入 insert (index, value) { // 越界 可以添加到队尾 if (index > this.length || index < 0) { return false } const preNode = index === 0 ? this.head : this.find(index - 1) const newNode = new Node(value) newNode.next = preNode.next preNode.next = newNode this.length++ return true } // 查找 find (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return null } let i = 0 let node = this.head.next while (i++ < index) { node = node.next } return node } // 删除 delete (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return false } const node = index === 0 ? this.head : this.find(index - 1) node.next = node.next ? node.next.next : null this.length-- return true } // 检查越界 checkRange (index) { return index + 1 > this.length || index < 0 } // 遍历显示所有节点 display () { let currentNode = this.head.next // 忽略头指针的值 while (currentNode !== null) { console.log(currentNode.value) currentNode = currentNode.next } }}// Testconst LList = new LinkedList()LList.add('chen')LList.add('curry')LList.add('sang')LList.add('zhao')LList.display() // chen -> curry -> sang -> zhaoconsole.log('-------------insert item------------')LList.insert(1, 'qian') // 首元素后插入LList.insert(5, 'zhou') // 尾元素后插入LList.display() // chen -> qian -> curry -> sang -> zhao -> zhouconsole.log('-------------delete item------------')LList.delete(1)LList.delete(0)LList.display() // curry -> sang -> zhao -> zhouconsole.log('-------------find by index------------')console.log(LList.find(0)); 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120class Node { constructor (value) { this.value = value this.next = null }}class CircleLinkedList { constructor () { this.length = 0 this.head = null this.tail = null } // 新增 add (value) { this.insert(this.length, value) } // 插入 insert (index, value) { // 越界 可以添加到队尾 if (index > this.length || index < 0) { return false } const newNode = new Node(value) if (this.length === 0) { this.head = this.tail = newNode this.length++ return true } if (index === 0) { newNode.next = this.head this.tail.next = newNode this.head = newNode this.length++ return true } if (index === this.length) { this.tail.next = newNode newNode.next = this.head this.tail = newNode this.length++ return true } const preNode = this.find(index - 1) newNode.next = preNode.next preNode.next = newNode this.length++ return true } // 查找 find (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return null } let i = 0 let node = this.head while (i++ < index) { node = node.next } return node } // 删除 delete (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return false } if (this.length === 1) { this.head = null this.tail = null this.length-- return true } if (index === 0) { this.length-- this.tail.next = this.head = this.head.next return true } const node = this.find(index - 1) if (index + 1 === this.length) { this.length-- this.tail = node node.next = this.head return true } this.length-- node.next = node.next.next return true } // 检查越界 checkRange (index) { return index + 1 > this.length || index < 0 } // 遍历显示所有节点 display () { let index = 0 let currentNode = this.head while (index++ < this.length) { console.log(currentNode.value) currentNode = currentNode.next } }}// Testconst LList = new CircleLinkedList()LList.add('chen')LList.add('curry')LList.add('sang')LList.add('zhao')LList.display() // chen -> curry -> sang -> zhaoconsole.log('-------------insert item------------')LList.insert(1, 'qian') // 首元素后插入LList.insert(5, 'zhou') // 尾元素后插入LList.display() // chen -> qian -> curry -> sang -> zhao -> zhouconsole.log('-------------delete item------------')LList.delete(1)LList.delete(0)LList.display() // curry -> sang -> zhao -> zhouconsole.log('-------------find by index------------')console.log(LList.find(0)) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091class Node { constructor (value) { this.value = value this.pre = null this.next = null }}class DoubleLinkedList { constructor () { this.length = 0 this.head = new Node('head') } // 新增 add (value) { this.insert(this.length, value) } // 插入 insert (index, value) { // 越界 可以添加到队尾 if (index > this.length || index < 0) { return false } const newNode = new Node(value) const preNode = index === 0 ? this.head : this.find(index - 1) newNode.pre = preNode if (preNode.next) { newNode.next = preNode.next preNode.next.pre = newNode } preNode.next = newNode this.length++ return true } // 查找 find (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return null } let i = 0 let node = this.head.next while (i++ < index) { node = node.next } return node } // 删除 delete (index) { // 越界 或者 链表为空 if (this.checkRange(index) || !this.length) { return false } const node = this.find(index) node.pre.next = node.next if (node.next) { node.next.pre = node.pre } this.length-- return true } // 检查越界 checkRange (index) { return index + 1 > this.length || index < 0 } // 遍历显示所有节点 display () { let currentNode = this.head.next // 忽略头指针的值 while (currentNode !== null) { console.log(currentNode.value) currentNode = currentNode.next } }}// Testconst LList = new DoubleLinkedList()console.log('-------------add item------------')LList.add('chen')LList.add('curry')LList.add('sang')LList.add('zhao')LList.display() // chen -> curry -> sang -> zhaoconsole.log('-------------insert item------------')LList.insert(1, 'qian') // 首元素后插入LList.insert(5, 'zhou') // 尾元素后插入LList.display() // chen -> qian -> curry -> sang -> zhao -> zhouconsole.log('-------------delete item------------')LList.delete(1)LList.delete(0)LList.display() // curry -> sang -> zhao -> zhouconsole.log('-------------find by index------------')console.log(LList.find(0))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"JavaScript 两个有序数组排序","date":"2020-05-12T02:17:57.000Z","path":"2020/05/12/2020-05-12-JavaScript-两个有序数组排序/","text":"输入:nums1 = [1,2,3,0,0,0], m = 3nums2 = [2,5,6], n = 3输出: [1,2,2,3,5,6] 12345678910function merge(nums1, m, nums2, n) { let len = m + n while (n > 0) { if (m <= 0) { nums1[--len] = nums2[--n] continue } nums1[--len] = nums1[m - 1] > nums2[n - 1] ? nums1[--m] : nums2[--n] }}","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"JavaScript 大小固定有序数组","date":"2020-05-11T10:12:46.000Z","path":"2020/05/11/2020-05-11-JavaScript-大小固定有序数组/","text":"123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148/** * 1. 做什么 大小固定有序数组,支持增 删 改 查 * 2. 做成什么样 技术指标 空间复杂度和时间复杂度 尽量低 * 3. 实现方案 插入位置(涉及增和改) 使用二分查找(稀疏数组) * 4. 遇到问题 稀疏数组插入搬运问题 */function sparseArrayInsertIndex (arrs, num) { let left = 0, right = arrs.length - 1 let index = -1 while (left <= right) { while (left < right && typeof arrs[right] !== 'number') right-- while (left < right && typeof arrs[left] !== 'number') left++ lastLeft = left lastRight = right let mid = left + ((right - left) >> 1) while (mid < right && typeof arrs[mid] !== 'number') mid++ if (arrs[mid] > num) { right = mid - 1 } else if (arrs[mid] < num) { left = mid + 1 } else { index = mid } } if (index !== -1) { // 取最后一个相对的插入 while (arrs[index] === num) { index++ } } else { index = right } return index}const preArraySize = 4class SortArray { constructor (size = preArraySize) { // 存储的数量 this._size = 0 // 默认长度为4 this._array = new Array(size) this.length = size } /** * 添加 * @param {*} item */ add (item) { // 添加第一个特殊处理 if (this._size === 0) { this._array[0] = item this._size = 1 } else { if (this._size === this.length) { console.log('数组满了') return } const index = sparseArrayInsertIndex(this._array, item) // 计算出正确下标 插值 this._set(index, item) } } /** * 根据下标删除 * @param {*} index */ remove (index) { if (this._rangeCheck(index)) { console.log('下标越界') return -1 } this._size-- delete this._array[index] return index } /** * 根据下标设置值 * @param {*} index * @param {*} item */ modify (index, item) { if (this._rangeCheck(index)) { console.log('modify 下标越界') return -1 } if (this._size === 0) { this._array[0] = item this._size = 1 } else { delete this._array[index] this.add(item) } } /** * 根据下标设置值 * @param {*} index * @param {*} item */ _set (index, item) { console.log('set', index, item, this._array) // 未赋值的 直接赋值 if (this._array[index] === undefined) { this._array[index] = item } else { // todo // 1 计算前后哪里稀疏 // 2 如果前面稀疏 向前面搬移 // 3 如果后面稀疏 向后面搬移 // 没想到特别好的方案 } this._size++ } /** * 根据下标取值 * @param {*} index */ get (index) { if (this._rangeCheck(index)) { console.log('get 下标越界') return -1 } return this._array[index] } /** * @param {*} index * 下标越界检查 */ _rangeCheck (index) { return index > this.length - 1 || index < 0 } /** * 当前存储的数据个数 */ get size () { return this._size }}const arr = new SortArray(10)for (let i = 0; i < 7; i++) { arr.modify(i, i)}arr.add(100)arr.add(33)arr.add(44)arr.remove(3)arr.add(3)console.log(arr)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"JavaScript 动态扩容数组","date":"2020-05-09T09:07:12.000Z","path":"2020/05/09/2020-05-09-JavaScript-动态扩容数组/","text":"123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100/** * 1. JavaScript 中的数组和其他语言的数组不太一样 * 2. 原生就支持动态扩容 * 3. 使用 ArrayBuffer 模拟其他语言数组 实现数组的动态扩容 * 4. Uint8Array 来做 DataView */const preArraySize = 4class DynamicArray { constructor (size = preArraySize) { // 存储的数量 this._size = 0 // 最后一个的下标 this._lastIndex = 0 // 默认长度为4 this._array = new Uint8Array(size) } /** * 添加 * @param {*} item */ add (item) { this.set(this._lastIndex + 1, item) } /** * 根据下标删除 * @param {*} index */ remove (index) { if (this.rangeCheck(index)) { console.log('下标越界') return undefined } this._array[index] = 0 this._size-- return index } /** * 根据下标设置值 * @param {*} index * @param {*} item */ set (index, item) { this._lastIndex = Math.max(index, this._lastIndex) if (this.rangeCheck(index)) { const size = index + 1 console.log('set 下标越界') this._array = this.copyBuffer(size) this._array[index] = item } else { this._array[index] = item } this._size++ } /** * 根据下标取值 * @param {*} index */ get (index) { if (this.rangeCheck(index)) { // console.log('get 下标越界') return undefined } return this._array[index] } /** * @param {*} index * 下标越界检查 */ rangeCheck (index) { return index > this._array.length - 1 || index < 0 } /** * 复制 * @param {*} size */ copyBuffer (size) { const buffer = new Uint8Array(size) buffer.set(this._array) return buffer } /** * 当前存储的数据个数 */ get size () { return this._size }}const arr = new DynamicArray(10)for (let i = 0; i < 20; i++) { arr.set(i, i)}arr.add(22)arr.add(33)arr.add(44)arr.remove(3)for (let i = 0; i < 25; i++) { console.log(arr.get(i))}console.log(arr);","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"并行","date":"2020-04-20T11:35:36.000Z","path":"2020/04/20/2020-04-20-并行/","text":"并行计算背景当算法无法优化后,并行的数据进行处理 方案 数据分片 2. 并行处理 3. 合并 案例 并行排序:数据切分后,并行排序,最后合并 并行查找:数据切分后,并行查找,节省时间 并行搜索和并行字符串匹配等","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"索引","date":"2020-04-20T11:17:55.000Z","path":"2020/04/20/2020-04-20-索引/","text":"索引意义节省内存空间,提高增删改查的效率 索引定义功能性 是否是格式数据(MySQL数据),非格式化数据(搜索引擎爬取的数据)需要预处理 静态数据还是动态数据,动态数据要考虑,增 删 改,设计更复杂 存储内存还是硬盘,大量数据不适宜存储于内存 单值查找还是区间查找,支持区间查找的数据结构,更复杂些 单关键词还是多关键词 非功能性 索引的存储空间要小,尤其是在内存中 考虑索引的维护成本,索引维护不能太难 具体的数据结构散列表增删改查操作的性能非常好,时间复杂度是 O(1)。一些键值数据库,比如 Redis、Memcache,就是使用散列表来构建索引的。这类索引,一般都构建在内存中 红黑树作为一种常用的平衡二叉查找树,数据插入、删除、查找的时间复杂度是 O(logn),也非常适合用来构建内存索引。Ext 文件系统中,对磁盘块的索引,用的就是红黑树。 B+ 树比起红黑树来说,更加适合构建存储在磁盘中的索引。B+ 树是一个多叉树,所以,对相同个数的数据构建索引,B+ 树的高度要低于红黑树。当借助索引查询数据的时候,读取 B+ 树索引,需要的磁盘 IO 次数会更少。所以,大部分关系型数据库的索引,比如 MySQL、Oracle,都是用 B+ 树来实现的。 跳表也支持快速添加、删除、查找数据。而且,我们通过灵活调整索引结点个数和数据个数之间的比例,可以很好地平衡索引对内存的消耗及其查询效率。Redis 中的有序集合,就是用跳表来构建的。 位图和布隆过滤器位图很省内存空间,还可以使用布隆过滤器优化,我们可以先构建布隆过滤器,拦截数据是否存在,不存在就不用查了,省了一次索引查询 有序数组静态数据,使用有序数组,二分查找也很快","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"A*搜索算法","date":"2020-04-20T09:38:10.000Z","path":"2020/04/20/2020-04-20-A-搜索算法/","text":"A* 和 dijkstra 对比 dijkstra 算法:计算 dist 距离(当前顶点到起点距离),A* 算法:计算 f 值(dist + h(顶点到终点的麦哈顿距离, abs(x - xEnd) + abs(y - yEnd) )) A* 更新顶点 dist ,同时更新 f 值 循环结束条件,dijkstra 所有值出队列结束,A* 到了终点就结束 A* 并没有考察所有路线,到终点后得到的 dist,并不一定是最小,但是节省了更多计算资源","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"B+树","date":"2020-04-20T09:04:34.000Z","path":"2020/04/20/2020-04-20-B-树/","text":"B+树由二叉查找树,演化而来 支持区间 节点不存储数据,作为索引用于查找 叶子节点存储数据,并且使用链表串联一起,按照顺序排列 减少内存消耗 索引存储到硬盘 磁盘IO较慢 减少磁盘IO 二叉树,改为多叉树(m 叉树),降低树高度,减少IO 总结 每个节点中子节点的个数不能超过 m,也不能小于 m/2; 根节点的子节点个数可以不超过 m/2,这是一个例外; m 叉树只存储索引,并不真正存储数据,这个有点儿类似跳表; 通过链表将叶子节点串联在一起,这样可以方便按区间查找; 一般情况,根节点会被存储在内存中,其他节点存储在磁盘中。","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"向量空间","date":"2020-04-20T08:45:43.000Z","path":"2020/04/20/2020-04-20-向量空间/","text":"欧几里得距离使用向量空间,获得一个小的推荐系统 根据不同维度打分,得到一个分数向量 计算向量之间的欧几里得距离,距离最小的,最相似","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"朴素贝叶斯算法","date":"2020-04-20T08:18:31.000Z","path":"2020/04/20/2020-04-20-朴素贝叶斯算法/","text":"公式 骚扰短信过滤 黑名单:位图存储(号码黑名单) + 布隆过滤 规则:短信中,当某个词,出现就过滤 朴素贝叶斯:短信中,某些词出现,计算这个短信是垃圾短信的概率,然后过滤","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"位图","date":"2020-04-20T07:44:07.000Z","path":"2020/04/20/2020-04-20-位图/","text":"位图 使用每一位,来存放状态,适合大规模数据,状态又不多的情况,数据范围不能太大 因为使用每一位来表示状态,可以节省大量内存空间 使用数组存储数据,可以达到排序的效果 如果两组数据都是位图,存储交集和并集就很容易获取,比如 位图 A 存储男女数据,位图 B 存储是否喜欢游戏,就很容易取得数据(喜欢游戏的男人) 布隆过滤器当数据范围很大时,申请的内存空间就可能浪费,此时使用布隆过滤器 申请空间,相对整个数据小很多,此时不能直接存数据,因为位图值范围比数据小 使用多个hash函数,对数据进行hash运算,得到多个值,分别存入位图 判断某个数据是否在位图中时,计算hash值,从位图中取出,当结果都为1时,此数据存在 这样就节省了空间,但是造成一个误判问题,数据不在位图中,但是hash值计算重复,导致位图结果是存在的,对这种结果不敏感,可以接受 bloom filter: False is always false. True is maybe true","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"最短路径","date":"2020-04-18T10:44:10.000Z","path":"2020/04/18/2020-04-17-最短路径/","text":"Dijkstra 算法","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"拓扑排序","date":"2020-04-18T03:40:36.000Z","path":"2020/04/18/2020-04-17-拓扑排序/","text":"算法解析 像我们穿衣服一样,有序且不能循环,拓扑排序同样如此 算法是基于数据结构的算法,有序且不能循环,比较适用的数据结构就是:有向无环图 实际拓扑排序,就是基于有向无环图的算法 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113class Graph { constructor () { this.adj = new Map() } // 添加边 有向 addEdge (from, to) { if (!this.adj.get(from)) { this.adj.set(from, [to]) } else { this.adj.get(from).push(to) } } // Kahn 算法 topoSortByKahn () { const inDegree = new Map() // 统计每个顶点的入度 for (const [k, v] of this.adj) { if (!inDegree.has(k)) { inDegree.set(k, 0) } for (let i = 0; i < v.length; i++) { const vertex = v[i] if (inDegree.has(vertex)) { inDegree.set(vertex, inDegree.get(vertex) + 1) } else { inDegree.set(vertex, 1) } } } // 入度为0队列 const queue = [] for (const [k, v] of inDegree) { if (v === 0) { queue.push(k) } } while (queue.length) { const i = queue.pop() console.log('->' + i) if (this.adj.get(i)) { for (let j = 0; j < this.adj.get(i).length; ++j) { let k = this.adj.get(i)[j] inDegree.set(k, inDegree.get(k) - 1) if (inDegree.get(k) == 0) queue.push(k) } } } } // DFS 深度优先搜索 sortingByDFS () { // 构建逆邻接表 其实就是把,之前的图指向反转 const inverseAdj = new Map() for (const [k, v] of this.adj) { for (let i = 0; i < v.length; i++) { const vertex = v[i] if (inverseAdj.has(vertex)) { inverseAdj.set(vertex, [...inverseAdj.get(vertex), k]) } else { inverseAdj.set(vertex, [k]) } } } // 深度优先遍历 const vertexes = new Set([...inverseAdj.keys()]) // 所有的顶点 const visited = [] for (const vertex of vertexes) { if (!visited.includes(vertex)) { visited.push(vertex) this.dfs(vertex, inverseAdj, visited) } } } dfs (vertex, inverseAdj, visited) { // 边界兼容 取不到对应数组 // if (!inverseAdj.has(vertex)) { // inverseAdj.set(vertex, []) // } if (!inverseAdj.has(vertex)) { console.log('==>', vertex) // 递归结束条件更明确 return } for (let i = 0; i < inverseAdj.get(vertex).length; i++) { const v = inverseAdj.get(vertex)[i] if (visited.includes(v)) { continue } visited.push(v) this.dfs(v, inverseAdj, visited) } console.log('==>', vertex); }}const dag = new Graph()dag.addEdge(2, 1)dag.addEdge(3, 2)dag.addEdge(2, 4)dag.addEdge(4, 1)// dag.topoSortByKahn()dag.sortingByDFS()const dag2 = new Graph()dag2.addEdge('A2', 'B')dag2.addEdge('A1', 'B')dag2.addEdge('z', 'A2')dag2.addEdge('B', 'C1')dag2.addEdge('B', 'C2')// dag2.topoSortByKahn()dag2.sortingByDFS()","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"webpack","date":"2020-03-17T10:32:54.000Z","path":"2020/03/17/2020-03-17-webpack/","text":"webpack 构建项目 init 项目 webpack babel react 命令优化 react-router webpack-dev-server 模块热替换(Hot Module Replacement) 文件路径优化 redux devtool 优化编译 css 编译图片 按需加载 缓存 HtmlWebpackPlugin 提取公共代码 生产坏境构建 文件压缩 指定环境 优化缓存 public path 打包优化 抽取 css 使用 axios 和 middleware 优化 API 请求 合并提取 webpack 公共配置 webpack-common-config 优化目录结构并增加 404 页面(2017-09-04) 加入 babel-plugin-transform-runtime 和 babel-polyfill 集成 PostCSS redux 模块热替换配置 模拟 AJAX 数据之 Mock.js 使用 CSS Modules 使用 json-server 代替 Mock.js","tags":[]},{"title":"动态规划3 实战","date":"2020-03-11T10:16:51.000Z","path":"2020/03/11/2020-03-11-动态规划3/","text":"字符串纠错莱文斯坦距离 表示字符串之间差异 可以增加、删除和替换来编辑字符 最长公共子串 表示字符串之间相似 只允许增加和删除操作 回溯算法实现 莱文斯坦距离1234567891011121314151617181920212223242526272829303132333435363738394041const strA = 'mitcmu'const strB = 'mtacnu'const n = 6const m = 6let minDist = Number.MAX_SAFE_INTEGER // 存储结果// 调用方式 lwstBT(0, 0, 0);/** * 莱文斯坦回溯法 * * @param {*} i 第一个字符串下标 * @param {*} j 第二个字符串下标 * @param {*} editDist */function lwstBT(i, j, editDist) { // 结束条件 if (i === n || j === m) { // 长度偏差 if (i < n) { editDist += n - i } if (j < m) { editDist += m - j } if (editDist < minDist) { minDist = editDist } return } // 相等接着遍历 if (strA[i] === strB[j]) { lwstBT(i + 1, j + 1, editDist) } else { lwstBT(i + 1, j, editDist + 1) // strA[i] 删除 或者 strB[j] 前面加一个 lwstBT(i, j + 1, editDist + 1) // strB[j] 删除 或者 strA[i] 前面加一个 lwstBT(i + 1, j + 1, editDist + 1) // 替换为相同字符 }}lwstBT(0, 0, 0)console.log(minDist) 动态规划 画出状态转移表 状态转移方程 如果:a[i]!=b[j],那么:min_edist(i, j)就等于:min(min_edist(i-1,j)+1, min_edist(i,j-1)+1, min_edist(i-1,j-1)+1)如果:a[i]==b[j],那么:min_edist(i, j)就等于:min(min_edist(i-1,j)+1, min_edist(i,j-1)+1,min_edist(i-1,j-1))其中,min 表示求三数中的最小值。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465const strA = 'mitcmu'const strB = 'mtacnu'const n = 6const m = 6/** *莱文斯坦距离 动态规划 * * @param {*} strA * @param {*} n * @param {*} strB * @param {*} m * @returns */function lwstDP(strA, n, strB, m) { const minDist = new Array(n) for (let i = 0; i < n; i++) { minDist[i] = new Array(m) } for (j = 0; j < m; ++j) { // 初始化第0行: strA[0..0] 与 strB[0..j] 的编辑距离 if (strA[0] === strB[j]) minDist[0][j] = j else if (j != 0) minDist[0][j] = minDist[0][j - 1] + 1 else minDist[0][j] = 1 } for (i = 0; i < n; ++i) { // 初始化第0列: strA[0..i] 与 strB[0..0] 的编辑距离 if (strA[i] === strB[0]) minDist[i][0] = i else if (i != 0) minDist[i][0] = minDist[i - 1][0] + 1 else minDist[i][0] = 1 } for (i = 1; i < n; ++i) { // 按行填表 for (j = 1; j < m; ++j) { if (strA[i] === strB[j]) minDist[i][j] = min( minDist[i - 1][j] + 1, minDist[i][j - 1] + 1, minDist[i - 1][j - 1] ) else minDist[i][j] = min( minDist[i - 1][j] + 1, minDist[i][j - 1] + 1, minDist[i - 1][j - 1] + 1 ) } } return minDist[n - 1][m - 1]}function min(x, y, z) { let minNum = Number.MAX_SAFE_INTEGER if (x < minNum) { minNum = x } if (y < minNum) { minNum = y } if (z < minNum) { minNum = z } return minNum}console.log(lwstDP(strA, n, strB, m)) 动态规划(公共子串) 如果:a[i]==b[j],那么:max_lcs(i, j)就等于:max(max_lcs(i-1,j-1)+1, max_lcs(i-1, j), max_lcs(i, j-1));如果:a[i]!=b[j],那么:max_lcs(i, j)就等于:max(max_lcs(i-1,j-1), max_lcs(i-1, j), max_lcs(i, j-1));其中 max 表示求三数中的最大值。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849const strA = 'mitcmu'const strB = 'mtacnu'const n = 6const m = 6function lcs(strA, n, strB, m) { const maxlcs = new Array(n) for (let i = 0; i < n; i++) { maxlcs[i] = new Array(m) } for (let j = 0; j < m; ++j) { //初始化第0行:a[0..0]与b[0..j]的maxlcs if (strA[0] == strB[j]) maxlcs[0][j] = 1 else if (j != 0) maxlcs[0][j] = maxlcs[0][j - 1] else maxlcs[0][j] = 0 } for (let i = 0; i < n; ++i) { //初始化第0列:a[0..i]与b[0..0]的maxlcs if (strA[i] == strB[0]) maxlcs[i][0] = 1 else if (i != 0) maxlcs[i][0] = maxlcs[i - 1][0] else maxlcs[i][0] = 0 } for (let i = 1; i < n; ++i) { // 填表 for (j = 1; j < m; ++j) { if (strA[i] == strB[j]) maxlcs[i][j] = max( maxlcs[i - 1][j], maxlcs[i][j - 1], maxlcs[i - 1][j - 1] + 1 ) else maxlcs[i][j] = max( maxlcs[i - 1][j], maxlcs[i][j - 1], maxlcs[i - 1][j - 1] ) } } return maxlcs[n - 1][m - 1]}function max(x, y, z) { let maxv = Number.MIN_SAFE_INTEGER if (x > maxv) maxv = x if (y > maxv) maxv = y if (z > maxv) maxv = z return maxv}console.log(lcs(strA, n, strB, m))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"动态规划2 理论","date":"2020-03-11T10:16:45.000Z","path":"2020/03/11/2020-03-11-动态规划2/","text":"动态规划适合解决什么问题 一个模型三个特征 一个模型 多阶段决策最优解模型 解决问题分为多个阶段,每个阶段对应一种状态 选择一组状态,得到最优解 三个特征最优子结构问题的最优解,包含子问题的最优解,可以理解为,后面阶段的状态,可以由前面状态推到处理 无后效性 只关心当前阶段值,不关心具体得到过程 前面阶段值一旦确定,不受后面阶段影响 重复子问题不同的组合状态,可能得到相同的结果 解题思路状态转移表法 利用回溯算法,一般回溯算法,都可以使用动态规划实现 回溯算法画出递归树,找出重复子问题 然后画出状态表 回溯算法实现 => 定义状态 => 画递归树 => 找重复子问题 => 画状态转移表 => 根据递推关系填表 => 将填表过程翻译成代码 状态转移方程法类似这种 min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j)) 递归 + 备忘录 迭代递推 找最优子结构 => 写状态转移方程 => 将状态转移方程翻译成代码","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"动态规划","date":"2020-03-11T02:55:17.000Z","path":"2020/03/11/2020-03-11-动态规划/","text":"动态规划(Dynamic Programming)0-1 背包问题 动态规划1234567891011121314151617181920212223242526272829303132/** * 计算0-1背包问题 动态规划 * @param {*} weightList 物品重量 * @param {*} num 物品个数 * @param {*} totalWeight 背包可承载重量 * @returns */function knapsack(weightList, num, totalWeight) { const states = new Array(totalWeight + 1) // 默认值false // 第一行的数据要特殊处理,可以利用哨兵优化 states[0] = true if (weightList[0] <= totalWeight) { states[weightList[0]] = true } for (let i = 1; i < num; ++i) { // 动态规划 for (let j = totalWeight - weightList[i]; j >= 0; --j) { //把第i个物品放入背包 if (states[j]) { states[j + weightList[i]] = true } } } for (let i = totalWeight; i >= 0; --i) { // 输出结果 if (states[i]) return i } return 0}const totalWeight = [2, 2, 4, 6, 3]console.log(knapsack(totalWeight, 5, 9))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"回溯算法","date":"2020-03-10T07:24:21.000Z","path":"2020/03/10/2020-03-10-回溯算法/","text":"回溯算法利用具体案例来理解,回溯算法 八皇后8X8 的棋盘,每个棋子的行、列、对角线,都不能有其他棋子 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152const result = new Array(8) //全局或成员变量,下标表示行,值表示queen存储在哪一列function cal8queens(row) { // 调用方式:cal8queens(0); if (row === 8) { // 8个棋子都放置好了,打印结果 printQueens(result) return // 8行棋子都放好了,已经没法再往下递归了,所以就return } for (let column = 0; column < 8; ++column) { // 每一行都有8中放法 if (isOk(row, column)) { // 有些放法不满足要求 result[row] = column // 第row行的棋子放到了column列 cal8queens(row + 1) // 考察下一行 } }}function isOk(row, column) { //判断 row行 column列 放置是否合适 var leftUp = column - 1, rightUp = column + 1 for (let i = row - 1; i >= 0; --i) { // 逐行往上考察每一行 if (result[i] === column) return false // 第i行的column列有棋子吗? if (leftUp >= 0) { // 考察左上对角线:第i行leftUp列有棋子吗? if (result[i] === leftUp) return false } if (rightUp < 8) { // 考察右上对角线:第i行rightUp列有棋子吗? if (result[i] === rightUp) return false } --leftUp ++rightUp } return true}function printQueens(result) { // 打印出一个二维矩阵 for (let row = 0; row < 8; ++row) { let rowText = '' for (let column = 0; column < 8; ++column) { rowText += result[row] === column ? 'Q ' : '* ' } console.log(rowText) } console.log('\\n')}cal8queens(0) 0-1 背包1234567891011121314151617181920212223242526272829/** * 0 - 1背包问题 * @param {*} index 表示考察到哪个物品了 * @param {*} cWeight 已经装的重量 * @param {*} items 每个物品重量 * @param {*} num 表示物品个数 * @param {*} weight 背包重量 */let maxW = Number.MIN_VALUE //存储背包中物品总重量的最大值function find(index, cWeight, items, num, weight) { // cWeight==w表示装满了; // index==num表示已经考察完所有的物品 if (cWeight == weight || index == num) { // 更新最大重量 if (cWeight > maxW) maxW = cWeight return } // 不装进去 find(index + 1, cWeight, items, num, weight) // 装进去 但是判断下是不是超过了 if (cWeight + items[index] <= weight) { //当前物品装进背包 find(index + 1, cWeight + items[index], items, num, weight) }}const a = [66, 12, 11, 40, 50, 33]find(0, 0, a, 10, 100)console.log(maxW) 正则表达式12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849class Pattern { constructor(pattern, plen) { // 正则表达式 this.pattern = pattern // 正则表达式长度 this.plen = plen this.matched = false this.int = 0 } match(text, tlen) { // 文本串及长度 this.matched = false this.rMatch(0, 0, text, tlen) return this.matched } /** * * * @param {*} tIndex 字符串下标 * @param {*} pIndex 匹配下标 * @param {*} text 字符串 * @param {*} tlen 长度 * @memberof Pattern */ rMatch(tIndex, pIndex, text, tlen) { if (this.matched) return // 如果已经匹配了,就不要继续递归了 if (pIndex == this.plen) { // 正则表达式到结尾了 if (tIndex == tlen) this.matched = true // 文本串也到结尾了 return } if (this.pattern[pIndex] == '*') { // *匹配任意个字符 for (let k = 0; k <= tlen - tIndex; ++k) { this.rMatch(tIndex + k, pIndex + 1, text, tlen) } } else if (this.pattern[pIndex] == '?') { // ?匹配0个或者1个字符 this.rMatch(tIndex, pIndex + 1, text, tlen) this.rMatch(tIndex + 1, pIndex + 1, text, tlen) } else if (tIndex < tlen && this.pattern[pIndex] == text[tIndex]) { // 纯字符匹配才行 this.rMatch(tIndex + 1, pIndex + 1, text, tlen) } }}const p = new Pattern('*', 2)console.log(p.match('c*a*b', 1))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"分治算法","date":"2020-03-10T06:54:52.000Z","path":"2020/03/10/2020-03-10-分治算法/","text":"分治算法(divide and conquer)分治算法:顾名思义,分而治之,将原来大的问题,分解成 n 个小的,并且结构与原问题相似问题,递归的解决这些问题,合并成总的结果 分治算法适用范围: 问题可以分解为相似结构的子问题 子问题可以独立求解,子问题没有相关性 具有分解终止条件,也就是说问题足够小的时候,可以直接求解 子问题结果,可以合并成原问题,且复杂度不能过高,过高那分解问题就没必要了 分治算法是一种处理问题的思想,递归是一种编程技巧 递归步骤: 分解:将原问题分解成小问题 解决:递归求解各个子问题,若问题足够小,直接求解 合并:将各个子问题结果合并成原问题解","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"贪心算法","date":"2020-03-09T13:49:28.000Z","path":"2020/03/09/2020-03-10-贪心算法/","text":"贪心算法 (greedy algorithm)第一步:联想到贪心算法,一组数据给定限制值和期望值,在满足限制值的前提下,期望值最大第二部:是否可使用贪心算法,每次选择,单位限制值下单位期望值最大第三部:在数据中,小范围验证 贪心算法实战1.分糖果: 我们有 m 个糖果和 n 个孩子。我们现在要把糖果分给这些孩子吃,但是糖果少,孩子多(m<n),所以糖果只能分配给一部分孩子。每个糖果的大小不等,这 m 个糖果的大小分别是 s1,s2,s3,……,sm。除此之外,每个孩子对糖果大小的需求也是不一样的,只有糖果的大小大于等于孩子的对糖果大小的需求的时候,孩子才得到满足。假设这 n 个孩子对糖果大小的需求分别是 g1,g2,g3,……,gn。我的问题是,如何分配糖果,能尽可能满足最多数量的孩子? 答:每次挑选需求最小的孩子,然后在糖果中挑选最小满足他的糖果,这样才能更多的满足孩子(每个孩子期望值一样) 2.钱币找零:这个问题在我们的日常生活中更加普遍。假设我们有 1 元、2 元、5 元、10 元、20 元、50 元、100 元这些面额的纸币,它们的张数分别是 c1、c2、c5、c10、c20、c50、c100。我们现在要用这些钱来支付 K 元,最少要用多少张纸币呢? 答:先用大额纸币支付,然后选择小额纸币(每张的纸币期望相同) 3.区间覆盖:假设我们有 n 个区间,区间的起始端点和结束端点分别是[l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。我们从这 n 个区间中选出一部分区间,这部分区间满足两两不相交(端点相交的情况不算相交),最多能选出多少个区间呢?答:排序区间,选择左端点和已覆盖区域不想交,同时右端点最小(给右侧区间留更大位置) 霍夫曼编码(数据压缩编码,有效节省数据存储空间)1.出现越多的字符,编码长度越低,且为了防止解码问题,任何一个编码都不能是另外一个编码的前缀","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"AC 自动机","date":"2020-03-09T03:09:26.000Z","path":"2020/03/09/2020-03-09-AC-自动机/","text":"单模式串匹配算法:单个模式串与主串比较多模式串匹配算法:多个模式串与主串比较 经典多模式串匹配算法:AC 自动机 AC 自动机: 构建 Trie 树,基础部分不变,需要增加类似 KMP 的 fail (或者说 next)数组,增加匹配长度记录 fail 数组构建 首先是对树的遍历 找子节点的失败节点 根据当前节点和当前节点的失败节点,找当前节点子节点的失败节点 匹配函数 子节点不匹配,节点更换为子节点的失败节点 判断节点是不是结束节点,不是结束节点则寻找其失败节点是不是结束节点 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121class ACNode { constructor(data) { this.data = data this.children = new Map() // 标志是否结束 this.isEndingChar = false // 字符串长度 this.length = 0 // 失败next数组 this.fail = null }}class ACTree { constructor(data) { this.root = new ACNode('/') } insert(text) { // 根节点 let node = this.root // 遍历 for (let char of text) { // 如果子节点没存过,设置子节点 if (!node.children.get(char)) { node.children.set(char, new ACNode(char)) } // 取出下一个子节点,接着构建 node = node.children.get(char) } // 结束标志,长度标志 node.isEndingChar = true node.length = text.length } buildFailurePointer() { // 1. 对树进行遍历 let root = this.root const queue = [] // 放入栈中等待遍历 queue.push(root) while (queue.length) { let node = queue.shift() for (const nodeChild of node.children.values()) { // 空节点跳过 if (!nodeChild) { continue } // 根据当前节点的失败指针 求其子节点的失败指针 if (node === root) { nodeChild.fail = root } else { let nodeFail = node.fail while (nodeFail) { // 当前节点的子节点 与 当前节点对应失败节点的子节点, 如果相等, 那么当前节点子节点的失败节点 是 当前节点对应失败节点的子节点 const nodeFailChild = nodeFail.children.get(nodeChild.data) if (nodeFailChild) { nodeChild.fail = nodeFailChild // 找到就终端 break } // 找不到就接着找 nodeFail = nodeFail.fail if (!nodeFail) { // 如果失败节点不存在了 那就指向root nodeChild.fail = root } } } // 放入栈中等待遍历 queue.push(nodeChild) } } } match(text) { let node = this.root for (let i = 0; i < text.length; i++) { const char = text[i] // 寻找匹配的节点 while (node != this.root && !node.children.get(char)) { node = node.fail } // 取出子节点 node = node.children.get(char) // 如果子节点 不存在 那要重根节点开始找 if (!node) { node = this.root } let tmp = node while (tmp != this.root) { if (tmp.isEndingChar) { console.log( `Start from ${i - node.length + 1}, length: ${node.length}` ) } tmp = tmp.fail } } }}function match(text, patterns) { let automata = new ACTree() for (let pattern of patterns) { automata.insert(pattern) } automata.buildFailurePointer() automata.match(text)}let patterns = ['at', 'art', 'oars', 'soar']let text = 'soarsoars'match(text, patterns)let patterns2 = ['Fxtec Pro1', '谷歌Pixel']let text2 = '一家总部位于伦敦的公司Fxtex在MWC上就推出了一款名为Fxtec Pro1的手机,该机最大的亮点就是采用了侧滑式全键盘设计。DxOMark年度总榜发布 华为P20 Pro/谷歌Pixel 3争冠'match(text2, patterns2)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"Trie 树","date":"2020-03-07T14:29:44.000Z","path":"2020/03/07/2020-03-07-Trie-树/","text":"Trie 树(字典树) 专门处理字符串匹配的数据结构 树的 root 节点不表示意思 其余每个节点,表示字符串中的字符 从根节点到红色节点,表示一个字符串(但是红色节点并不一定是叶子节点) 4.生成 Trie 的过程 代码实现 Trie 树拆解: Trie 树有多个杈 每个树杈都需要快速查找子节点 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152class TrieNode { constructor(data) { this.data = data this.isEndingChar = false this.children = new Array(26) }}class Trie { constructor() { this.root = new TrieNode('/') // 存储无意义字符 } // 往Trie树中插入一个字符串 insert(text) { let node = this.root for (let i = 0; i < text.length; ++i) { const index = text[i].charCodeAt() - 'a'.charCodeAt() if (!node.children[index]) { const newNode = new TrieNode(text[i]) node.children[index] = newNode } node = node.children[index] } node.isEndingChar = true } // 在Trie树中查找一个字符串 find(pattern) { let node = this.root for (let i = 0; i < pattern.length; ++i) { const index = pattern[i].charCodeAt() - 'a'.charCodeAt() if (!node.children[index]) { return false // 不存在pattern } node = node.children[index] } // 不能完全匹配,只是前缀 return node.isEndingChar }}var tree = new Trie()var strs = ['how', 'hi', 'her', 'hello', 'so', 'see']for (let str of strs) { tree.insert(str)}for (let str of strs) { console.log(tree.find(str))}console.log(tree.find('world')) Trie 树存在的问题: 使用数组存储子节点指针,会占用更多的内存空间,尤其是字符集比较大的时候,插入和查找效率都会降低 字符串前缀重合的多,才能节省空间 手动实现工业级容易出bug 指针非连续存储,对缓存优化不好 比较适合的场景是:搜索时候的联想功能","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"字符串(KMP算法)-3","date":"2020-03-04T07:47:24.000Z","path":"2020/03/04/2020-03-04字符串-3/","text":"KMP 算法:在模式串与主串比较过程中,当遇到坏字符时,匹配成功的好前缀,如何最大程度向后滑动 拆解: 找到坏字符下标 j 坏字符前面的子串,是好前缀 需要好前缀(模式串与主串匹配部分)滑动最多 滑动计算,只关心模式串,就可以了(此时是好前缀) 所有模式串前缀子串,与后缀子串匹配 得到最长的前缀子串,最长后缀子串,以及最长前缀子串的下标 j’ 此时下一个需要匹配的字符位置(模式串中)就是 j = j’ + 1 拆解模式串 next 算法: 第一个字符无匹配 下标为 -1 核心概念是 上次匹配到的字符下标 再接着向后比较 如果相等,那 k 值增加 1 上次匹配到的字符下标 再接着向后比较 如果不相等 pattern[k + 1] != pattern[i]此时我们知道 pattern[k] != pattern[i-1] 因为之前都是匹配的我们在之前匹配过的子串中寻找,即 k = next[k](次长串),看看接下来的字符能和 pattern[i] 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465/** * 计算next数组 * @param {*} pattern 模式串 * @param {*} pLength 模式串的长度 */function getNext(pattern, pLength) { let next = new Array(pLength) next[0] = -1 // 第一个字符无匹配 下标为 -1 let k = -1 // 以及匹配到的下标 for (let i = 1; i < pLength; ++i) { // 上次匹配到的字符下标 再接着向后比较 如果不相等 pattern[k + 1] != pattern[i] // 此时我们知道 pattern[k] != pattern[i-1] 因为之前都是匹配的 // 我们在之前匹配过的子串中寻找,即k = next[k](次长串),看看接下来的字符能和pattern[i] while (k != -1 && pattern[k + 1] != pattern[i]) { k = next[k] } // 核心概念是 上次匹配到的字符下标 再接着向后比较 如果相等,那k值增加1 if (pattern[k + 1] == pattern[i]) { ++k } next[i] = k } return next}/** * kmp 算法 * @param {*} main 主串 * @param {*} pattern 模式串 */function kmp(main, pattern) { // 长度 const mLength = main.length const pLength = pattern.length // 获取next数组 const next = getNext(pattern, pLength) // 模式串下标 let j = 0 for (let i = 0; i < mLength; i++) { // 一直找到 main[i]和 pattern[j] 坏字符 while (j > 0 && main[i] != pattern[j]) { // j为坏字符下标 // j - 1为好前缀最后一个字符下标 // next[j - 1] 好前缀最长可匹配字符下标 +1是下个要匹配的字符 j = next[j - 1] + 1 // 滑动到最大可滑动的下标 } // 匹配的字符 向后移动 if (main[i] == pattern[j]) { ++j } // 找到匹配模式串的了 if (j === pLength) { return i - pLength + 1 // 得出当前主串中匹配的第一个下标 } } return -1}// ['a', 'b', 'a', 'b', 'a', 'e', 'a', 'b', 'a', 'b', 'a', 'c']// ['a', 'b', 'a', 'b', 'a', 'c']console.log( kmp( ['a', 'b', 'a', 'b', 'a', 'e', 'a', 'b', 'a', 'b', 'a', 'c'], ['a', 'b', 'a', 'b', 'a', 'c'] ))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"字符串(BM算法)-2","date":"2020-01-06T09:11:32.000Z","path":"2020/01/06/2020-01-06字符串-2/","text":"BM 算法(Boyer-Moore)简析 在暴力匹配中,我们把主串中子串和模式串一一对比,这样效率低 BM 算法中,当主串字符在模式串中不存在时,我们就跳过这次对比,直接从当前字符的下一个子串开始对比 原理分析 坏字符规则(bad character rule) 当倒序遍历模式串的字符,和主串对应位置字符无法匹配时,就称这个字符为坏字符,如果这个字符在模式串中没有,那主串匹配位置,移到当前字符后 在模式串中无法匹配的字符小标记为 xi,在模式串中找当前主串字符,下标为 si(找到多个取最后一个,防止滑动过多),未找到 si 为-1,向后滑动的位置为 xi-si 存在问题,就是 xi-si 可能为负数 好后缀规则(good suffix shift) 主串和模式串倒序匹配,匹配好的子串为 U,当下一个开始不匹配的时候,我们开始向后滑动 滑动到的位置,是模式串的前缀子串和匹配好的子串 U 的最大交集 坏字符和好后缀同时使用,那个计算出的向后滑动多,使用哪个 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081/** * 模式串生成map * @param {*} pattern */const patternMap = {}function generatePMap(pattern) { for (let i = 0; i < pattern.length; i++) { const word = pattern[i] patternMap[word] = i }}const suffix = {}/** * 计算模式串的前缀和后缀子串 * @param {*} pattern */function generateFixMap(pattern) { for (let i = 0; i < pattern.length; i++) { suffix[i] = -1 } const pLength = pattern.length for (let i = 0; i < pLength - 1; i++) { let j = i // 起始下标 let k = 0 // 长度 while (j >= 0 && pattern[j] === pattern[pLength - 1 - k]) { --j ++k suffix[k] = j + 1 } }}/** * BM 算法 * @param {*} main 主串 * @param {*} pattern 模式串 */function bm(main, pattern) { // 模式串 generatePMap(pattern) // 前后缀串 generateFixMap(pattern) let i = 0 const mLength = main.length const pLength = pattern.length // 匹配结束边界 while (i <= mLength - pLength) { let j = pLength - 1 for (j; j >= 0; j--) { if (pattern[j] != main[i + j]) { break } } // 匹配成功了 if (j < 0) { return i } //计算坏字符 const k = pLength - 1 - j let move = pLength if (k) { // 模式子串中的存在相同后缀子串 if (suffix[k] != -1) { move = j - suffix[k] + 1 } else { // 这里把好后缀的范围缩小 来查是否存在相同子串 for (let r = j + 2; r < pLength; r++) { if (suffix[pLength - r] === 0) { move = r break } } } } // 计算下标移动 const num = isNaN(patternMap[main[i + j]]) ? -1 : patternMap[main[i + j]] i = i + Math.max(j - num, move) } return -1}console.log(bm(['a', 'b', 'd', 'c', 'b', 'd'], ['c', 'b', 'd']))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"字符串","date":"2020-01-06T03:24:34.000Z","path":"2020/01/06/2020-01-06字符串/","text":"BF(Brute Force)算法 中文:暴力匹配算法 主串:在该字符串中查找字符串模式串:在主串中,查找这个字符串 算法: 主串长度 n 模式串长度 m 从 0、1、2… n - m 且长度为 m 的 n - m + 1 个子串进行对比 时间复杂度: n - m +1 个子串比较 m 次,因此复杂度 O((n - m +1) * m) => O(n * m) 实际比较 BF 常用: 一般字符串长度不会太长,影响不大,另外当比对一旦不一样的时候,就可以停止 实现简单不容易出错 RK 算法 (Rabin-Karp) n -m +1 个子串求 hash 值与模式串的 hash 值比较 当散列冲突时,再比较子串和模式串,需要控制 hash 冲突,要不然就退化成 BF 算法了 时间复杂度: 遍历主串技术 hash 值,复杂度为 n,进行比较 n -m +1 次,因此时间复杂度为 O(n)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"优先搜索","date":"2019-12-31T03:44:06.000Z","path":"2019/12/31/2019-12-31优先搜索/","text":"广度优先搜索 BFS(Breadth-First-Search)含义:地毯式的层层推进搜索,由近及远 定义顶点个数为 V,边数为 E visited 数组,记录已经访问过得顶点,不再次访问 queue 队列,记录刚刚访问的顶点,为了接下来访问此顶点的下一个顶点 prev 数组,用于存储访问记录(反向),找到顶点后,反向遍历就是最短路径 复杂度: 时间: 对于连通图,边数 E 大于等于 顶点数 V - 1 搜索到最后一个顶点,每个顶点,每条边都会被访问,时间复杂度为 O(E + V),即时间复杂度为 O(V) 空间: visited、queue、prev 大小都和 V 相关,所以最终空间复杂度为 O(V) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152/** * 广度优先搜索 使用 JavaScript 写的伪代码 * @param {*} s 当前顶点 * @param {*} t 目标顶点 * @param {*} v 图的订单数 * @param {*} graph 图 * @param {*} visited 遍历过的顶点 * @param {*} queue 队列 已经被访问,但连接的顶点还没被访问,用于找到下一层节点访问 * @param {*} prev 访问记录,反向的,反向递归,可以打印s => t 的最短访问记录 */function bfs(s, t) { if (s === t) { return } // 已经访问 const visited = {} visited[s] = true // 队列用于接下来访问 const queue = new Queue() queue.enqueue(s) // 路径数组 const prev = new Array(v).fill(-1) while (queue.length) { // 上个顶点 const lastV = queue.dequeue() // graph[lastV].length 图的下一层的大小 for (let i = 0; i < graph[lastV].length; i++) { // 当前顶点 const nowV = graph[lastV][i] if (!visited[nowV]) { // 路径 prev[nowV] = lastV if (nowV === v) { print(prev, s, v) return } // 记录遍历过 visited[nowV] = true // 入队 queue.enqueue(nowV) } } }}function print(prev, s, t) { // 递归打印s->t的路径 if (prev[t] != -1 && t != s) { print(prev, s, prev[t]) } console.log(t + ' ')} 深度优先搜索 DFS (Depth-First-Search)含义:走迷宫,选择一条路走,没路回退,接着换路,知道走到终点 定义顶点个数为 V,边数为 E visited 数组,记录已经访问的顶点,不在继续访问 prev 记录访问顺序,用于得出最短路径 found 已经找到终点之后,不再进行递归 复杂度: 时间: 最多遍历两遍边,所以时间复杂度为 O(E) 空间:visited、prev 和顶点 V 成正比,递归深度不会超过,顶点个数,空间复杂度为:O(V) 12345678910111213141516171819202122232425262728293031323334353637383940414243444546/** * 深度优先搜索 伪代码 * @param {*} startV 起始点 * @param {*} endV 终点 * @param {*} v 顶点个数 * @param {*} visited 访问过 * @param {*} prev 访问路径 * @param {*} graph 图 * */const found = false // 标志找到顶点不再递归function dfs(startV, endV) { const visited = {} const prev = new Array(v).fill(-1) recurDfs(startV, endV, visited, prev) print(prev, startV, endV)}function recurDfs(startV, endV, visited, prev) { if (found) { return } visited[startV] = true if (startV === endV) { found = true return } for (let i = 0; i < graph[startV].length; i++) { if (found) { break } const nowV = graph[startV][i] if (!visited[nowV]) { prev[nowV] = startV recurDfs(nowV, endV, visited, prev) } }}function print(prev, s, t) { // 递归打印s->t的路径 if (prev[t] != -1 && t != s) { print(prev, s, prev[t]) } console.log(t + ' ')}","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"图","date":"2019-12-30T12:37:31.000Z","path":"2019/12/30/2019-12-30图/","text":"图(Graph)概念 顶点(vertex):图中的每个点 边(edge):任意两个顶点的连线 顶点的度(degree):顶点连接的边的数量 方向:边具有方向,这种图叫有向图 出度(out-degree)和入度(in-degree):顶点指向其他顶点的边数,其他顶点指向当前顶点的边数 带权图(weighted graph):每条边都有权重 存储邻接矩阵 二维数组存储 无向 a[i][j]和 a[j][i]都存 1 有向 i 指向 j a[i][j]存 1 带权 a[i][j]存权重 优势: 矩阵方便进行数学运算 存储方式简单直接 基于数组,访问顶点关系简单 劣势: 无向图 多存储了一倍的数据 稀疏图 很多空间没用上 邻接表存储方法 有向图 每个顶点,存储指向的顶点 无向图 每个顶点,存储连接的点 优势: 占用空间小 链表可以采用更高效的数据结构,红黑树、平衡二叉树、跳表 劣势: 不利于计算,是用时间换空间","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"堆应用","date":"2019-12-30T11:47:39.000Z","path":"2019/12/30/2019-12-30堆应用/","text":"优先级队列 按照优先级,优先级高的先出,优先级队列===堆 优先级队列插入数据===堆插入数据,优先级队列取出数据===取出堆顶 应用合并有序小文件 每个小文件取出第一个元素,放入小顶堆 从小顶堆中,取出堆顶放入数组中,并堆化 从堆顶来源文件,接着取出元素放入堆中,并堆化 高性能定时器 对执行任务按照时间,放入堆中 计算堆顶和当前差值,定时器在过差值时间后,再执行任务 避免了轮询,消耗性能 求 Top K 创建 K 大堆 遍历数组,大于堆顶放入,小于继续 最后这个堆就是 Top K 求中位数 把数据分割为两份,小顶堆和大顶堆,并且大顶堆的最大值小于小顶堆的最小值(个数是奇书时大顶堆多一个数) 新入数据,如果小于等于大顶堆的堆顶,测数据插入小顶堆,否则插入大顶堆 维护两个堆的数据平衡,如果大顶堆超过n/2 + 1,取堆顶数据插入小顶堆,反之取小堆顶数据插入大顶堆","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"堆","date":"2019-12-30T09:20:24.000Z","path":"2019/12/30/2019-12-30堆/","text":"概念 堆是完全二叉树 每个节点大于等于子节点或者小于等于子节点(大顶堆,小顶堆) 堆的实现插入 完全二叉树使用数组存储更方便,从下表 1 开始,左子节点 2 * i,右子节点 2 * i+1 插入数据如果不满足堆的定义,那么堆化,把子节点和父节点数据交换,知道满足堆的要求 删除堆顶元素 把最后一个节点放到堆顶 然后逆向堆化 时间复杂度交换的过程,和树的高度相关,时间复杂度 O(nlogn) 堆排序比快排慢 数据不是连续访问,不利于CPU缓存 堆排序建堆的过程中,会打乱有序度,增加交换次数","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"2019-12-24递归树","date":"2019-12-24T03:54:25.000Z","path":"2019/12/24/2019-12-24递归树/","text":"概念递归思想中,把大问题分解为小问题,把这个过程画出来,就是一个树,成为递归树 归并排序 归并排序的过程,可以画出一个树 每一层归并的时间近似为 n,假设树的高度为 h,那时间复杂度为 O(n * h) 归并排序树是个满二叉树,那 h 为 log2n,所以时间复杂度为 O(nlogn) 重点 分析出每层时间复杂度 分析出树的形状,计算树的高度","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"2019-12-23红黑树-2","date":"2019-12-23T10:11:36.000Z","path":"2019/12/23/2019-12-23红黑树-2/","text":"概念 根节点是黑色节点 每个叶子节点是黑色的空节点,不存储数据 任何相邻节点不能同时为红,被黑色节点隔开 每个节点,到其可到达的叶子节点,都包含相同数目的黑色节点 左旋(右旋同理) 围绕某个节点的左旋 2 树右子节点上提转换为 3 树,在左子节点下方,转为 2 树(2-3 树的转换) 怎么维持平衡 左右旋转维持树的左右子树高度相差不大 根据关注节点改变颜色,维持树的颜色平衡","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"2019-12-23红黑树","date":"2019-12-23T07:03:18.000Z","path":"2019/12/23/2019-12-23红黑树/","text":"平衡二叉查找树 任意一个节点,其左右子树的高度相差不超过 1 很多平衡二叉树,并没有严格遵守这一点 为了解决二叉查找树,在动态插入、删除等动态更新下,退化成为链表 只要树的高度,不比 log2n 大很多,还保留在指数级,就是一个合格的平衡二叉查找树 红黑树(R-B Tree) 根节点是黑色 任何叶子节点都是黑色空节点,不存储数据 任何相邻节点都不能为红色,必须黑色隔开 每个节点,到达其叶子节点的任意路径,黑色节点个数相同 高度分析 去掉红色节点,黑色节点构成的树,最多变成四叉树 根据每条路径黑色节点个数相同,从叶子节点取出节点放到节点下,可组成完全二叉树 完全二叉树高度近似 log2n,黑色二叉树高度近似 log2n 红色节点被黑色节点隔开,每一个红色节点,对应一个黑色节点,那红色二叉树的高度近似 log2n 整体高度近似 2log2n 为何使用红黑树 AVL 完全平衡,查找很高效,但是为了维持平衡,需要在插入和删除,都需要调整 红黑树查找、插入、删除,性能都比较稳定,适用于工程 动态数据结构对比散列表:插入删除查找都是 O(1), 是最常用的,但其缺点是不能顺序遍历以及扩容缩容的性能损耗。适用于那些不需要顺序遍历,数据更新不那么频繁的。 跳表:插入删除查找都是 O(logn), 并且能顺序遍历。缺点是空间复杂度 O(n)。适用于不那么在意内存空间的,其顺序遍历和区间查找非常方便。 红黑树:插入删除查找都是 O(logn), 中序遍历即是顺序遍历,稳定。缺点是难以实现,去查找不方便。其实跳表更佳,但红黑树已经用于很多地方了。","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"二叉树搜索树","date":"2019-12-23T03:07:02.000Z","path":"2019/12/23/2019-12-23二叉树搜索树/","text":"二叉搜索树(Binary Search Tree)查找实现12345678910111213function findNode(data) { const p = tree while (p !== null && p !== undefined) { if (p.data === data) { return p } else if (p.data < data) { p = p.left } else { p = p.right } } return null} 插入实现123456789101112131415161718function insertNode(data) { const p = tree while (p !== null && p !== undefined) { if (data > p.data) { if (p.right === null || p.right === undefined) { p.right = new Node(data) return } p = p.right } else { if (p.left === null || p.left === undefined) { p.left = new Node(data) return } p = p.left } }} 删除实现1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253function deleteNode(data) { let p = tree // 指向删除节点 let pp // 指向删除节点的父节点 // 找到要删除节点 while (p !== null && p !== undefined && p.data !== data) { pp = p if (p.data < data) { p = p.right } else { p = p.left } } // 删除节点不存在 if (p === null || p === undefined) { return } // 左右节点都存在 此时找右子树最小的节点 if ( p.left !== null && p.left !== undefined && p.right !== null && p.right !== undefined ) { let minP = p.right // 最小右树子节点 let minPP = p // 最小右树子节点的父节点 while (mimP.left !== null && minP.left !== undefined) { minPP = minP minP = mimP.left } // 数据替换完成 p.data = minP.data // 删除最小节点 p = minP pp = minPP } let child // 删除节点是叶子节点或者只有一个子节点 if (p.left !== null || p.left !== undefined) { child = p.left } else if (p.right !== null || p.right !== undefined) { child = p.right } else { child = null } if (pp === null || pp === undefined) { tree = child // 删除根节点 } else if (pp.left === p) { pp.left = child } else { pp.right = child }} 时间复杂度查找: O(logn)插入: O(logn)删除: O(logn) 当特殊时间会退化成 O(n)的复杂度 和散列表比较 对比 散列表 二叉树查找树 是否有序 无序存储,还需再次排序 有序,中序遍历就可以 O(n)输出有序数据 性能 需要动态扩容,散列冲突需要考虑,性能不稳定 不需要扩容,二叉查找时有可能性能不平衡,但是平衡二叉查找树,性能很平衡,稳定在 O(logn) 时间复杂度 常量级别,但因为冲突因子存在,常量并不一定小,加上哈希函数的耗时,可能更长 稳定在 O(logn)可能比常量级更小 实现复杂度 考虑扩容、缩容、散列冲突,比较复杂 只需要考虑平衡性","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"二叉树基础","date":"2019-12-20T03:17:02.000Z","path":"2019/12/20/2019-12-20二叉树基础/","text":"概念 高度:从叶子节点到当前节点 深度:从根节点到当前节点 层数:和深度类似,深度+1 二叉树 (Binary Tree) 每个节点最多有两个子节点 左子节点和右子节点 二叉树分类 完美二叉树(Perfect Binary Tree):除了叶子节点,都有左右子节点,且每一层都被填满 满二叉树(Full Binary Tree):除了叶子节点,都有左右子节点 完全二叉树(Complete Binary Tree):除了最后一层,其它层都被填满,且最后一层左对齐 链式存储每个节点储存数据,和左右子节点的指针 数组存储根节点存储在小标 1(方便接下来计算),如果当前值存在小标 i,那么 2i 存储左子节点,2i+1 存右子节点 二叉树遍历 前序遍历:自身,左子树,右子树 中序遍历:左子树,自身,右子树 后序遍历:左子树,右子树,自身 12345678910111213141516171819202122232425262728// 伪代码// 前序function preOrder(node) { if (!node) { return } console.log(node) // 打印自身 preOrder(node.left) // 左 preOrder(node.right) // 右}// 中序function inOrder(node) { if (!node) { return } inOrder(node.left) // 左 console.log(node) // 打印自身 inOrder(node.right) // 右}// 后序function postOrder(node) { if (!node) { return } postOrder(node.left) // 左 postOrder(node.right) // 右 console.log(node) // 打印自身} 每个节点最多遍历两次 时间复杂度 O(n)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"哈希算法","date":"2019-12-16T08:54:48.000Z","path":"2019/12/16/2019-12-16哈希算法/","text":"哈希算法 单向算法,不能从哈希值,推到出原始数据 数据敏感,原始值很小的差距,hash 值可以很大改变 hash 冲突小,不同的值得出的 hash 值,冲突的概率很小 高效,很长的文本也能很快算出 hash 值 应用 数据加密 MD5、SHA、DES、AES 反向推到数据可能小 散列冲突小 唯一标识 对图片做唯一标识,方便的识别图片,处理版权问题 数据校验 先得到数据的 hash,然后获取数据,计算 hash 值看数据是否改变 散列函数 散列函数对性能的要求比较高,对安全的要求低,所以散列函数一般采用简单的 负载均衡 把用户ip或者id做hash运算,得到的hash值取模,此时的值对应此编号的服务器 数据分片 把数据计算hash值,对机器取模放入对应编号机器 查找的时候一样计算取模,去指定机器查找,这样实现了数据的分片 分布式存储 先数据分片,但是扩容会出问题,需要大量搬移数据 使用一致性hash算法,把hash值区间[0, max]分成小区间 小区间个数远远大于机器个数,当加入机器,把部分区间搬到新机器 区块链 区块链中,区块分为头和体 区块头中会存储上一个区块,和当前区块的 hash 值,计算比较耗时 如果要篡改一个区块,必须把区块后所有的区块全部计算一遍","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"散列表","date":"2019-12-16T03:24:18.000Z","path":"2019/12/16/2019-12-16散列表/","text":"概念 数组实现随机访问 O(1) 散列函数实现下标对应 散列值 hash 值非负数 相同散列值,结果相同 不同散列值,结果不同(散列冲突) 散列冲突解决: 开放寻址法 线性探测:向后查找空处插入(线性查找) 二次探测法:寻址时使用平方 双重散列:多个散列函数,第一个冲突用第二个 链表法: 散列值相同,放入同一个槽的链表中 装载因子: 填入表的个数/散列表长度 工业级散列表 装载因子过高:动态扩容(避免一次搬移数据消耗太多时间,先扩容数据搬移,在每次查找时完成) 装载因子过低:省内存可以动态缩容 冲突解决分析 开放寻址法:优点:数组存储查找快,方便序列化缺点:冲突代价高,尤其在删除是还需要标记,所以一般装载因子都得小,这样需要更多存储空间总结:数据量小,装载因子小,适合开放寻址 链表法:优点:存储空间利用率更高,转载因子可以更大缺点:链表需要存储指针,需要消耗更多内存空间,内存地址不连续,对CPU缓存不友好总结:适合存储大对象,大量数据","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"跳表","date":"2019-12-05T04:59:51.000Z","path":"2019/12/05/2019-12-05跳表/","text":"链表加多级索引就是跳表时间复杂度分析 n 个数据,假设每 2 个数据建 1 索引,那第一级索引个数为 n/2,第 k 级索引是 n/2^k 当 k 级索引个数为 2 时,n/2^k = 2,k = log2(n) - 1,加上原始数据,那整个跳表高度为 log2(n) 每 2 个数据一个索引,那每一级遍历最多 3 次,时间复杂度为:O(3*log2(n)) => O(log(n)) 空间复杂度分析 第一级 n/2 第二级 n/4 第三级 n/8 …等比数列 额外的空间占用 n - 2 当索引建立间隔增加,空间占用会更小 插入和删除 查找的时间复杂度为: O(log(n)) 执行插入的时间复杂度为 O(1) 删除查找的时间复杂度为: O(log(n)) 执行删除的时间复杂度为 O(1),同时还需要删除索引节点,单链表还需要找到前驱节点 索引动态更新 当插入数据过多时,可能退化为单链表,这时候需要动态更新索引 使用随机函数来插入索引,从 k 级到 1 级执行插入,随机函数要保证插入的数据大小平衡和索引大小平衡","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"二分查找(2)","date":"2019-12-04T06:22:14.000Z","path":"2019/12/04/2019-12-04二分查找-2/","text":"第一个给定值123456789101112131415161718192021const list = [1, 2, 3, 4, 5, 6, 8, 8, 8, 9, 10]function binarySearchFirst(list = [], value) { let low = 0 let hight = list.length - 1 while (low <= hight) { const middle = Math.floor(hight + (low - hight) / 2) if (list[middle] > value) { hight = middle - 1 } else if (list[middle] < value) { low = middle + 1 } else { if (middle === 0 || list[middle - 1] !== value) { return middle } hight = middle - 1 } } return -1}console.log(binarySearchFirst(list, 8)) 最后一个给定值123456789101112131415161718192021const list = [1, 2, 3, 4, 5, 6, 8, 8, 8, 9, 10]function binarySearchLast(list = [], value) { let low = 0 let hight = list.length - 1 while (low <= hight) { const middle = Math.floor(hight + (low - hight) / 2) if (list[middle] > value) { hight = middle - 1 } else if (list[middle] < value) { low = middle + 1 } else { if (middle === list.length - 1 || list[middle + 1] !== value) { return middle } low = middle + 1 } } return -1}console.log(binarySearchLast(list, 8)) 第一个大于等于给定值12345678910111213141516171819const list = [1, 2, 3, 4, 5, 6, 8, 8, 8, 9, 10]function binarySearchGE(list = [], value) { let low = 0 let hight = list.length - 1 while (low <= hight) { const middle = Math.floor(hight + (low - hight) / 2) if (list[middle] >= value) { if (middle === 0 || list[middle - 1] < value) { return middle } hight = middle - 1 } else { low = middle + 1 } } return -1}console.log(binarySearchGE(list, 8)) 最后一个小于等于给定值12345678910111213141516171819const list = [1, 2, 3, 4, 5, 6, 8, 8, 8, 9, 10]function binarySearchLE(list = [], value) { let low = 0 let hight = list.length - 1 while (low <= hight) { const middle = Math.floor(hight + (low - hight) / 2) if (list[middle] <= value) { if (middle === list.length - 1 || list[middle + 1] > value) { return middle } low = middle + 1 } else { hight = middle - 1 } } return -1}console.log(binarySearchLE(list, 8)) 12345678910111213141516171819202122232425262728293031// 升序排列的循环有序数组const list = [4, 5, 6, 7, 8, 1, 2, 3]function binarySearchCircle(list = [], value) { let low = 0 let high = list.length - 1 while (low <= high) { const middle = Math.floor(high + (low - high) / 2) // 循环有序数组被切割后,会分为有序数组和循环有序数组 if (list[middle] === value) { return middle } if (list[low] <= list[middle]) { // 左侧有序 if (list[middle] >= value && list[low] <= value) { high = middle - 1 } else { low = middle + 1 } } else { // 右侧有序 if (list[high] >= value && list[middle] <= value) { low = middle + 1 } else { high = middle - 1 } } } return -1}console.log(binarySearchCircle(list, 8))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"二分查找","date":"2019-12-02T11:40:26.000Z","path":"2019/12/02/2019-12-3二分查找/","text":"二分查找 不断地二分区间,快速缩小区间范围 时间复杂度 O(logn) 局限 依赖数组,数组的下标随机访问,时间复杂度 O(1) 依赖有序,如果没序,需要先排序,那时间复杂度就高了(插入和删除操作不频繁的场景) 数据量太小,不需要用,遍历性能也不差,但是比较很耗费性能,那建议使用二分查找,减少比较 数据量不能太大,数组要求连续的内存存储,数据量太大对内存影响太大 123456789101112131415161718// 非递归function binarySearch(list = [], value) { let low = 0 let high = list.length - 1 while (low <= high) { const middle = Math.floor(low + (high - low) / 2) if (list[middle] === value) { return middle } else if (list[middle] > value) { high = middle - 1 } else { low = middle + 1 } } return -1}const list = [1, 2, 3, 4, 5, 6, 8, 10]console.log(binarySearch(list, 7)) 12345678910111213141516171819// 递归function binarySearch(list = [], value) { return bSearch(list, 0, list.length - 1, value)}function bSearch(list, low, high, value) { if (low > high) { return -1 } const middle = Math.floor(low + (high - low) / 2) if (list[middle] === value) { return middle } else if (list[middle] > value) { return bSearch(list, low, middle - 1, value) } else { return bSearch(list, middle + 1, high, value) }}const list = [1, 2, 3, 4, 5, 6, 8, 10]console.log(binarySearch(list, 2)) 123456789101112131415161718192021222324252627function sqrtBisection(n) { if (isNaN(n)) return NaN; if (n === 0 || n === 1) return n; var low = 0, high = n < 1 ? 1 : n, pivot = (low + high) / 2, lastPivot = pivot; // do while 保证执行一次 do { console.log(low, high, pivot, lastPivot) if (Math.pow(pivot, 2) > n) { high = pivot; } else if (Math.pow(pivot, 2) < n) { low = pivot; } else { return pivot; } lastPivot = pivot; pivot = (low + high) / 2; } // 2018-04-25 22:08 更新 // 使用Number.EPSILON表示能够接受的最小误差范围 while (Math.abs(pivot - lastPivot) >= Number.EPSILON) return pivot;}console.log('sqrtBisection', sqrtBisection(9), Math.sqrt(9))","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"排序(3)","date":"2019-12-02T09:14:21.000Z","path":"2019/12/02/2019-12-2排序-3/","text":"线性排序 时间复杂度为:O(n)桶排序 bucket sort桶排序概念 n 个数据均匀分配到 m 个桶中 每个桶中元素为 k = n / m 对每个桶中的数据进行排序(快速排序 O(klogk)) m 个桶,整个时间福再度 O(m _ klogk) ====(k = n / m)====> O(n _ log(n / m)) 当 n 接近于 m 时,时间复杂度为 O(n) 桶排序局限 桶之间数据要均匀分配,否则排序要退化为快排 桶之间数据要有序,否则再次排序时间复杂度 计数排序 counting sort 可以理解为桶排序的特殊情况,多少个数据,就多少个桶 计数排序只能用在数据范围不大的地方,且数据必须为非负数 123456789101112131415161718192021222324252627282930313233343536let testArr = []let i = 0while (i < 10) { testArr.push(Math.floor(Math.random() * 10)) i++}function counting(list = []) { let max = list[0] list.forEach(num => { if (num > max) { max = num } }) const countArr = new Array(max + 1).fill(0) list.forEach(num => { countArr[num]++ }) // 相邻累加 countArr.reduce((pre, cur, i) => { const plus = pre + cur countArr[i] = plus return plus }, 0) const tempArray = [] // 倒序放值 for (let i = list.length - 1; i >= 0; i--) { // 值 list[i];计数位置 countArr[list[i]];转为数组下标 countArr[list[i]] - 1 const index = countArr[list[i]] - 1 tempArray[index] = list[i] countArr[list[i]]-- } return tempArray}console.log(testArr)console.log(counting(testArr)) 基数排序手机号码的例子,按照位去比较,当大的位数不相同时,直接比较就可以得到大小,不用再比较小的位了","tags":[]},{"title":"排序(2)","date":"2019-11-28T10:01:23.000Z","path":"2019/11/28/2019-11-28排序-2/","text":"分治思想 将大的问题分解成小问题,然后分别解决 分治是一种思想,递归是编程技巧 归并排序 merge sort12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849/** * 归并排序 * @param {*} list * 1. 把数组分解到最小 * 2. 然后合并 (有序) * 3. 得到有序数组 * 使用递归,递归公式如下: * mergeSort(list) = merge(mergeSort(listLeft), mergeSort(listRight)) * 终止条件 list <= 1 * 合并:使用两个哨兵从两个数组里面取值,值小的放入临时数组中,然后哨兵后移 */let testArr = []let i = 0while (i < 50) { testArr.push(Math.floor(Math.random() * 1000)) i++}function mergeSort(list) { if (list.length <= 1) { return list } const middle = Math.floor(list.length / 2) const leftArr = list.slice(0, middle) const rightArr = list.slice(middle) return merge(mergeSort(leftArr), mergeSort(rightArr))}function merge(leftArr, rightArr) { const temp = [] let i = 0 let j = 0 let leftLength = leftArr.length let rightLength = rightArr.length while (i < leftLength && j < rightLength) { if (leftArr[i] <= rightArr[j]) { temp.push(leftArr[i]) i++ } else { temp.push(rightArr[j]) j++ } } return temp.concat(leftArr.slice(i)).concat(rightArr.slice(j))}console.log(mergeSort(testArr))// 稳定排序 merge 里 leftArr[i] <= rightArr[j] 相同值左侧先放就是稳定// 时间复杂度 O(nlogn)// 空间复杂度 O(n) 不是原地排序 快速排序 quick sort12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849let testArr = []let i = 0while (i < 50) { testArr.push(Math.floor(Math.random() * 1000)) i++}/** * 快速排序 * @param {*} list * 递归工时 1. quickSort(p ... r) = quickSort(p ... q - 1) + quickSort(q + 1 ... r) * 终止条件 2. p >= r */function quickSort(list = []) { quick_sort(list, 0, list.length - 1)}function quick_sort(list, p, r) { // 终止条件 if (p >= r) { return } // 分区点 const q = partition(list, p, r) // 递归 quick_sort(list, p, q - 1) quick_sort(list, q + 1, r)}// 分区点function partition(list, p, r) { const pivot = list[r] let i = p for (let j = p; j < r; j++) { if (list[j] < pivot) { if (i !== j) { ;[list[i], list[j]] = [list[j], list[i]] } i++ } } ;[list[i], list[r]] = [list[r], list[i]] return i}console.log(testArr)quickSort(testArr)console.log(testArr)// 非稳定排序// 时间复杂度 O(nlogn)// 空间复杂度 O(1) 原地排序 123456789101112131415161718192021222324252627282930// 获取数组中第k大的元素,利用分区的思想,当分区点 p + 1 === k 时,得到第 K 大(降序排列)function getK(list, k, m, n) { const p = partition(list, m, n) if (p + 1 === k) { return list[p] } else if (p + 1 > k) { return getK(list, k, m, p - 1) } else { return getK(list, k, p + 1, n) }}function partition(list, m, n) { let i = m let pivot = list[n] for (let j = m; j < n; j++) { // 降序排列 if (list[j] > pivot) { if (j != i) { ;[list[j], list[i]] = [list[i], list[j]] } i++ } } ;[list[n], list[i]] = [list[i], list[n]] return i}const list = [1, 2, 3, 4, 5, 6]console.log('k', getK(list, 2, 0, 5), list)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"排序","date":"2019-11-28T03:38:06.000Z","path":"2019/11/28/2019-11-28排序/","text":"排序算法的执行效率 最好情况、最坏情况和平均情况时间复杂度 时间复杂度的系数、常数和低阶 比较次数和移动次数 排序算法的内存消耗 原地排序,空间复杂度 O(1) 排序算法的稳定性 相同值经过排序位置不变 有序度 数组中具有有序关系对的个数 满有序度 n * (n - 1) / 2 有序度 = 满有序度 - 逆有序度 排序算法冒泡 bubble sort123456789101112131415161718192021222324252627282930/** * 冒泡排序 * @param {*} list 数组 * 1. 每一次比较相邻两个数的大小,按照比较结果排序 * 2. 经过 n 排序,得到排序结果 * 3. 每次冒泡次数为:j < len - 1 - i ,-1 是相邻比较次数比长度少一次,-i 是因为排过的不用再比较 * 4. 设置标志位,如果一次冒泡没有排序,代表排序完成提前退出 */function bubbleSort(list = []) { const len = list.length for (let i = 0; i < len; i++) { let isExchange = false for (let j = 0; j < len - 1 - i; j++) { if (list[j] > list[j + 1]) { [list[j], list[j + 1]] = [list[j + 1], list[j]] isExchange = true } } if (!isExchange) { break } } return list}console.log(bubbleSort([3, 23, 1, 2, 3, 4]))/** * 1. 原地排序 空间复杂度O(1) * 2. 稳定排序 只有大于时才交换 * 3. 时间复杂度 最好一次冒泡 O(n) 最坏(倒序) O(n^2) 平均 * / 插入 insertion sort123456789101112131415161718192021222324252627/** * 插入排序 * @param {*} list 数组 * 1.遍历有序数组找到插入位置,插入数据 * 2.把数组分为有序数组和无序数组 * 3.插入开始 i = 1 知道数组结束 * 4.遍历有序数组找到插入位置插入,其他数据后移 */function insertionSort(list = []) { for (let i = 1; i < list.length; i++) { const value = list[i] let j = i - 1 for (; j >= 0; j--) { if (value < list[j]) { list[j + 1] = list[j] } else { break } } list[j + 1] = value } return list}console.log(insertionSort([3, 23, 1, 2, 3, 4]))// 原地排序// 稳定排序// 最好O(n) 最坏O(n^2) 平均 O(n^2) 选择 selection sort1234567891011121314151617181920212223/** * 选择排序 * 1.分为排序和未排序部分,未排序部分找出最小值,放到已排序队尾 * @param {*} list 数组 */function selectionSort(list = []) { const len = list.length for (let i = 0; i < len - 1; i++) { let min = i; for (let j = i + 1; j < len; j++) { if (list[min] > list[j]) { min = j } } [list[i], list[min]] = [list[min], list[i]] } return list}console.log(selectionSort([23, 3, 4, 2, 3, 1]))// 原地排序// 非稳定排序// 最好O(n^2) 最坏O(n^2) 平均 O(n^2)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"递归","date":"2019-11-27T11:05:38.000Z","path":"2019/11/27/2019-11-27递归/","text":"递归的条件 问题可以分解为子问题 分解后的子问题,除了数据规模不同,解决方法一致 存在递归终止条件,不能存在无限循环 递归关键点 递归公式 终止条件 1234567891011121314151617181920// 走台阶,一次可以走一次或者两次,总共7个台阶,有多少种可能呢?// 1.问题分解:7个台阶可能性可以拆分为,6个台阶的可能性+5个台阶可能性// 2.解法一致:6个台阶和5个台阶,可以接着分解// 3.终止条件:1个和2个台阶时分别有1,2种可能性,可终止// 递归公式// f(n) = f(n-1)+f(n-2)// 终止条件// f(1) = 1// f(2) = 2function f(n) { if (n === 1) { return 1 } if (n === 2) { return 2 } return f(n - 1) + f(n - 2)} 递归难点 不要深入去思考递归过程,我们需要的是推到递归公式和终止条件 警惕堆栈溢出 1234567891011// 伪代码// 全局变量,表示递归的深度。int depth = 0;int f(int n) { ++depth; if (depth > 1000) throw exception; if (n == 1) return 1; return f(n-1) + 1;} 警惕重复计算 12345678910111213141516171819202122232425262728function f(n) { if (n === 1) { return 1 } if (n === 2) { return 2 } return f(n - 1) + f(n - 2)}// f(7) = f(6) + f(5)// f(6) = f(5) + f(4)// 重复计算了f(5)// 代码优化const temp = {}function f(n) { if (n === 1) { return 1 } if (n === 2) { return 2 } if (temp[n]) { return temp[n] } let ret = f(n - 1) + f(n - 2) temp[n] = ret return ret} 调试递归: 打印日志发现,递归值 结合条件断点进行调试","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"队列","date":"2019-08-06T11:36:30.000Z","path":"2019/08/06/2019-08-06队列/","text":"队列 先进先出,后进后出 数组实现的顺序队列 链表实现的链式队列 普通队列head 头,tail 尾,n 长度空时:head === tail满时:tail === n 循环队列head 头,tail 尾,n 长度空时:head === tail满时:(tail + 1) % n === head 阻塞队列 队列为空时,队头取数据阻塞,直到有数据;队列满时阻塞插入,直到有空间再执行插入 完美的符合 生产者-消费者 模型 当生产者生产数据过多,消费者来不及消费(队列满了),此时生产者阻塞;当消费者消费过多,生产者来不及生产(队列空了),此时消费者阻塞 并发队列当多线程消费队列时,称为并发队列,要保证队列的线程安全,可以加锁","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"栈","date":"2019-08-05T09:11:51.000Z","path":"2019/08/05/2019-08-05栈/","text":"栈 先进后出,后进先出,只允许在一端插入和删除数据。 数组实现为顺序栈。 链表实现为链式栈。 栈的应用1. 浏览器前进后退栈 1[a, b, c] 栈 2[]后退:栈 1[a, b] 栈 2[c]后退:栈 1[a] 栈 2[b, c]前进:栈 1[a, b] 栈 2[c]新开:栈 1[a, b, d] 栈 2[](清空) 2. [] 括号匹配我们用栈来保存未匹配的左括号,从左到右依次扫描字符串。当扫描到左括号时,则将其压入栈中;当扫描到右括号时,从栈顶取出一个左括号。匹配继续执行,不匹配非法格式。 3. 表达式求值 问题1.函数调用为何使用栈来实现?答:后入先出(LIFO)的数据结构,和函数嵌套执行顺序天然合适。另外就是函数执行的临时作用域,栈顶生成执行完重置,也很合适。","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"链表","date":"2019-07-23T11:05:00.000Z","path":"2019/07/23/2019-07-22-链表/","text":"定义 不需要连续内存,通过“指针”将零散的内存块串联起来 分类 单向链表尾节点指向 null 双向链表拥有双向指针,头节点的前节点是 null,尾节点的后节点是 null 循环链表尾节点执向 头节点 双向循环链表拥有双向指针,头节点的前节点是尾节点,尾节点的后节点是头节点 复杂度 插入 O(1) 删除 O(1) 查找 O(n) 知识点1.指针与变量 变量赋值给指针,是把变量的内存地址赋值给了指针 指针保存这个变量的内存地址,此内存地址指向这个变量 2.指针丢失与内存泄露1234567// a.next => b 此时插入 xa.next = x // 此时a.next 已经改变x.next = a.next // 指针丢失//正确代码x.next = a.next // 先把下个节点赋值a.next = x 3.哨兵 哨兵很有用,尤其在处理边界问题时 4.链表验证 链表为空时 链表只要一个节点时 链表只有两个节点时 处理链表头尾时 5.画图和举列子比如简单的插入值,就可以在图上画出示意图 6.练习题链表练习题 单链表反转 链表中环的检测 两个有序的链表合并 删除链表倒数第 n 个结点 求链表的中间结点 leetcode 对应 206,141,21,19,876","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"数组","date":"2019-07-22T14:47:03.000Z","path":"2019/07/22/2019-07-22-数组/","text":"线性表数据结构,用连续的内存,来存储相同类型的数据(JavaScript 中的数组比较类型可不同) 线性 线性表:数据连成一条线,并且数据最多只有前后两个方向(数组、链表、队列和栈) 非线性表:可以有多个方向(树、图和堆) 连续内存空间和相同数据类型 连续内存支持随机访问 O(1) 插入 O(n)和删除 O(n)","tags":[{"name":"技术,数据结构,算法","slug":"技术,数据结构,算法","permalink":"http://yoursite.com/tags/技术,数据结构,算法/"}]},{"title":"复杂度分析","date":"2019-07-19T06:22:17.000Z","path":"2019/07/19/2019-07-19-复杂度分析/","text":"大 O 复杂度表示法表示随着数据增加,复杂度的增长趋势 大 O 时间复杂度表示随着数据增加,代码执行时间的增长趋势 只关注执行次数最多的代码 加法法则(只关注量级最大的,比如n^2大于n) 乘法法则(嵌套代码的复杂度,等于嵌套代码复杂度的乘积) 时间复杂度分析 最好情况时间复杂度 最理想情况下,代码执行时间复杂度 最坏情况时间复杂度 最理糟糕情况下,代码执行时间复杂度 平均情况时间复杂度 其实是求加权平均值,即期望值 均摊情况时间复杂度 把时间复杂度高的,均摊到其他复杂度低的操作上,计算出来的时间复杂度 复杂度量级(递增) 常量阶 O(1) 对数阶 O($\\log$n) 线性阶 O(n) 线性对数阶 O(n$\\log$n) 平方阶 O(n^2) 等 指数阶 O(2^n) 阶乘阶 O(n!) 大 O 空间复杂度表示随着数据增加,代码存储空间的增长趋势 (其他类似时间复杂度)","tags":[{"name":"技术","slug":"技术","permalink":"http://yoursite.com/tags/技术/"},{"name":"算法","slug":"算法","permalink":"http://yoursite.com/tags/算法/"}]},{"title":"JavaScript 继承","date":"2019-07-02T09:39:29.000Z","path":"2019/07/02/JavaScript-继承/","text":"","tags":[]},{"title":"node.js 笔记","date":"2018-02-11T06:14:06.000Z","path":"2018/02/11/了不起的-node-js-笔记/","text":"诞生新的 V8 引擎可以嵌到操作系统集成层,Ryan Dahl 看到这个机会,让 JavaScript 可以调用操作系统底层异步接口,实现了将其带到操作系统的目的。 流程安装模块依赖构建应用中间件定义路由监听","tags":[]},{"title":"JavaScript 权威指南","date":"2018-02-08T07:48:05.000Z","path":"2018/02/08/JavaScript-权威指南/","text":"","tags":[]},{"title":"Android + Linux + Jenkins + React Native 自动化构建","date":"2017-09-20T09:55:07.000Z","path":"2017/09/20/Android-Linux-Jenkins-自动化构建/","text":"环境配置因为公司给的服务器,没有任何配置,我们需要从头开始配置。 连接服务器本人用 Mac 1ssh <用户名>@<ip地址> -p <端口> 替换<>及其对应内容,没有端口可以不写。如需密码会要求输入,照此操作就可。 服务器系统1ping <ip地址> 得到 ttl = 64 判断系统为 Linux Git 安装配置CentOS 系统 使用 yum 安装 123456yum install git // 安装git –version // 查看安装是否成功git config --global user.name "用户名" // 配置用户名git config --global user.email "邮箱" // 配置邮箱ssh-keygen -t rsa -b 4096 -C "邮箱" // 生成公钥,三次握手直接 enter 默认配置cat ~/.ssh/id_rsa.pub // 复制公钥 公司使用 gitolite 搭建的 git 相关文章大家可以自己搜索。把生成的公钥添加到 git,配置相应权限。 JDK 安装配置12su -c "yum install java-1.8.0-openjdk" // 安装java -version // 检查是否安装 openjdk 下载 Jenkins 安装123sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.reposudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.keysudo yum install jenkins Jenkins 安装Start/Stop 命令 12sudo service jenkins start/stop/restartsudo chkconfig jenkins on SDK 安装配置123456789101112131415161718192021222324252627// 基础包wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz// 解压tar -zxvf android-sdk_r24.4.1-linux.tgz// 进到tools目录cd android-sdk-linux/tools// 查看可见sdk版本./android list sdk -a// 版本前面会有序号 [-a 列出所有sdk版本] [-u 无ui模式] [-t 指定安装版本]./android update sdk -a -u -t [序号]// 指定版本多个安装android update sdk -u --all --filter 1,2,3// 安装完成后此时我们在 .../tools 目录下, 获取当前 sdk 路径cd .. && pwd// 配置 ANDROID_HOMEvim /etc/profile// 加上export ANDROID_HOME="你的sdk路径"// 立即生效source /etc/profile node 配置123456789// 安装make及gcc,gcc-c++编译器yum install -y gcc-c++ make// 获取源码curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash -// 安装yum install nodejs// 查看版本node -vnpm -v React Native 环境配置RN 官网 1npm install -g react-native-cli 这里记住 Android SDK Build-Tools 版本为 23.0.1版本具体信息安装方式参见 上面 SDK 配置 Jenkins 配置// 打开 jenkins 默认配置 这里可以更改配置 比如更改 Java 环境配置 1vi /etc/default/jenkins // 查看 jenkins 打印记录 可以看到秘钥 1vi /var/log/jenkins/jenkins.log 本地使用的 gitolite 需要和 JenkinsJenkins 和 Gitolite 关联 1sudo -u jenkins ssh-keygen","tags":[{"name":"技术","slug":"技术","permalink":"http://yoursite.com/tags/技术/"},{"name":"Android","slug":"Android","permalink":"http://yoursite.com/tags/Android/"},{"name":"Jenkins","slug":"Jenkins","permalink":"http://yoursite.com/tags/Jenkins/"},{"name":"Linux","slug":"Linux","permalink":"http://yoursite.com/tags/Linux/"},{"name":"React Native","slug":"React-Native","permalink":"http://yoursite.com/tags/React-Native/"}]},{"title":"《白银时代》未来是银色的","date":"2017-06-26T07:27:35.000Z","path":"2017/06/26/《白银时代》未来是银色的/","text":"未来是银色的?是的,现在是银色的。 王小波的时代三部曲,大二的时候就买了。我还记得我留下两本,《黑铁时代》和《白银时代》,《黄金时代》送人了。买的时候好奇呀,总是听说王小波,书牛逼,人更牛逼,一定要看看。买是买了,看了几张看不下去了。有种看《百年孤独》的感觉,那种天马行空的跳跃,荒诞和混沌的故事,让当时的我脑袋昏沉。 现在想想,我很奇怪我当时的状态,初中的时候都会去啃《西游记》和《水浒传》原著,大学时的我,怎么仿佛丧失了阅读能力。幸好在工作几年后,慢慢明白了,读书的重要性,开始重拾读书的能力。 没想到,沉下心来读《白银时代》,越读越是绝望与悲凉。未来是银色的,那时的小说家不会写小说;历史学家不知历史,因为历史要为革命服务;哲学家的证件也保不得平安;画家一定要解释自己画的什么,解释不清要去砸碱劳动改造。 每一个故事都在实实在在的发生着,数盲症越来越多,传染性越来越强。老大哥式的人物,在受着“鞭刑”,“公司”的力量无处不在,安置着各种人。随着技术的进步,镣铐越来越多随处可见,镣铐的花样也越来越漂亮,甚至变成了装饰品。 很想知道,王小波假如活到现在会怎样?活在这个银色的世界会怎样?他是会直挺挺的战斗,直至死去。还是会从“数盲症”那里弄到签证,远离海外。还是…… 现在是银色的,那未来呢?云计算 + 大数据 + AI ,这些是未来,如果这些是银色的呢? BIG BROTHER IS WATCHING YOU.","tags":[{"name":"文学","slug":"文学","permalink":"http://yoursite.com/tags/文学/"}]},{"title":"React Native 构建跨平台APP","date":"2017-06-22T08:21:53.000Z","path":"2017/06/22/React-Native-构建跨平台APP/","text":"想要了解更多项目内容,请点击RN-Gank,欢迎来骚扰。 初衷最初想开始这个项目,是因为想做个有意思的东西,放到 GitHub 上面。也是想锻炼自己,想完成一个产品,从有想法开始到初版完成,这样一个过程。 产品方向 阅读类 阅读类 APP 简单的呈现更多内容 API 选择 http://gank.io/api 的 API 简洁且有每日福利,在此先感谢 gank 提供 API。 交互逻辑 采取主流的 TabBar + ListView 形式,简洁高效展示内容。 UI 黑白灰素雅冷淡风。 以上是整个产品初期的思考总结 实践问题与解决交互与设计 (一) 从开始确定阅读类 APP 就要考虑产品的最终形态,到应用市场下载了几个主流的阅读了 APP,感觉都不是很喜欢,然后搜索一些比较有设计感的 APP,看人家 APP 亮点。 最终确定,RN-Gank 需要简洁、高效和素雅的产品风格。 当我们着手去做的时候才发现,没有设计师真是挺痛苦的(当然没有产品经理真心爽)。美观优雅的布局,需要优秀设计师的支持才能做到。 写布局样式的过程中,只能边写边调,当然最终也做到了简洁美观 🙂。 应用技术 (一) 应用的架构选取,首先确立 React Navigation 导航,React Native 最初提供两种导航方式,但这两种都存在各种的设计缺陷。 得益于 React 社区的活跃,第三方组织和个人都提供了较好的导航方案,其中 React Community 提供的 React Navigation 是官方推荐之一。多方比较,这里选取的也是这种方案,具体的比较过程这里就不赘述了。 (二) 数据的流动是整个 APP 非常核心的部分,Redux 为 React 提供的全家桶是不二之选,且和 React Navigation 的搭配如丝般顺滑。同时在数据管理中引入了 Immutable 不可变对象,很好的优化了性能。 开发的整个过程,当然遇到了很多小问题,这里就不一一细说了。 总结这篇文章比较简陋,肯定没法详细讲述,整个项目的过程。后续肯定有针对性的文章,把每个点抽出来细细的讲。 欢迎关注微信公众号: song_say个人博客: 宋公子的博客GitHub: DavidSongzw知乎: 行走着的宋公子","tags":[{"name":"技术","slug":"技术","permalink":"http://yoursite.com/tags/技术/"},{"name":"Android","slug":"Android","permalink":"http://yoursite.com/tags/Android/"},{"name":"React Native","slug":"React-Native","permalink":"http://yoursite.com/tags/React-Native/"},{"name":"iOS","slug":"iOS","permalink":"http://yoursite.com/tags/iOS/"}]},{"title":"关于我","date":"2017-04-13T09:48:44.000Z","path":"2017/04/13/关于我/","text":"欢迎关注微信公众号: song_say个人博客: 宋公子的博客GitHub: DavidSongzw知乎: 行走着的宋公子","tags":[{"name":"关于我","slug":"关于我","permalink":"http://yoursite.com/tags/关于我/"}]},{"title":"Hexo搭建个人博客(Mac)","date":"2017-04-13T07:49:15.000Z","path":"2017/04/13/Hexo搭建个人博客-Mac/","text":"Hexo 介绍Hexo 是一个简单、快速和功能强大的博客框架。你可以使用 MD 或者其他语言书写,然后一键生成静态页面。 安装准备 Node.js Git GitHub 配置 Node图形化安装 下载地址下载安装就好了。 命令行安装 命令行安装命令地址 Using Homebrew:慢的话请科学上网 1brew install node Using MacPorts: 123ort install nodejs<major version>//例如port install nodejs7 Using pkgsrc: 安装二进制包: 1pkgin -y install nodejs 或者手动构建 1cd pkgsrc/lang/nodejs && bmake install 配置 Git Mac 安装 Xcode 后自带 Git没有的话,只用安装 Command Line Tools 就可以了 1xcode-select --install 图形化 Git下载地址 配置 GitHub 注册 GitHub 账号并建立仓库,仓库命和用户名必须一致(踩过不一致的坑…),比如我的地址为:https://github.com/DavidSongzw/DavidSongzw.github.io.git 配置 git 用户名和密码 12git config --global user.email \"你的邮箱\"git config --global user.name \"你的名字\" 生成 SSH 公钥 1234567cd ~/.ssh // 检查是否已经有公钥,有的话执行下面三个命令mkdir key_backupcp id_rsa* key_backuprm id_rsa*// 然后执行生成新的公钥ssh-keygen -t rsa -C “你的邮箱地址” // 和GitHub上邮箱一致 添加 SSH 公钥 在.ssh 下看到 id_rsa.pub,打开文件复制编码,按照下面步骤添加:Account settings -> SSH Keys -> Add SSH Key 验证是否成功 1ssh -T [email protected] 安装 Hexo 全局安装 1npm install -g hexo-cli 初始化 1234567hexo init <folder> //folder 博客目录名称cd <folder>npm install// 例如hexo init blog //folder 博客目录名称cd blognpm install 目录结构 12345678├── _config.yml├── node_modules├── package.json├── scaffolds├── source ├── _posts └── _drafts or assets or ...└── themes 配置 Hexo1. _config.yml下面讲的是一些常见配置 Site 123456title: 博客名字subtitle: 博客副标题description: 描述author: 你的名字email: 邮箱keywords: 自己的一些关键词 Deployment 1234deploy: type: git // 类型git repo: https://github.com/DavidSongzw/DavidSongzw.github.io.git // 地址 branch: master // 所在分支 注意安装插件: 1npm install hexo-deployer-git --save 2. node_modules编译出来的文件不用关心。 3. package.json整个项目的配置信息(比如名称、版本、许可证等元数据)。 4. scaffolds当我们去创建新的帖子的时候,会先在此目录寻找是否存在,不存在才会创建新的文件。 5. source5.1 _posts创建的帖子在此存放。 5.2 _drafts or assets or …根据配置不同可能不存在此目录,比如我的配置中有 assets 这个目录,用于放置一些写作资料,比如图片。 6. themes(主题)我的主题是yilia,详细配置参照作者 litten 的 主题 GitHub 和 litten 博客配置样本,这里就不啰嗦了。 Hexo 命令123456789101112hexo new \"postName\" #新建文章hexo new page \"pageName\" #新建页面hexo generate #生成静态页面hexo server #开启本地(localhost:4000,'ctrl + c'关闭server)hexo deploy #部署到GitHub简写hexo n == hexo newhexo g == hexo generatehexo s == hexo serverhexo d == hexo deploy 新建文章12// 例如我创建这篇博客hexo new "Hexo搭建个人博客(Mac)" 自动生成的内容如下: --- title: Hexo搭建个人博客(Mac) date: 2017-04-13 15:49:15 tags: --- 更改后如下: --- title: Hexo搭建个人博客(Mac) date: 2017-04-13 15:49:15 categories: 博客 tags: [Hexo, 个人博客] --- 需要注意的是加多个便签,请按照上面格式添加。 书写语法不论是程序还是文字工作者,都建议使用 Markdown 语法,可参考如下链接学习。Markdown 语法 wiki 欢迎关注微信公众号: song_say个人博客: 宋公子的博客GitHub: DavidSongzw知乎: 行走着的宋公子","tags":[{"name":"Hexo","slug":"Hexo","permalink":"http://yoursite.com/tags/Hexo/"},{"name":"个人博客","slug":"个人博客","permalink":"http://yoursite.com/tags/个人博客/"}]},{"title":"小程序的小思考","date":"2017-04-09T14:47:11.000Z","path":"2017/04/09/小程序的小思考/","text":"连接一切微信战略层是想微信连接一切。 订阅号已经超预期的完成了,连接线上的服务,但是体验感不好,继而微信推出了服务号。 而服务号存在感一直很低,能说的出来的服务号,也就招商银行做的不错。小程序的推出就顺理成章了,承接服务号线上服务功能,连接线下场景。 在微信公开课上,张小龙举了这样两个小程序的例子: 在公交车站扫一下二维码,就知道下一班车什么时候到。在公共汽车站,扫一下二维码就可以购票,完全不用排队。 一直提及的都是线下场景,这也显示了微信对于连接一切的野心。 为何是线下线上APP的获客成本已经居高不下,成本最高的互联网金融行业,已经过千甚至于逼近上万。 线上已经高居不下,线下又是一片未开发的处女地,这显然是微信想吃掉的市场。 而且在线下相对封闭的环境下,用户的注意力不会线上一样,很容易被吸引,一条微信,一条推送,就可能吸引走用户的注意力。 举两个例子: 分众传媒 可能很多人都不知道分众传媒,但是绝大部分白领都天天接触,办公楼宇电梯里外的广告牌,很大部分都来自于分众传媒。在这种密闭空间下,用户对广告的接受度要高上很多,甚至愿意看广告。这是线下场景的极好运用,我们也相信,线下场景可挖掘部分远远不止于此。 古北水镇 这个清明节去了古北水镇,购票大厅排队的人很多。而微信购票流程如下: 整个过程如丝般顺畅,不用浪费排队买票和检票时间。 这已经极大的方便了游客,我们来看看使用小程序,这个过程是怎样的。 看似只少了关注和点击跳转,可是这里是最容易流失用户的关键点,而且小程序的优化要远超普通web页面。 用完即走张小龙一直在强调用完即走,这让开发者很担心,用户不在自己的产品里面停留,那怎么样留住用户呢。我们反过来想,好的产品才会有回头客,如果用户用的很爽的话,下次还会回来的。 小程序的新增功能1、 小程序支持模糊搜索 2、万众期待的“附近”功能,即将上线 附近功能虽然还没正式上线吗,却也是板上钉钉。该功能可以说是千呼万唤,它也几乎成为了零售领域最期待的一项功能:用户进入微信小程序界面后,可以直接看到所在地周围一定范围的所有“小程序”,包括实体店、服务店、商场等。 3、APP和小程序有了新的连接方式 在微信中分享App页面直接唤醒小程序,即APP链接分享到微信,点开就是小程序。 4、小程序兼容线下二维码 原有二维码也能进小程序如果你是一家商户,不仅可以通过小程序后台生成新的二维码,也可以将线下已经铺设的二维码经过后台配置,让顾客扫描原有二维码就可以直接打开小程序。 5、小程序与公众号新增三种连接方式 1)公众号可以把关联小程序放在自定义菜单中,用户点击可直达小程序。 2)公众号模版消息可打开相关小程序,即公众号运营者可以向用户推送关联小程序页面。 3)公众号绑定相关小程序时,可选择给粉丝下发通知,即公众号运营者可以通知粉丝,“我绑定了这个小程序”,粉丝点击消息就可以打开小程序,且该消息不占用原有群发条数。 6、降低开发门槛——个人可开发 启示 我们的微信商城比APP使用率高很多,小程序比微信 web页面体验好很多。可以很好的替代现在的微信商城,并提供更好的服务。 微信新开放的功能,可以让用户无缝进入小程序,弱化了推广。 二维码的扫码进入指定页面功能,在线下场景有极大想象空间。比如用户想要进入冻品列表,直接扫码进入,极大的缩短了用户购买流程。 消息推送功能,是极大的利器,可以配合运营更好的做活动。 小程序、订阅号、服务号、企业号必然会组成一个大的微信生态。就像 App Store ,因为微信天然与用户一直保持连接。微信商城和小程序起到联动的效应,在微信生态中发展壮大。 本文受到 可能吧 微信公众号文章启发,大家可以关注 knbknb 。如有不妥,表示歉意并删除。 欢迎关注微信公众号: song_say个人博客: 宋公子的博客GitHub: DavidSongzw知乎: 行走着的宋公子","tags":[{"name":"微信","slug":"微信","permalink":"http://yoursite.com/tags/微信/"},{"name":"小程序","slug":"小程序","permalink":"http://yoursite.com/tags/小程序/"}]}]