文章目录
五.链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表的特点
1.插入、删除数据效率高O(1)级别(只需更改指针指向即可),随机访问效率低O(n)级别(需要从链头至链尾进行遍历)。
2.和数组相比,内存空间消耗更大,因为每个存储数据的节点都需要额外的空间存储后继指针。
1. 单链表
每个节点只包含一个指针,即后继指针。
class Node {
constructor(element){
this.element = element
this.next = null
}
}
class LinkedList {
constructor(){
this.head = null
this.count = 0
}
push(element){
const node = new Node(element)
let current ;
if(this.head===null){
this.head = node
}else{
current = this.head
while(current.next!==null){
current = current.next
}
current.next = node
}
this.count++
}
removeAt(index){
if(index>=0&&index<this.count){
let current = this.head
if(index===0){
this.head = current.next
}else{
let previous
for(let i =0;i<index;i++){
previous = current
current = current.next
}
previous.next = current.next
}
this.count--
return current.element
}
return
}
getNodeAt(index){
if(index>=0&&index<this.count){
let node = this.head
for(let i=0;i<index;i++){
node= node.next
}
return node
}
return
}
removeAt2(index){
if(index>=0&&index<this.count){
let current = this.head
if(index===0){
this.head = current.next
}else{
const previous = this.getNodeAt(index-1)
current = previous.next
previous.next = current.next
}
this.count--
return current.element
}
return
}
equalFn(a,b){
return JSON.stringify(a)===JSON.stringify(b)
}
indexOf(element){
let current = this.head
for(let i=0;i<this.count;i++){
if(this.equalFn(element,current.element)){
return i
}
current = current.next
}
return -1
}
remove(element){
const index = this.indexOf(element)
return this.removeAt(index)
}
insert(element,index){
if(index>=0&& index<=this.count){
const node = new Node(element)
if(index===0){
const current = this.head
node.next = current
this.head = node
}else{
const previous = this.getNodeAt(index-1)
const current = previous.next
node.next = current
previous.next = node
}
this.count++
return true
}
return false
}
isEmpty(){
return this.size()===0
}
size(){
return this.count
}
getHead(){
return this.head
}
}
2. 双向链表
节点除了存储数据外,还有两个指针分别指向前一个节点地址(前驱指针prev)和下一个节点地址(后继指针next)。
class DoublyNode extends Node {
constructor(element) {
super(element);
this.prev = null;
}
}
class DoublyLinkedList extends LinkedList {
constructor() {
super();
this.tail = null;
}
push(element) {
const node = new DoublyNode(element);
if (this.head == null) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
this.count++;
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new DoublyNode(element);
let current = this.head;
if (index === 0) {
if (this.head == null) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
this.head.prev = node;
this.head = node;
}
} else if (index === this.count) {
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
} else {
const previous = this.getNodeAt(index - 1);
current = previous.next;
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this.count++;
return true;
}
return false;
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index === 0) {
this.head = this.head.next;
if (this.count === 1) {
// {2}
this.tail = undefined;
} else {
this.head.prev = undefined;
}
} else if (index === this.count - 1) {
current = this.tail;
this.tail = current.prev;
this.tail.next = undefined;
} else {
current = this.getNodeAt(index);
const previous = current.prev;
previous.next = current.next;
current.next.prev = previous; // NEW
}
this.count--;
return current.element;
}
return undefined;
}
getHead() {
return this.head;
}
getTail() {
return this.tail;
}
}
3. 循环链表
循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 undefined,而是指向第一个元素(head)
class CircularLinkedList extends LinkedList {
constructor() {
super();
}
push(element) {
const node = new Node(element);
let current;
if (this.head == null) {
this.head = node;
} else {
current = this.getNodeAt(this.size() - 1);
current.next = node;
}
node.next = this.head;
this.count++;
}
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(element);
let current = this.head;
if (index === 0) {
if (this.head == null) {
// if no node in list
this.head = node;
node.next = this.head;
} else {
node.next = current;
current = this.getNodeAt(this.size() - 1);
// update last element
this.head = node;
current.next = this.head;
}
} else {
const previous = this.getNodeAt(index - 1);
node.next = previous.next;
previous.next = node;
}
this.count++;
return true;
}
return false;
}
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index === 0) {
if (this.size() === 1) {
this.head = undefined;
} else {
let last = this.getNodeAt(this.size() - 1);
this.head = this.head.next;
last.next = this.head;
}
} else {
const previous = this.getNodeAt(index - 1);
current = previous.next;
previous.next = current.next;// 如果删的是最后一个也不怕,current.next 是head
}
this.count--;
return current.element;
}
return undefined;
}
}
六. 集合
集合是由一组无序且唯一(即不能重复)的项组成的。
1.集合类
class KerwinSet {
constructor() {
this.items = {};
}
add(element) {
if (!this.has(element)) {
this.items[element] = element;
return true;
}
return false;
}
delete(element) {
if (this.has(element)) {
delete this.items[element];
return true;
}
return false;
}
clear() {
this.items = {};
}
size() {
return Object.keys(this.items).length;
}
values() {
return Object.values(this.items);
}
has(element) {
return element in this.items;
}
}
2.ES6的Set
var myset = new Set()
myset.add(100)
myset.add(200)
myset.size
myset.values()
myset.delete(100)
myset.clear()
3.集合的运算
-
并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。
-
交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。
-
差集:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集 合的元素的新集合。
七. 字典
字典和集合很相似,集合以[值,值]的形式存储元素,字 典则是以[键,值]的形式来存储元素。字典也称作映射、符号表或关联数组。
1.字典的封装
class Dictionary {
constructor() {
this.table = {};
}
toStrFn(item) {
if (item === null) {
return 'NULL';
} else if (item === undefined) {
return 'UNDEFINED';
} else if (typeof item === 'string' || item instanceof String) {
return `${item}`;
}
return JSON.stringify(item);
}
hasKey(key) {
return this.table[this.toStrFn(key)] != null;
}
set(key, value) {
if (key != null && value != null) {
const tableKey = this.toStrFn(key);
this.table[tableKey] = new ValuePair(key, value);
return true;
}
return false;
}
remove(key) {
if (this.hasKey(key)) {
delete this.table[this.toStrFn(key)];
return true;
}
return false;
}
get(key) {
const valuePair = this.table[this.toStrFn(key)];
return valuePair == null ? undefined : valuePair.value;
}
//它会以数组形式返回字典中的所有 valuePair 对象。
keyValues() {
return Object.values(this.table);
}
keys() {
return this.keyValues().map(valuePair => valuePair.key);
}
values() {
return this.keyValues().map(valuePair => valuePair.value);
}
size() {
return Object.keys(this.table).length;
}
isEmpty() {
return this.size() === 0;
}
clear() {
this.table = {};
}
forEach(callbackFn) {
const valuePairs = this.keyValues();
for (let i = 0; i < valuePairs.length; i++) {
callbackFn(valuePairs[i].key, valuePairs[i].value);
}
}
}
class ValuePair {
constructor(key, value) {
this.key = key;
this.value = value;
}
}
var mymap = new Dictionary()
2.散列表
HashMap 类,它是 Dictionary 类的一种散列表 实现方式。.散列算法的作用是尽可能快地在数据结构中找到一个值。
class ValuePair {
constructor(key, value) {
this.key = key;
this.value = value;
}
}
class HashTable {
constructor() {
this.table = {};
}
toStrFn(item) {
if (item === null) {
return 'NULL';
} else if (item === undefined) {
return 'UNDEFINED';
} else if (typeof item === 'string' || item instanceof String) {
return `${item}`;
}
return JSON.stringify(item);
}
myHashCode1(key) {
if (typeof key === 'number') {
return key;
}
const tableKey = this.toStrFn(key);
let hash = 0;
for (let i = 0; i < tableKey.length; i++) {
hash += tableKey.charCodeAt(i);
}
//为了得到比较小的数值,我们会使用 hash 值和一个任意数做除法的余数
return hash % 37;
}
myHashCode2(key) {
const tableKey = this.toStrFn(key);
let hash = 5381;
for (let i = 0; i < tableKey.length; i++) {
hash = (hash * 33) + tableKey.charCodeAt(i);
}
return hash % 1013;
}
hashCode(key) {
return this.myHashCode2(key);
}
put(key, value) {
if (key != null && value != null) {
const position = this.hashCode(key);
this.table[position] = new ValuePair(key, value);
return true;
}
return false;
}
get(key) {
const valuePair = this.table[this.hashCode(key)];
return valuePair == null ? undefined : valuePair.value;
}
remove(key) {
const hash = this.hashCode(key);
const valuePair = this.table[hash];
if (valuePair != null) {
delete this.table[hash];
return true;
}
return false;
}
}
3. ES6的Map
var mymap = new Map()
mymap.set("name","kerwin")
mymap.set({a:1},"aaaaaa")
mymap.get("name")
mymap.delete("name")
八 . 树
树是一种分层数据的抽象模型。
1. 二叉树
二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。
2. 二叉搜索树
二叉搜索树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大的值。
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1,
EQUALS: 0
};
class Node {
constructor(key) {
this.key = key; // 节点值
this.left = null; // 左侧子节点引用
this.right = null; // 右侧子节点引用
}
}
class BinarySearchTree {
constructor() {
this.root = null; //Node 类型的根节点
}
insert(key) {
if (this.root == null) {
this.root = new Node(key);
} else {
this.insertNode(this.root, key);
}
}
insertNode(node, key) {
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
if (node.left == null) {
node.left = new Node(key);
} else {
this.insertNode(node.left, key);
}
} else {
if (node.right == null) {
node.right = new Node(key);
} else {
this.insertNode(node.right, key);
}
}
}
compareFn(a, b) {
if (a === b) {
return Compare.EQUALS;
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
inOrderMap(callback) {
this.inOrderMapNode(this.root, callback);
}
inOrderMapNode(node, callback) {
if (node != null) {
this.inOrderMapNode(node.left, callback);
callback(node.key);
this.inOrderMapNode(node.right, callback);
}
}
preOrderMap(callback) {
this.preOrderMapNode(this.root, callback);
}
preOrderMapNode(node, callback) {
if (node != null) {
callback(node.key);
this.preOrderMapNode(node.left, callback);
this.preOrderMapNode(node.right, callback);
}
}
postOrderMap(callback) {
this.postOrderMapNode(this.root, callback);
}
postOrderMapNode(node, callback) {
if (node != null) {
this.postOrderMapNode(node.left, callback);
this.postOrderMapNode(node.right, callback);
callback(node.key);
}
}
min() {
return this.minNode(this.root);
}
minNode(node) {
let current = node;
while (current != null && current.left != null) {
current = current.left;
}
return current;
}
max() {
return this.maxNode(this.root);
}
maxNode(node) {
let current = node;
while (current != null && current.right != null) {
current = current.right;
}
return current;
}
search(key) {
return this.searchNode(this.root, key);
}
searchNode(node, key) {
if (node == null) {
return false;
}
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
return this.searchNode(node.left, key);
} else if (
this.compareFn(key, node.key) === Compare.BIGGER_THAN
) {
return this.searchNode(node.right, key);
} else {
return true;
}
}
remove(key) {
this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
if (node == null) {
return null;
}
if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
node.left = this.removeNode(node.left, key);
return node;
} else if (
this.compareFn(key, node.key) === Compare.BIGGER_THAN
) {
node.right = this.removeNode(node.right, key);
return node;
} else {
// 键等于 node.key
// 第一种情况
if (node.left == null && node.right == null) {
node = null;
return node;
}
// 第二种情况 不用担心其中一个节点会比上个节点大
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
// 第三种情况
const target = this.minNode(node.right);
node.key = target.key;
node.right = this.removeNode(node.right, target.key);
return node;
}
}
}
2-1 遍历
- 中序遍历是一种以上行顺序访问 BST 所有节点的遍历方式,也就是以从最小到最大的顺序
访问所有节点。 中序遍历的一种应用就是对树进行排序操作。 - 先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构
化的文档。 - 后序遍历则是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目
录及其子目录中所有文件所占空间的大小。
2-2 移除
九. 二叉堆
二叉堆是一种特殊的二叉树,有以下两个特性。
- 它是一棵完全二叉树,表示树的每一层都有左侧和右侧子节点(除了最后一层的叶节点), 并且最后一层的叶节点尽可能都是左侧子节点,这叫作结构特性。
- 二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值,最大堆允许你快速 导出树的最大值。所有的节点都大于等于(最大堆)或小于等于(最小堆)每个它的子 节点。这叫作堆特性。
1. 最小堆
const Compare = {
less: -1,
bigger: 1,
equ: 0
}
class MinHeap {
heap = [];
getLeftIndex(index) {
return 2 * index + 1;
}
getRightIndex(index) {
return 2 * index + 2;
}
getParentIndex(index) {
if (index === 0) {
return undefined;
}
return Math.floor((index - 1) / 2);
}
insert(value) {
if (value != null) {
this.heap.push(value); // {1}
this.siftUp(this.heap.length - 1); // {2}
return true;
}
return false;
}
compareFn(a, b) {
if (a === b) {
return Compare.equ
}
return a < b ? Compare.less : Compare.bigger
}
siftUp(index) {
let parent = this.getParentIndex(index);
while (
index > 0 &&
this.compareFn(this.heap[parent], this.heap[index]) ===
Compare.bigger
) {
swap(this.heap, parent, index);
index = parent;
parent = this.getParentIndex(index);
}
}
size() {
return this.heap.length;
}
isEmpty() {
return this.size() === 0;
}
findMinimum() {
return this.heap[0];
}
extract() {
if (this.isEmpty()) {
return undefined;
}
if (this.size() === 1) {
return this.heap.shift();
}
const removedValue = this.heap[0];
this.heap[0] = this.heap.pop(); //不破坏结构
this.siftDown(0);
return removedValue;
}
siftDown(index) {
let element = index;
const left = this.getLeftIndex(index);
const right = this.getRightIndex(index);
const size = this.size();
if (
left < size &&
this.compareFn(this.heap[element], this.heap[left]) ===
Compare.bigger
) {
element = left;
//如果大, element 改变了
}
if (
//上面最新的element 与 右边进行对比
right < size &&
this.compareFn(this.heap[element], this.heap[right]) ===
Compare.bigger
) {
element = right;
}
if (index !== element) {
swap(this.heap, index, element);
this.siftDown(element);
}
}
}
function swap(array, a, b) {
const temp = array[a];
array[a] = array[b];
array[b] = temp;
}
var heap = new MinHeap()
2. 最大堆
class MaxHeap extends MinHeap {
constructor() {
super();
}
compareFn(a, b) {
if (a === b) {
return Compare.equ
}
return a > b ? Compare.less : Compare.bigger
}
}
十. 排序算法
1. 冒泡排序
冒泡排序比较所有相邻的两个项,如果第一个比第二个大,则交换它们。元素项向上移动至 正确的顺序,就好像气泡升至表面一样,冒泡排序因此得名。
function bubbleSort(array) {
const {
length
} = array;
for (let i = 0; i < length; i++) {
for (let j = 0; j < length - 1; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
}
}
}
return array;
}
function swap(array, a, b) {
const temp = array[a];
array[a] = array[b];
array[b] = temp;
}
2. 选择排序
选择排序算法是一种原址比较排序算法。选择排序大致的思路是找到数据结构中的最小值并 将其放置在第一位,接着找到第二小的值并将其放在第二位,以此类推。
function selectionSort(array) {
const {
length
} = array;
let indexMin;
for (let i = 0; i < length - 1; i++) {
indexMin = i;
for (let j = i; j < length; j++) {
if (array[indexMin] > array[j]) {
indexMin = j;
}
}
if (i !== indexMin) {
swap(array, i, indexMin);
}
}
return array;
};
3. 插入排序
插入排序每次排一个数组项,以此方式构建最后的排序数组。
function insertionSort(array) {
const {
length
} = array;
let temp;
for (let i = 1; i < length; i++) {
let j = i;
temp = array[i];
while (j > 0 && array[j - 1] > temp) {
array[j] = array[j - 1];
j--;
}
array[j] = temp;
}
return array;
};
4. 归并排序
归并排序是一种分而治之算法。其思想是将原始数组切分成较小的数组,直到每个小数组只 有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。
function mergeSort(array) {
if (array.length > 1) {
const {
length
} = array;
const middle = Math.floor(length / 2);
const left = mergeSort(array.slice(0, middle));
const right = mergeSort(array.slice(middle, length));
array = merge(left, right);
}
return array;
}
function merge(left, right ) {
let i = 0;
let j = 0;
const result = [];
while (i < left.length && j < right.length) {
result.push(
left[i] < right[j] ? left[i++] : right[j++]
);
console.log(result)
//先push ,再++
}
return result.concat(i < left.length ? left.slice(i) : right.slice(j));
}
5. 快速排序
function quickSort(arr){
const {length} = arr
if(length<2){
return arr
}
let base = arr[0]
let min = arr.slice(1).filter(item=>item<=base)
let max = arr.slice(1).filter(item=>item>base)
return quickSort(min).concat([base]).concat(quickSort(max))
}
6. 计数排序
计数排序使用一个用来存储每个元素在原始 数组中出现次数的临时数组。在所有元素都计数完成后,临时数组已排好序并可迭代以构建排序 后的结果数组。
function countingSort(array) {
if (array.length < 2) {
return array;
}
const maxValue = findMaxValue(array);
const counts = new Array(maxValue + 1);
array.forEach(element => {
if (!counts[element]) {
counts[element] = 0;
}
counts[element]++;
});
let sortedIndex = 0;
counts.forEach((count, i) => {
while (count > 0) {
array[sortedIndex++] = i;
count--;
}
});
return array;
}
function findMaxValue(array) {
let max = array[0];
for (let i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
}
countingSort([5,7,4,9,1])
7. 桶排序
桶排序(也被称为箱排序)也是分布式排序算法,它将元素分为不同的桶(较小的数组), 再使用一个简单的排序算法,例如插入排序(用来排序小数组的不错的算法),来对每个桶进行 排序。然后,它将所有的桶合并为结果数组。
function insertSort(arr) {
const {
length
} = arr
let temp; //存 当前这一轮对应索引的元素值
for (let i = 1; i < length; i++) {
temp = arr[i]
let j = i;
while (j > 0 && arr[j - 1] > temp) {
arr[j] = arr[j - 1]
j--
}
arr[j] = temp
}
console.log(arr)
}
function bucketSort(array, bucketSize = 3) {
if (array.length < 2) {
return array;
}
const buckets = createBuckets(array, bucketSize);
//分桶装
return sortBuckets(buckets);
}
function createBuckets(array, bucketSize) {
let minValue = array[0];
let maxValue = array[0];
for (let i = 1; i < array.length; i++) {
if (array[i] < minValue) {
minValue = array[i];
} else if (array[i] > maxValue) {
maxValue = array[i];
}
}//一次循环找到最小最大值
const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1; // 最后一个索引+1
const buckets = [];
for (let i = 0; i < bucketCount; i++) {
buckets[i] = [];
}
for (let i = 0; i < array.length; i++) {
const bucketIndex = Math.floor((array[i] - minValue) / bucketSize);
buckets[bucketIndex].push(array[i]);
}
return buckets;
}
function sortBuckets(buckets) {
const sortedArray = [];
for (let i = 0; i < buckets.length; i++) {
if (buckets[i] != null) {
insertSort(buckets[i]);
sortedArray.push(...buckets[i]);
}
}
return sortedArray;
}
bucketSort([5,4,3,2,6,1,7,10,9,8])
8. 基数排序
基数排序也是一个分布式排序算法,它根据数字的有效位或基数(这也是它为什么叫基数排 序)将整数分布到桶中。基数是基于数组中值的记数制的。
const arr = [35, 2, 26, 2, 5, 8, 34, 1, 56, 99, 33];
const radixSort = (arr = []) => {
const base = 10;
let divider = 1;
let maxVal = 0;
while (divider === 1 || divider <= maxVal) {
const buckets = [...Array(10)].map(() => []);
for (let val of arr) {
buckets[Math.floor((val / divider) % base)].push(val);
maxVal = val > maxVal ? val : maxVal;
}
arr = [].concat(...buckets);
console.log(arr)
divider *= base;
};
return arr;
};
十一. 搜索算法
1. 顺序搜索
顺序或线性搜索是最基本的搜索算法。它的机制是,将每一个数据结构中的元素和我们要找 的元素做比较。顺序搜索是最低效的一种搜索算法。
function sequentialSearch(array, value) {
for (let i = 0; i < array.length; i++) {
if (value===array[i]) {
return i;
}
}
return -1
}
sequentialSearch([3,2,1],2)
2.二分搜索
var Arr = [5, 7, 4, 2, 9];
function binarySearch(find, arr, low, high) {
arr = quickSort(arr);
if (low <= high) {
if (arr[low] == find) {
return low;
}
if (arr[high] == find) {
return high;
}
var mid = Math.ceil((high + low) / 2);
if (arr[mid] == find) {
return mid;
} else if (arr[mid] > find) {
return binarySearch(find, arr, low, mid - 1);
} else {
return binarySearch(find, arr, mid + 1, high);
}
}
return -1;
}
binarySearch(9, Arr, 0, Arr.length - 1)
3.内插搜索
内插搜索是改良版的二分搜索。二分搜索总是检查 mid 位置上的值,而内插搜索可能会根 据要搜索的值检查数组中的不同地方。
var arr = [5, 7, 4, 2, 9];
function InsertionSearch(arr, val, start, end) {
arr = quickSort(arr);
var end = end || arr.length - 1;
var start = start || 0;
if (start <= end && val>=arr[start] && val<=arr[end]) {
var mid = start + Math.floor((val - arr[start]) / (arr[end] - arr[start]) * (end - start))
if (arr[mid] == val) {
return mid;
}
if (arr[mid] > val) {
return InsertionSearch(arr, val, start, mid - 1);
} else {
return InsertionSearch(arr, val, mid + 1, end);
}
}
return -1
}
InsertionSearch(arr, 2)
十二. 随机算法
迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位 置比当前位置小。这样,这个算法可以保证随机过的位置不会再被随机一次
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
swap(array, i, randomIndex);
}
return array;
}
十三.算法设计
1.分而治之
分而 治之是算法设计中的一种方法。它将一个问题分成多个和原问题相似的小问题,递归解决小问题, 再将解决方式合并以解决原来的问题。
分而治之算法可以分成三个部分。
(1) 分解原问题为多个子问题(原问题的多个小实例)。
(2) 解决子问题,用返回解决子问题的方式的递归算法。递归算法的基本情形可以用来解决子 问题。
(3) 组合这些子问题的解决方式,得到原问题的解。
2.动态规划
动态规划(dynamic programming,DP)是一种将复杂问题分解成更小的子问题来解决的优 化技术。
用动态规划解决问题时,要遵循三个重要步骤:
(1) 定义子问题;
(2) 实现要反复执行来解决子问题的部分;
(3) 识别并求解出基线条件。
2-1 背包问题
背包问题是一个组合优化问题。它可以描述如下:给定一个固定大小、能够携重量 W 的背 包,以及一组有价值和重量的物品,找出一个最佳解决方案,使得装入背包的物品总重量不超过 W,且总价值最大。
function knapSack(weights, values, W) {
var n = weights.length;
var f = new Array(n)
f[-1] = new Array(W + 1).fill(0)//第-1个物品,
//负一行的出现可以大大减少了在双层循环的分支判定。
// console.log(f)
for (var i = 0; i < n; i++) {
f[i] = new Array(W).fill(0)
for (var j = 0; j <= W; j++) {
if (j < weights[i]) {
f[i][j] = f[i - 1][j]
} else {
f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - weights[i]] + values[i]); //case 3
}//即使i = 0 也不怕,i-1 为-1 有-1行不怕
}
}
return f[n - 1][W]
}
2-2 最长公共子序列
找出两个字符 串序列的最长子序列的长度。最长子序列是指,在两个字符串序列中以相同顺序出现,但不要求 连续(非字符串子串)的字符串序列。
function LCS(str1, str2) {
var m = str1.length
var n = str2.length
var dp = [new Array(n + 1).fill(0)] //第一行全是0
for (var i = 1; i <= m; i++) { //一共有m+1行
dp[i] = [0] //第一列全是0
for (var j = 1; j <= n; j++) { //一共有n+1列
if (str1[i - 1] === str2[j - 1]) {
//注意这里,str1的第一个字符是在第二列中,因此要减1,str2同理
dp[i][j] = dp[i - 1][j - 1] + 1 //对角+1
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
}
}
}
return dp[m][n];
}
console.log(LCS("abcadf", "acbaed"))
3. 贪心算法
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
function tanx(capacity, weights, values) {
var list = [];
for (var i = 0, len = weights.length; i < len; i++) {
list.push({
num: i + 1, //第几件商品
w: weights[i], //重量
v: values[i],
rate: values[i] / weights[i]
});
}
list.sort(function (a, b) {
return b.rate-a.rate
});
// console.log(list)
var selects = [];
var total = 0;
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i];
if (item['w'] <= capacity) {
selects.push({
num: item.num,
rate: 1, //完整的商品记录为1
v: item.v,
w: item.w
});
total = total + item.v;
capacity = capacity - item.w;
} else if (capacity > 0) {
//选取不完整的商品
var rate = capacity / item['w'];
var v = item.v * rate;
selects.push({
num: item.num,
rate: rate,
v: item.v * rate,
w: item.w * rate
});
total = total + v;
break;
} else {
break;
}
}
return {
selects,
total
}
}
4.回溯算法
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。
题目
给定一个 二维字符网格 board 和一个字符串单词 word
如果 word 存在于网格中,返回 true ;否则,返回 false
单词必须按照字母顺序,通过相邻的单元格内的字母构成
二维数组: board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]],
目标: word = “ABCCED”
function exist(board, word) {
let row = board.length;
let col = board[0].length;
for (let i = 0; i < row; i++) {
for (let j = 0; j < col; j++) {
const ret = find(i, j, 0);
if (ret) {
return ret;
}
}
}
return false;
function find(r, c, cur) {
if (r >= row || r < 0) return false;
if (c >= col || c < 0) return false;
if (board[r][c] !== word[cur]) return false;
if (cur == word.length - 1) return true;
let letter = board[r][c];
board[r][c] = null;
const ret =
find(r - 1, c, cur + 1) ||
find(r + 1, c, cur + 1) ||
find(r, c - 1, cur + 1) ||
find(r, c + 1, cur + 1);
//用null做标记是避免重复查找
board[r][c] = letter;
return ret;
}
};
十四. 算法复杂度
O 表示法用于描述算法的性能和复杂程 度。O 表示法将算法按照消耗的时间进行分类,依据随输入增大所需要的空间/内存。
- O(1)
function inc(n){
return ++n;
}
- O(n)
function search(arr,value){
for(let i =0;i<arr.length;i++){
if(arr[i]===value){
return i
}
}
return -1
}
- O(n2 )
function bubbleSort(array) {
const {
length
} = array
for (let i = 0; i < length; i++) {
for (let j = 0; j < length - 1-i; j++) {
if (array[j] > array[j + 1]) {
//交换位置的方法
swap(array,j,j+1)
}
}
}
console.log(array)
}