数据结构
1.哈希(Hash)
哈希是干嘛用的?看下面的伪代码,理解哈希的定义:
a <- {
'0':0,
'1':1,
'key':'value',
'键':'值'
}
一个 key 对应一个 value。所有满足一个键、一个值的这种结构。比如 http 请求的第二部分和 http 响应的第二部分都是 key:value。注意:不一定要用冒号,或逗号,只要是这种形式,都叫哈希。
了解了哈希的概念,那哈希怎么用呢?
伪代码:
// 声明数组 a
a <- {
'0':0,
'1':2,
'2':1,
'3':56,
'4':3,
'5':67,
'6':3,
'length':7
}
// 声明一个 hash,记录每一个数字出现多少次
hash <- {
}
// 遍历 数组 a
// 把 a 里面的每一个数字都读到
// 根据 a[index] 取到的值,放到 hash 对应的桶里面,hash 有无数个桶
index <- 0
while( index < a['length'] )
// 当前的数字,取值范围:0,2,1,56,3,67,3
number = a[index]
if hash[number] == undefined // hash[number] 不存在
hash[number] = 1
else // hash[number] 存在
hash[number] = hash[number] + 1
end
index <- index + 1
end
遍历数组 a,每一步遍历过程,这个过程称为 入桶,伪代码如下:
// 遍历的数字,取值范围:0,2,1,56,3,67,3
hash <- {}
// 第一轮
index == 0
number = 0
hash <- {
'0':1
}
// 第二轮
index == 1
number = 2
hash <- {
'0':1 ,
'2':1
}
// 第三轮
index == 2
number = 1
hash <- {
'0':1 ,
'1':1,
'2':1
}
// 第四轮
index == 3
number = 56
hash <- {
'0':1 ,
'1':1,
'2':1,
'56':1
}
// 第五轮
index == 4
number = 3
hash <- {
'0':1 ,
'1':1,
'2':1,
'56':1,
'3':1
}
// 第六轮
index == 5
number = 67
hash <- {
'0':1 ,
'1':1,
'2':1,
'56':1,
'3':1,
'67':1
}
// 第七轮
index == 6
number = 3
hash <- {
'0':1 ,
'1':1,
'2':1,
'3':2,
'56':1,
'67':1
}
接下来,遍历我们的 hash,伪代码如下:
// hash 有多少个值?
// hash 值多少个,跟数组 a 里面最大的值有关
// 思路:找出 a 里面最大的值
index2 <- 0
max <- findMax(a) // findMax 函数找出数组 a 里面最大的值
newArr <- {} // 声明一个新数组
while( index2 <= max ) // index2 == max(67)是可以出现的,index2 < max + 1
count = hash[index2] // 取到值的个数
if count == 1
newArr.push(index2)
else if count == 2
newArr.push(index2)
newArr.push(index2)
else if count == 3
newArr.push(index2)
newArr.push(index2)
newArr.push(index2)
end
index2 <- index2 + 1
end
print newArr
上面的代码优化后,如下所示:
index2 <- 0
max <- findMax(a) // findMax 函数找出数组 a 里面最大的值
newArr <- {} // 声明一个新数组
while( index2 <= max ) // index2 == max(67)是可以出现的,index2 < max + 1
count = hash[index2] // 取到值的个数
if count != undefined
countIndex = 0;
while( countIndex < count )
newArr.push(index2)
countIndex <- countIndex + 1
end
end
index2 <- index2 + 1
end
print newArr
分析上面伪代码,这个过程叫做 出桶,如下所示:
hash <- {
'0':1 ,
'1':1,
'2':1,
'3':2,
'56':1,
'67':1
}
// 出桶
// 让 i 从 0 增长到 67
index2 = 0
newArr = [0]
index2 = 1
newArr = [0,1]
index2 = 2
newArr = [0,1,2]
index2 = 3
newArr = [0,1,2,3,3]
index2 = 4
什么也不做
index2 = 5
什么也不做
...
...
index2 = 56
newArr = [0,1,2,3,3,56]
...
...
index2 = 67
newArr = [0,1,2,3,3,56,67]
-
计数排序中的桶(复杂度 O(n + max),比快排还快) 比较排序的复杂度是:NLogN 计数排序的优缺点 优点: 它很快 缺点:
- 必须要有一个 hash 作为计数的工具
- 无法对小数和负数进行排序
-
桶排序与计数排序的区别 计数排序是最简单的,因为它不需要做二次排序,但是它浪费了桶 桶排序:节约桶,浪费 CPU 排序的一个原则:要么浪费空间,要么浪费时间
-
基数排序与计数排序的区别 基数排序:基数是几进制,可以适应特别大大数字,桶的个数是固定的
2.队列(Queue)
- 先进先出
- 可以用数组实现
- 举例:排队
代码实现如下:
// 声明一个队列 q
var q = [];
// 入队
q.push('张三');
q.push('李四');
q.push('王二');
// 出队
q.shift(); // 张三
q.shift(); // 李四
q.shift(); // 王二
3.栈(stack)
- 先进后出
- 可以用数组实现
- 举例:盗梦空间
代码实现如下:
var stack = [];
// 入栈
stack.push('第一层梦');
stack.push('第二层梦');
stack.push('第三层梦');
// 出栈
stack.pop(); // 第三层梦
stack.pop(); // 第二层梦
stack.pop(); // 第一层梦
注意:数组是哈希
4.链表(Linked List)
- 数组无法直接删除中间的一项,链表可以
- 用哈希(JS里面用对象表示哈希)实现链表
- head、node 概念 head:第一个 hash 对象,链表的表头,只要知道表头就可以知道后面的所有项。 node:节点
链表与数组的区别? 数组的优缺点:在数组里删除一项特别复杂,如果你取到数组的第 n 项很简单,array[n-1] 链表的优缺点:你可以随意的删除一项。但如果你想取到链表的第 n 项,还挺复杂的
所以,如果不涉及经常删除,或者只删头尾,就用数组。如果经常从中间删除一个数据,就用链表。
代码实现:
var a = {
value:0,
next:{
value:2,
next:{
value:1,
next:undefined
}
}
}
// 删除第二项
a.next = a.next.next
5.树(tree)
只要你有层级结构,就要用到树。
-
举例:层级结构、DOM
-
概念:层数、深度、节点个数 层数:树的层级,上面层是小的,下面层是大的 深度:一共有多少层 节点个数:每一个 hash 就是一个节点。没有子节点的节点称作叶子节点。
-
二叉树 概念:每个节点最多只有两个分支(不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”和“右子树”。二叉树的分支具有左右次序,不能颠倒。 二叉树的第 i 层至多拥有 2^{i-1} 个节点数
推导过程如下:
前提:二叉树
层数(i) 那一层最多有几个节点 所有层节点数 对应数组中的位置
0 1 2^0 1 (2^1-1) 0
1 2 2^1 3 (2^2-1) 1~2
2 4 2^2 7 (2^3-1) 3~6
3 8 2^3 15 (2^4-1) 7~14
4 16 2^4 31 (2^5-1) 15~31
5 32 2^5 63 (2^6-1) 32~63
-
满二叉树 概念:每一层上的节点数都是最大节点数
-
完全二叉树
-
完全二叉树和满二叉树可以用数组实现
-
其他树可以用哈希(对象)实现
-
操作:增删改查
-
堆排序用到了 tree
概念:堆(二叉堆)可以视为一棵完全二叉树,完全二叉树的一个“优秀”的性质是,除了最底层外,每一层都是满的,这使得堆可以用数组来表示(普通的一般二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。