文章目录
一个月前再图书馆借了这本书,原因是因为本人没有系统学过数据结构,算法也没有完全正规学过,因此想看看这些基础书补充一下基础知识。
本书作为一本入门书先看看,但是我网评一般,自己在看的时候书内确实有写错误,但不妨碍对基础知识的学习。
数据结构
数组
数组比较简单我们比较常用的数据结构,这里不做过多介绍,详细查看MDN
列表
列表也是常用的数据结构,例如待办清单、购物清单、十佳榜单等。针对于列表中保存数据不是太多。当不需要在一个很长的列表中查找元素,或者对其进行排序时,列表显得尤为有用,反之。如果数据结构非常复杂列表的作用就不大了。
列表的实现:
function List() {
this.listSize = 0;//列表的元素个数
this.pos = 0;//列表的当前位置
this.dataStore = [];//列表的数据存储
this.clear = clear;//清空列表中的所有元素
this.find = find;//查找列表中元素
this.toString = toString;//返回列表的字符串形式
this.insert = insert;//在现有元素后插入新元素
this.append = append;//在列表的末尾添加新元素
this.remove = remove;//从列表中删除指定元素
this.front = front;//当前列表的当前位置移动到第一个元素
this.end = end;//当前列表的当前位置移动到最后一个元素
this.prev = prev;//当前位置前移一位
this.next = next;//当前位置后移一位
this.length = length;//返回列表中元素的个数
this.currPos = currPos;//返回列表当前位置
this.moveTo = moveTo;//将当前位置移动到指定位置
this.getElement = getElement;//返回当前位置的元素
}
function append(element) {
this.dataStore[this.listSize++] = element;
}
function find(element) {
for (var i = 0; i < this.dataStore.length; ++i) {
if (this.dataStore[i] == element) {
return i;
}
}
return -1;
}
function remove(element) {
var foundAt = this.find(element);
if (foundAt > -1) {
this.dataStore.splice(foundAt,1);
--this.listSize;
return true;
}
return false;
}
function toString() {
return this.dataStore;
}
栈
栈是和列表类似的一种数据,它可以解决计算机世界里的很多问题,栈是一种高效的数据结构,因为数据只能从栈顶添加或删除,所以这样的操作很快,而且容易实现。栈的使用遍布程序预验实现的方方面面,从表达式求值到处理函数调用。
栈是后入先出(LIFO,last-lin-first-out)
栈的实现
function Stack() {
this.dataStore = [];
this.top = 0;
this.push = push;//入栈
this.pop = pop;//出栈
this.peek = peek;
this.clear = clear;
this.length = length;
}
//压入栈
function push(element) {
this.dataStore[this.top++] = element;
}
//弹出栈
function pop() {
return this.dataStore[--this.top];
}
//栈顶元素
function peek() {
// if (this.dataStore[this.top - 1] == undefined) {
// return "none"
// }
return this.dataStore[this.top - 1];
}
//清空栈
function clear() {
this.top = 0;
}
//栈元素个数
function length() {
return this.top;
}
栈的用途
- 数制间的相互转换
/**
* 数制转换函数
* @param {Number} 转换数字
* @param {Number} base 数制转换基数
* @returns
*/
function mulBase(num, base) {
var s = new Stack();
do {
s.push(num % base);
num = Math.floor((num /= base));
} while (num > 0);
var converted = "";
while (s.length() > 0) {
converted += s.pop();
}
return converted;
}
- 回文
/**
* 判断是否为回文
* @param {String} word 要判断的字符串
* @returns
*/
function isPalindrome(word) {
var s = new Stack();
for (var i = 0; i < word.length; ++i) {
s.push(word[i]);
}
var word = "";
while (s.length() > 0) {
rword += s.pop();
}
if (word == rword) {
return true;
} else {
return false;
}
}
队列
队列是一种列表,不同的是队列只能在队尾插入元素。在队首删除元素。队列用于存储按照顺序排列的数据,先进先出(FIFO,first-in-first-out),这点和栈不同。
队列的实现
function Queue() {
this.dataStore = [];
this.enqueue = enqueue;//入队
this.dequeue = dequeue;//出队
this.front = front;
this.back = back;
this.toString = toString;
this.empty = empty;
}
//向队末尾添加一个元素
function enqueue(element) {
this.dataStore.push(element);
}
//删除队首的元素
function dequeue() {
return this.dataStore.shift();
}
function front() {
//读取队首和队末的元素
return this.dataStore[0];
}
function back() {
读取队首和队末的元素
return this.dataStore[this.dataStore.length - 1];
}
//显示队列内的所有元素
function toString() {
var retStr = "";
for (var i = 0; i < this.dataStore.length; ++i) {
retStr += this.dataStore[i] + "\n";
}
return retStr;
}
//队列是否为空
function empty() {
if (this.dataStore.length == 0) {
return true;
} else {
return false;
}
}
队列的应用
- 方块舞舞伴分配
/**
* 分配舞伴方法
* @param {Queue} males 男舞伴队列
* @param {Queue} females 女舞伴队列
*/
function dance(males, females) {
console.log("这组舞伴是:");
while (!females.empty() && !males.empty()) {
person = females.dequeue();
console.log("女舞者是" + person.name);
person = males.dequeue();
console.log("男舞者是" + person.name);
}
}
- 使用队列进行排序
假设有一个两位数数组,先根据个位排序分组,然后将所有值合并为一个数组,然后按照十位排序分组,再将数据合并为一个数据,得到排好序的数组。
优先队列
说一个现实场景,比如医院急诊科的候诊室,就是一个采用优先队列的例子。当病人进入候诊室,分诊会评估病情的严重程度,然后给一个优先级代码。高优先级的患者优先于低优先级者就医,同样优先级的患者按照优先来服务的顺序就医。
对插入队列的对象增加一个属性code作为判断优先级依据,数值越小优先级越高。
function Patient(name, code) {
this.name = name;
this.code = code;
}
function dequeue() {
var priority = this.dataStore[0].code;
for (var i = 1; i < this.dataStore.length; ++i) {
if (this.dataStore[i].code < priority) {
priority = i;
}
}
return this.dataStore.splice(priority, 1);
}
链表
这个数据结构也是我读这本书的原因之一,该数据和数组相比,无需开辟连续的内存空间。在其他语言中,数组开辟内存是固定的,而链表不需要存在固定的控件中。
链表的实现
- 普通链表
function Node(element) {
this.element = element;
this.next = null;
}
function LList() {
this.head = new Node("head");
this.find = find;//查找节点
this.insert = insert;//再指定节点后插入节点
this.remove = remove;//删除节点
this.display = display;//展示链表
}
function remove(item) {
var prevNode = this.findPrevious(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
function findPrevious(item) {
var currNode = this.head;
while (!(currNode.next == null) && currNode.next.element != item) {
currNode = currNode.next;
}
return currNode;
}
function find(item) {
var currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
}
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
function display() {
var currNode = this.head;
while (!(currNode.next == null)) {
print(currNode.next.element);
currNode = currNode.next;
}
}
- 双向链表
function Node(element) {
this.element = element;
this.next = null;//向后指针
this.previous = null;//向前指针
}
function LList() {
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.remove = remove;
this.findLast = findLast;
this.dispReverse = dispReverse;
}
function dispReverse() {
var currNode = this.head;
currNode = this.findLast();
while (!(currNode.previous == null)) {
print(currNode.element);
currNode = currNode.previous;
}
}
function findLast() {
var currNode = this.head;
while (!(currNode.next == null)) {
currNode = currNode.next;
}
return currNode;
}
function remove(item) {
var currNode = this.find(item);
if (!(currNode.next == null)) {
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
}
//findPrevious 没用了, 注释掉
/*function findPrevious(item) {
var currNode = this.head;
while (!(currNode.next == null) &&
(currNode.next.element != item)) {
currNode = currNode.next;
} return currNode;
}*/
function display() {
var currNode = this.head;
while (!(currNode.next == null)) {
print(currNode.next.element);
currNode = currNode.next;
}
}
function find(item) {
var currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
}
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
}
- 循环链表
function LList() {
this.head = new Node("head");
this.head.next = this.head;
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
function display() {
var currNode = this.head;
while (!(currNode.next == null) && !(currNode.next.element == "head")) {
print(currNode.next.element);
currNode = currNode.next;
}
}
链表的应用
前几天看面试题中有一道是,查找链表倒数第n个节点
-
解法一
遍历链表,记录链表的长度total,再次遍历链表,第total - N - 1个节点就是查找结果,需要遍历两次链表 -
解法二
先翻转,再计算
/**
* @param head: The first node of linked list.
*@param n: An integer.
* @return: Nth to last node of a singly linked list.
*/
ListNode nthToLast(ListNode head, int n) {
if(head == null){
return null;
}
head = reverse(head);
int index = 1;
while(head.next != null){
if(index++ == n){
break;
}
head = head.next;
}
return head;
}
/**
* 原地翻转
*/
private ListNode reverse(ListNode head){
ListNode prev = null;
while(head != null){
ListNode tmp = head.next;
head.next = prev;
prev = head;
head = tmp;
}
return prev;
}
- 解法三
双指针,第一个指针先走n个,然后第一个指针和第二个指针同时往右遍历,当第一个指针为null时,第二个指针就为倒数第n个元素
/**
* @param head: The first node of linked list.
* @param n: An integer.
* @return: Nth to last node of a singly linked list.
*/
ListNode nthToLast(ListNode head, int n) {
if(head == null){
return null;
}
ListNode dummy = head;
for(int i = 0; i < n; i++){
if(head == null){
return null;
}
head = head.next;
}
ListNode pre = dummy;
while(head != null){
head = head.next;
pre = pre.next;
}
return pre;
}
字典
字典是一种以键-值对
形式存储的数据结构
字典的实现
function Dictionary() {
this.add = add;
this.datastore = new Array();
this.find = find;
this.remove = remove;
this.showAll = showAll;
this.showAll2 = showAll2;
this.count = count;
}
function add(key, value) {
this.datastore[key] = value;
}
function find(key) {
return this.datastore[key];
}
function remove(key) {
delete this.datastore[key]; //delete是Object类的一部分,使用对键删掉键和与其无关的值。
}
function showAll() {
for (var key in Object.keys(this.datastore)) {
//调用Object的keys()方法可以返回传入参数中储存的所有键
console.log(key + " -> " + this.datastore[key]);
}
}
function showAll2() {
for (var key in Object.keys(this.datastore).sort()) {
//显示字典的内容时,结果是有序的。使用Object.keys()函数。
console.log(key + " -> " + this.datastore[key]);
}
}
function count() {
var n = 0;
for (var key in Object.keys(this.datastore)) {
++n;
}
console.log(n);
}
字典的应用
可以用该数据结构记录人员电话簿,以人员名称为KEY,以电话号码为VALUE
散列
散列是一种常用的数据存储技术,散列后的数据可以快速插入或取出。散列使用的数据结构叫做散列表。在散列上插入、删除和取用数据都非常快,但是对查找操作来说却效率低下,比如查找一组数据中最大值和最小值。这些操作得求助于其他数据结构,二叉查找树是一个很好的选择。
个人对于散列的理解就是java中的hash
散列的实现
- 简单散列函数做散列
function HashTable() {
this.table = new Array(137);
this.simpleHash = simpleHash;
this.showDistro = showDistro;
this.put = put;
//this.get = get;
}
function put(data) {
var pos = this.simpleHash(data);
this.table[pos] = data;
}
function simpleHash(data) {
var total = 0;
for (var i = 0; i < data.length; ++i) {
total += data.charCodeAt(i);
}
return total % this.table.length;
}
function showDistro() {
var n = 0;
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i] != undefined) {
print(i + ": " + this.table[i]);
}
}
}
- 更好的散列函数
function HashTable() {
this.table = new Array(137);
this.simpleHash = simpleHash;
this.betterHash = betterHash;
this.showDistro = showDistro;
this.put = put;
this.get = get;
}
function put(key, data) {
var pos = this.betterHash(key);
this.table[pos] = data;
}
function get(key) {
return this.table[this.betterHash(key)];
}
function showDistro() {
var n = 0;
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i] != undefined) {
print(i + ": " + this.table[i]);
}
}
}
function simpleHash(data) {
var total = 0;
for (var i = 0; i < data.length; ++i) {
total += data.charCodeAt(i);
}
print("Hash value: " + data + " -> " + total);
return total % this.table.length;
}
function betterHash(string) {
const H = 37;
var total = 0;
for (var i = 0; i < string.length; ++i) {
total += H * total + string.charCodeAt(i);
}
total = total % this.table.length;
if (total < 0) {
total += this.table.length - 1;
}
return parseInt(total);
}
碰撞处理
当散列函数对于多个输入产生同样的输出时, 就产生了碰撞。
开链法
在创建存储散列过的键值的数组时, 通过调用一个函数创建一个新
的空数组, 然后将该数组赋给散列表里的每个数组元素
// 为散列表每个位置创建一个数组
function buildChains() {
for (var i = 0; i < this.table.length; ++i) {
this.table[i] = new Array();
}
}
function showDistro() {
var n = 0;
for (var i = 0; i < this.table.length; ++i) {
if (this.table[i][0] != undefined) {
print(i + ": " + this.table[i]);
}
}
}
function put(key, data) {
var pos = this.betterHash(key);
var index = 0;
while (this.table[pos][index] != undefined) {
index += 2;
}
this.table[pos][index] = key;
this.table[pos][index + 1] = data;
}
function get(key) {
var index = 0;
var pos = this.betterHash(key);
while (this.table[pos][index] != key) {
index += 2;
}
return this.table[pos][index + 1];
}
线性探测法
线性探测法隶属于一种更一般化的散列技术: 开放寻址散列。 当发生碰撞时, 线性探测法检查散列表中的下一个位置是否为空。 如果为空,就将数据存入该位置; 如果不为空, 则继续检查下一个位置, 直到找到一个空的位置为止。 该技术是基于这样一个事实: 每个散列表都会有很多空的单元格, 可以使用它们来存储数据。
当存储数据使用的数组特别大时, 选择线性探测法要比开链法好。 这里有一个公式, 常常可以帮助我们选择使用哪种碰撞解决办法: 如果数组的大小是待存储数据个数的 1.5 倍,那么使用开链法; 如果数组的大小是待存储数据的两倍及两倍以上时, 那么使用线性探测法。
function HashTable() {
this.table = new Array(137);
this.values = [];
this.simpleHash = simpleHash;
this.betterHash = betterHash;
this.showDistro = showDistro;
this.put = put;
this.get = get;
}
function put(key, data) {
var pos = this.betterHash(key);
if (this.table[pos] == undefined) {
this.table[pos] = key;
this.values[pos] = data;
} else {
while (this.table[pos] != undefined) {
pos++;
}
this.table[pos] = key;
this.values[pos] = data;
}
}
function get(key) {
var hash = -1;
hash = this.betterHash(key);
if (hash > -1) {
for (var i = hash; this.table[hash] != undefined; i++) {
if (this.table[hash] == key) {
return this.values[hash];
}
}
}
return undefined;
}
集合
集合(set) 是一种包含不同元素的数据结构。 集合中的元素称为成员。 集合的两个最重要特性是:
- 集合中的成员是无序的
- 集合中不允许相同成员存在
集合的实现
function Set() {
this.dataStore = [];
this.add = add; //添加项
this.remove = remove; //移除项
this.size = size; //返回元素数量
this.union = union; //并集
this.intersect = intersect; //交集
this.subset = subset; //是否为子集
this.difference = difference; //补集
this.contains = contains;
this.show = show;
}
function contains(data) {
if (this.dataStore.indexOf(data) > -1) {
return true;
} else {
return false;
}
}
function add(data) {
if (this.dataStore.indexOf(data) < 0) {
this.dataStore.push(data);
return true;
} else {
return false;
}
}
function remove(data) {
var pos = this.dataStore.indexOf(data);
if (pos > -1) {
this.dataStore.splice(pos, 1);
return true;
} else {
return false;
}
}
function union(set) {
var tempSet = new Set();
for (var i = 0; i < this.dataStore.length; ++i) {
tempSet.add(this.dataStore[i]);
}
for (var i = 0; i < set.dataStore.length; ++i) {
if (!tempSet.contains(set.dataStore[i])) {
tempSet.dataStore.push(set.dataStore[i]);
}
}
return tempSet;
}
function intersect(set) {
var tempSet = new Set();
for (var i = 0; i < this.dataStore.length; ++i) {
if (set.contains(this.dataStore[i])) {
tempSet.add(this.dataStore[i]);
}
}
return tempSet;
}
function subset(set) {
if (this.size() > set.size()) {
return false;
} else {
for (var member in this.dataStore) {
if (!set.contains(member)) {
return false;
}
}
}
return true;
}
function size() {
return this.dataStore.length;
}
function difference(set) {
var tempSet = new Set();
for (var i = 0; i < this.dataStore.length; ++i) {
if (!set.contains(this.dataStore[i])) {
tempSet.add(this.dataStore[i]);
}
}
return tempSet;
}
二叉树和二叉查找树
树是计算机科学中经常用到的一种数据结构。 树是一种非线性的数据结构, 以分层的方式存储数据。 树被用来存储具有层级关系的数据, 比如文件系统中的文件; 树还被用来存储有序列表。
二叉树每个节点的子节点不允许超过两个,一个父节点的两个子节点分别称为左节点和右节点。
二叉查找树是一种特殊的二叉树, 相对较小的值保存在左节点中, 较大的值保存在右节点中。 这一特性使得查找的效率很高, 对于数值型和非数值型的数据, 比如单词和字符串, 都是如此。
二叉查找树的实现
function Node(data, left, right) {
this.data = data;
this.left = left;
this.right = right;
this.show = show;
}
function show() {
return this.data;
}
function BST() {
this.root = null;
this.insert = insert;
this.inOrder = inOrder;
}
function insert(data) {
var n = new Node(data, null, null);
if (this.root == null) {
this.root = n;
} else {
var current = this.root;
var parent;
while (true) {
parent = current;
if (data < current.data) {
current = current.left;
if (current == null) {
parent.left = n;
break;
}
} else {
current = current.right;
if (current == null) {
parent.right = n;
break;
}
}
}
}
}
遍历二叉查找树
中序
中序遍历的访问路径
function inOrder(node) {
if (!(node == null)) {
inOrder(node.left);
putstr(node.show() + " ");
inOrder(node.right);
}
}
中序可以用于排序数组
压入树的数据:23、45、16、37、3、99、22
遍历后的数据:3、16、22、23、37、45、99
先序
先序遍历的访问路径
function preOrder(node) {
if (!(node == null)) {
putstr(node.show() + " ");
preOrder(node.left);
preOrder(node.right);
}
}
压入树的数据:23、45、16、37、3、99、22
遍历后的数据:23、16、3、22、45、37、99
后序
后序遍历的访问路径
function postOrder(node) {
if (!(node == null)) {
postOrder(node.left);
postOrder(node.right);
putstr(node.show() + " ");
}
}
压入树的数据:23、45、16、37、3、99、22
遍历后的数据:3、22、16、37、99、45、23
查找最大值最小值
查找最大值
function getMax() {
var current = this.root;
while (!(current.right == null)) {
current = current.right;
}
return current.data;
}
查找最小值
function getMin() {
var current = this.root;
while (!(current.left == null)) {
current = current.left;
}
return current.data;
}
查找给定值
function find(data) {
var current = this.root;
while (current != null) {
if (current.data == data) {
return current;
} else if (data < current.data) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
二叉查找树上删除节点
function remove(data) {
root = removeNode(this.root, data);
}
function removeNode(node, data) {
if (node == null) {
return null;
}
if (data == node.data) {
// 没有子节点的节点
if (node.left == null && node.right == null) {
return null;
} // 没有左子节点的节点
if (node.left == null) {
return node.right;
} // 没有右子节点的节点
if (node.right == null) {
return node.left;
} // 有两个子节点的节点
var tempNode = getSmallest(node.right);
node.data = tempNode.data;
node.right = removeNode(node.right, tempNode.data);
return node;
} else if (data < node.data) {
node.left = removeNode(node.left, data);
return node;
} else {
node.right = removeNode(node.right, data);
return node;
}
}
算法
排序算法
排序算法查看之前的一个微信公众号推文
检索算法
在列表中查找数据有两种方式: 顺序查找和二分查找。 顺序查找适用于元素随机排列的列表; 二分查找适用于元素已排序的列表。 二分查找效率更高, 但是你必须在进行查找之前花费额外的时间将列表中的元素排序。
顺序查找
最简单的方法就是从列表的第一个元素开始对列表元素逐个进行判断, 直到找到了想要的结果, 或者直到列表结尾也没有找到。 这种方法称为顺序查找, 有时也被称为线性查找
。
function seqSearch(arr, data) {
for (var i = 0; i < arr.length; ++i) {
if (arr[i] == data) {
return i;
}
}
return -1;
}
二分法查找
如果你要查找的数据是有序的, 二分查找算法比顺序查找算法更高效。要理解二分查找算法的原理, 可以想象一下你在玩一个猜数字游戏, 这个数字位于 1~100 之间, 而要猜的数字是由你的朋友来选定的。
function binSearch(arr, data) {
var upperBound = arr.length - 1;
var lowerBound = 0;
while (lowerBound <= upperBound) {
var mid = Math.floor((upperBound + lowerBound) / 2);
print(" 当前的中点: " + mid);
if (arr[mid] < data) {
lowerBound = mid + 1;
} else if (arr[mid] > data) {
upperBound = mid - 1;
} else {
return mid;
}
}
return -1;
}
高级算法
动态规划
动态规划有时被认为是一种与递归相反的技术。 递归是从顶部开始将问题分解, 通过解决掉所有分解出小问题的方式, 来解决整个问题。 动态规划解决方案从底部开始解决问题, 将所有小问题解决掉, 然后合并成一个整体解决方案, 从而解决掉整个大问题。
计算斐波那契数列
递归方案:
function recurFib(n) {
if (n < 2) {
return n;
} else {
return recurFib(n - 1) + recurFib(n - 2);
}
}
动态规划方案:
function dynFib(n) {
var val = [];
for (var i = 0; i <= n; ++i) {
val[i] = 0;
}
if (n == 1 || n == 2) {
return 1;
} else {
val[1] = 1;
val[2] = 2;
for (var i = 3; i <= n; ++i) {
val[i] = val[i - 1] + val[i - 2];
}
return val[n - 1];
}
}
这两个方案用时基本相等
function iterFib(n) {
var last = 1;
var nextLast = 1;
var result = 1;
for (var i = 2; i < n; ++i) {
result = last + nextLast;
nextLast = last;
last = result;
}
return result;
}
寻找最长公共子串
function lcs(word1, word2) {
var max = 0;
var index = 0;
var lcsarr = new Array(word1.length + 1);
// 声明一个二维数组,并将元素初始化为0
for (var i = 0; i <= word1.length + 1; ++i) {
lcsarr[i] = new Array(word2.length + 1);
for (var j = 0; j <= word2.length + 1; ++j) {
lcsarr[i][j] = 0;
}
}
// 第二部分构建了用于保存字符匹配记录的表。 数组的第一个元素总是被设置为 0。
// 如果两个字符串相应位置的字符进行了匹配,
// 当前数组元素的值将被设置为前一次循环中数组元素保存的值加 1
for (var i = 0; i <= word1.length; ++i) {
for (var j = 0; j <= word2.length; ++j) {
if (i == 0 || j == 0) {
lcsarr[i][j] = 0;
} else {
if (word1[i - 1] == word2[j - 1]) {
lcsarr[i][j] = lcsarr[i - 1][j - 1] + 1;
} else {
lcsarr[i][j] = 0;
}
}
if (max < lcsarr[i][j]) {
max = lcsarr[i][j];
index = i;
}
}
}
console.log(lcsarr);
// 确认从哪里开始构建这个最长公共子串
var str = "";
if (max == 0) {
return "";
} else {
for (var i = index - max,j=0; j < max; ++j) {
str += word1[i++];
}
return str;
}
}
背包问题
如果在我们例子中的保险箱中有 5 件物品, 它们的尺寸分别是 3、 4、 7、8、 9, 而它们的价值分别是 4、 5、 10、 11、 13, 且背包的容积为 16,那么恰当的解决方案是选取第三件物品和第五件物品, 他们的总尺寸是 16, 总价值是 23。
- 递归解决方案
function max(a, b) {
return a > b ? a : b;
}
function knapsack(capacity, size, value, n) {
if (n == 0 || capacity == 0) {
return 0;
}
if (size[n - 1] > capacity) {
return knapsack(capacity, size, value, n - 1);
} else {
return max(
value[n - 1] + knapsack(capacity - size[n - 1], size, value, n - 1),
knapsack(capacity, size, value, n - 1)
);
}
}
- 动态规划解决方案
function max(a, b) {
return a > b ? a : b;
}
function dKnapsack(capacity, size, value, n) {
var K = [];
for (var i = 0; i <= n; i++) {
K[i] = [];
}
for (var i = 0; i <= n; i++) {
for (var w = 0; w <= capacity; w++) {
if (i == 0 || w == 0) {
K[i][w] = 0;
} else if (size[i - 1] <= w) {
K[i][w] = max(value[i - 1] + K[i - 1][w - size[i - 1]], K[i - 1][w]);
} else {
K[i][w] = K[i - 1][w];
}
}
}
console.log(K)
return K[n][capacity];
}
var value = [4, 5, 10, 11, 13];
var size = [3, 4, 7, 8, 9];
var capacity = 16;
var n = 5;
console.log(dKnapsack(capacity, size, value, n));
贪心算法
贪心算法是一种以寻找“优质解” 为手段从而达成整体解决方案的算法。 这些优质的解决方案称为局部最优解, 将有希望得到正确的最终解决方案, 也称为全局最优解。“贪心”这个术语来自于这些算法无论如何总是选择当前的最优解这个事实。
找零问题
贪心算法的一个经典案例是找零问题。 你从商店购买了一些商品, 找零 63美分, 店员要怎样给你这些零钱呢? 如果店员根据贪心算法来找零的话, 他会给你两个 25 美分、 一个10 美分和三个 1 美分。 在没有使用 50 美分的情况下这是最少的硬币数量。
function makeChange(origAmt, coins) {
var remainAmt = 0;
if (origAmt % 0.25 < origAmt) {
coins[3] = parseInt(origAmt / 0.25);
remainAmt = origAmt % 0.25;
origAmt = remainAmt;
}
if (origAmt % 0.1 < origAmt) {
coins[2] = parseInt(origAmt / 0.1);
remainAmt = origAmt % 0.1;
origAmt = remainAmt;
}
if (origAmt % 0.05 < origAmt) {
coins[1] = parseInt(origAmt / 0.05);
remainAmt = origAmt % 0.05;
origAmt = remainAmt;
}
coins[0] = parseInt(origAmt / 0.01);
}
function showChange(coins) {
if (coins[3] > 0) {
console.log("25 美分的数量 - " + coins[3] + " - " + coins[3] * 0.25);
}
if (coins[2] > 0) {
console.log("10 美分的数量 - " + coins[2] + " - " + coins[2] * 0.1);
}
if (coins[1] > 0) {
console.log("5 美分的数量 - " + coins[1] + " - " + coins[1] * 0.05);
}
if (coins[0] > 0) {
console.log("1 美分的数量 - " + coins[0] + " - " + coins[0] * 0.01);
}
}
var origAmt = 0.63;
var coins = [];
console.log(coins)
makeChange(origAmt, coins);
showChange(coins);
背包问题
function ksack(values, weights, capacity) {
var load = 0;
var i = 0;
var w = 0;
while (load < capacity && i < 4) {
if (weights[i] <= capacity - load) {
w += values[i];
load += weights[i];
} else {
var r = (capacity - load) / weights[i];
w += r * values[i];
load += weights[i];
}
++i;
}
return w;
}
var items = ["A", "B", "C", "D"];
var values = [50, 140, 60, 60];
var weights = [5, 20, 10, 12];
var capacity = 30;
console.log(ksack(values, weights, capacity)); // 显示 220