数据结构
数组(Array)
一个存储元素的线性的连续存储结构,元素可以通过索引来任意存取
栈(Stack)
栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,具有后入先出的特点(LIFO)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function Stack ( ) { this .dataStore = []; this .top = 0 ; this .push = function (ele ) { this .dataStore[this .top++] = ele; }; this .pop = function ( ) { return this .dataStore[--this .top]; }; this .peek = function ( ) { return this .dataStore[this .top-1 ]; }; this .clear = function ( ) { this .top = 0 ; } this .length = function ( ) { return this .top; } }
解决问题:
数制间的相互转换
1 2 3 4 5 6 7 8 9 10 11 12 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; }
判断回文数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function isPalindrome (word ) { var s = new Stack(); for (var i = 0 ; i < word.length; ++i) { s.push(word[i]); } var rword = "" ; while (s.length() > 0 ) { rword += s.pop(); } if (word == rword) { return true ; } else { return false ; } }
队列(Queue)
队列是一种特殊的列表,不同的是队列只能在队尾插入元素,在队首删除元素,具有先进先出的特点(FIFO)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function Queue ( ) { this .dataStore = []; this .enqueue = function (element ) { this .dataStore.push(element); }; this .dequeue = function ( ) { return this .dataStore.shift(); }; this .front = function ( ) { return this .dataStore[0 ]; }; this .back = function ( ) { return this .dataStore[this .dataStore.length-1 ]; }; this .toString = function ( ) { var retStr = "" ; for (var i = 0 ; i < this .dataStore.length; ++i) { retStr += this .dataStore[i] + "\n" ; } return retStr; }; this .empty = function ( ) { if (this .dataStore.length == 0 ) { return true ; } else { return false ; } }; }
优先队列
在一般情况下,从队列中删除的元素,一定是率先入队的元素。但是也有一些使用队列的应用,在删除元素时不必遵守先进先出的约定,比如优先队列
1 2 3 4 5 6 7 8 9 10 11 12 13 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 ); }
链表(LinkList)
数组不总是组织数据的最佳数据结构:
在很多编程语言中,数组的长度是固定的,所以当数组已被填满时,再要加入新的元素就会非常困难
在数组中,添加和删除元素也很麻烦,因为需要将数组中的其他元素向前或向后平移,以反映数组刚刚进行了添加或删除操作
JavaScript中数组被实现成了对象,效率很低
链表是由一组节点组成的集合。每个节点都使用一个对象的引用指向它的后继。指向另一个节点的引用叫做链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 function Node (element ) { this .element = element; this .next = null ; } function LList ( ) { this .head = new Node("head" ); this .find = function (item ) { var currNode = this .head; while (currNode.element != item) { currNode = currNode.next; } return currNode; }; this .insert = function (newElement, item ) { var newNode = new Node(newElement); var current = this .find(item); newNode.next = current.next; current.next = newNode; }; this .remove = function (item ) { var prevNode = this .findPrevious(item); if (!(prevNode.next == null )) { prevNode.next = prevNode.next.next; } }; this .display = function ( ) { var currNode = this .head; while (!(currNode.next == null )) { print(currNode.next.element); currNode = currNode.next; } }; }
还有
字典(Dictionary)
字典是一种以键值对形式存储数据的数据结构,就像电话号码簿里的名字和电话号码一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Dictionary ( ) { this .datastore = new Array (); this .add = function (key, value ) { this .datastore[key] = value; }; this .find = function (key ) { return this .datastore[key]; }; this .remove = function (key ) { delete this .datastore[key]; }; this .showAll = function ( ) { for (var key in Object .keys(this .datastore)) { print(key + " -> " + this .datastore[key]); } }; }
散列(HashTable)
散列是一种常用的数据存储技术,每个键值映射为一个唯一的数组索引,散列后的数据可以快速地插入或取用非常快
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function HashTable ( ) { this .table = new Array (137 ); this .betterHash = function (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); }; this .showDistro = function ( ) { var n = 0 ; for (var i = 0 ; i < this .table.length; ++i) { if (this .table[i] != undefined ) { print(i + ": " + this .table[i]); } } }; this .put = function (data ) { var pos = this .betterHash(data); this .table[pos] = data; }; this .get = function (key ) { return this .table[this .betterHash(key)]; } }
碰撞处理:开链法
在创建存储散列过的键值的数组时,通过调用一个函数创建一个新的空数组,然后将该数组赋给散列表里的每个数组元素。这样就创建了一个二维数组,存储多个键
挂载新的函数在散列上:
1 2 3 4 5 function buildChains ( ) { for (var i = 0 ; i < this .table.length; ++i) { this .table[i] = new Array (); } }
put
方法将键值散列,散列后的值对应数组中的一个位置,先尝试将数据放到该位置上的数组中的第一个单元格,如果该单元格里已经有数据了,put
方法会搜索下一个位置,直到找到能放置数据的单元格,并把数据存储进去
get
方法先对键值散列,根据散列后的值找到散列表中相应的位置,然后搜索该位置上的链,直到找到键值。如果找到,就将紧跟在键值后面的数据返回
碰撞处理:线性探索法
当发生碰撞时,线性探测法检查散列表中的下一个位置是否为空。如果为空,就将数据存入该位置;如果不为空,则继续检查下一个位置,直到找到一个空的位置为止。该技术是基于这样一个事实:每个散列表都会有很多空的单元格,可以使用它们来存储数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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)
集合是一种包含不同元素的数据结构。集合中的元素称为成员。集合的两个最重 要特性是:首先,集合中的成员是无序的;其次,集合中不允许相同成员存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 function Set ( ) { this .dataStore = []; this .add = function (data ) { if (this .dataStore.indexOf(data) < 0 ) { this .dataStore.push(data); return true ; } else { return false ; } }; this .remove = function (data ) { var pos = this .dataStore.indexOf(data); if (pos > -1 ) { this .dataStore.splice(pos, 1 ); return true ; } else { return false ; } }; this .size = size; this .union = function (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; }; this .intersect = function (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; }; this .subset = function (set ) { if (this .size() > set.size()) { this .difference = difference; return false ; } else { for (var member in this .dataStore) { if (!set.contains(member)) { return false ; } } } return true ; } this .difference = function (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; } }
二叉树
树是一种非线性的数据结构,以分层的方式存储数据,被用来存储具有层级关系的数据和有序列表。二叉树是一种特殊的树,它的子节点不超过两个,使得它们之上的操作非常高效。
二叉树有三种遍历方式:
中序:升序访问所有的
先序:先访问根节点,再访问左节点和右节点
后序:先访问叶子节点,从左节点到右节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 function Node (data, left, right ) { this .data = data; this .left = left; this .right = right; } function BST ( ) { this .root = null ; this .insert = function (data ) { var n = new Node(data, null , null ) if (this .root == null ) { this .root = n; } else { var current = this .root; var parent; while (1 ) { 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 ; } } } } }; this .inOrder = function (node ) { if (!(node == null )) { inOrder(node.left); node.show() inOrder(node.right); } }; this .preOrder = function ( ) { if (!(node == null )) { node.show() preOrder(node.left); preOrder(node.right); } }; this .postOrder = function ( ) { if (!(node == null )) { postOrder(node.left); postOrder(node.right); node.show() } }; this .getMin = function ( ) { var current = this .root; while (!(current.left == null )) { current = current.left; } return current.data }; this .find = function ( ) { var current = this .root; while (!(current.left == null )) { if (current.data == data) { return current } else if (data < current.data) { current = current.left; } else { current = current.right; } } return null ; }; this .remove = function (data ) { root = removeNode(this .root, data) } this .removeNode = function (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 = getMin(node.right); node.data = tempNode.data; node.right = removeNode(node.right, tempNode.data); return node; } else if (data < node.data) { node.left = this .removeNode(node.left, data); return node; } else { node.right = this .removeNode(node.right, data); return node; } } }
图
图是由边的集合以及顶点的集合组成,分为有向图和无序图。
圈是至少有一条边,且第一个顶点和最后一个顶点相同的路径,没有重复边或者重复顶点的圈是简单圈,除了第一个和最后一个顶点以外,路径的其它顶点有重复的圈被称为平凡圈
1 2 3 4 5 function Vertex (label, vasVisited ) { this .label = label; this .wasVisited = wasVisited; } function
排序
冒泡排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function bubbleSort (arr ) { var temp for (var outer = arr.length; outer > 1 ; --outer) { for (var inner = 0 ; inner < outer; ++inner) { if (arr[inner] > arr[inner+1 ]) { temp = arr[inner] arr[inner] = arr[inner+1 ] arr[inner+1 ] = temp } } } }
选择排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function selectionSort (arr ) { var min, temp for (var outer = 0 ; out < arr.length - 1 ; ++outer) { min = outer for (var inner = outer+1 ; inner < arr.length ; ++inner) { if (arr[inner] < arr[min]) { min = inner } temp = outer outer = min min = temp } } }
插入排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function insertionSort (arr ) { var temp, inner for (var outer = 1 ; outer < arr.length; ++outer) { temp = arr[outer] inner = outer while (inner > 0 && arr[inner-1 ] >= temp) { arr[inner] = arr[inner-1 ] --inner } arr[inner] = temp } }
快速排序
适用于大型数据集合,小数据性能反而下降
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function qSort (arr ) { if (arr.length === 0 ) { return [] } var left = [] var right = [] var pivot = arr[0 ] for (var i = 1 ; i < arr.length; i++) { if (arr[i] < pivot) { left.push(arr[i]) } else { right.push(arr[i]) } } return qSort(left).concat(pivot, qSort(right)) }
检索
顺序查找
1 2 3 4 5 6 7 8 9 function seqSearch (arr, data ) { for (var i = 0 ; i<arr.length; ++i) { if (arr[i] === data) { swap(arr[i], arr[i-1 ]) return i } } return -1 }
二分查找
相比顺序查找,二分查找算法比顺序查找更高效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function binSearch (arr, data ) { var upperBound = arr.length - 1 var lowerBound = 0 while (lowerBound <= upperBound) { var mid = Math .floor((upperBound + lowerBound) / 2 ) if (arr[mid] < data) { lowerBound = mid + 1 } else if (arr[mid] > data) { upperBound = mid - 1 } else { return mid } } return -1 }
动态规划
动态规划从问题的底部开始解决(与递归相反),把小问题解决合并成一个整体解决方案
得到斐波那契数列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function recurFib (n ) { if (n<2 ) { return n } else { return recurFib(n-1 ) + recurFib(n-2 ) } } function dynFib (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 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 Math .max(
value[n - 1 ] + knapsack(capacity - size[n - 1 ], size, value, n - 1 ),
knapsack(capacity, size, value, n - 1 )
)
}
}