JavaScript数据结构基础学习思
JavaScript数据结构基础学习
哈希表
现有的JavaScript“对象”都是基于哈希表实现的
哈希表提供了快速插入和查找操作,无论哈希表中总数有多少条数据,插入和查找的时间复杂度都是O(1)
哈希表引入了哈希函数,将输入的键名通过哈希函数转换成索引
为什么要把键名转换为索引不能直接存储呢?
1.有时候进行数据存储时,数据并没有设置键名。不允许字符串作为索引值
2.键名是用户动态设置的,不安全,通过key直接存储值导致哈希表内存分配不均
哈希表内存分配不均导致内存不足,转成索引值就是分配均匀的了(但是长度固定会导致哈希冲突)
查找第一个重复字符
使用for循环 时间复杂度O(n^2)
const str = 'hello world'
for(let i = 0;i<str.length;i++){
for(j = i+1;j<str.length;j++){
if(str[i] === str[j]0{
return str[i]
}
}
}
使用哈希表
1.可以直接使用对象
const str = 'hello world'
function findFirst(str) {
if (str.length < 1) return new Error('字符串长度为空')
const table = {}
for(const word of str){
if(table[word] ){
return word
}
table[word] = 1
}
}
- 使用Map对象
function findFirst(str){
if (str.length < 1) return new Error('字符串长度为空')
const map = new Map()
for(const word of str){
if(map.get(word){
return word
}
map.set(word,1)
}
}
3.自定义哈希函数使用
class HashTable{
constructor(){
this.size = 1000 // 哈希表需要提前定义长度
this.buckets = Array(1000).fill(null)
}
//将索引转换为索引值
hash(key){
let hash = 0;
for( const char of key){
hash+= char.charCodeAt(0)
//charCodeAt() 方法可返回指定位置的字符的 Unicode 编码。这个返回值是 0 - 65535 之间的整数。
}
return hash % this.size
}
set(key,value){
const keyHah = this.hash(key)
this.buckets[keyHash] = value
}
get(key){
const keyHahs= this.hash(key)
return this.buckets[keyHash]
}
}
哈希碰撞
- 哈希表必须规定长度,size内存不足的时候,会导致不同的key,返回同一个hash值。后面的值会覆盖前面的值
解决哈希碰撞链地址法
每一项都变成数组,相同的hash值push进去保持key值
class HashTable{
constructor(){
this.size = 1000
this.buckets = Array(1000).fill(null)
}
hash(key){
let hash = 0;
for(const char of key){
hash =+ char.charCodeAt(0)
}
return hash % this.size
}
set(key,value){
const keyHash = this.hash(key)
const bucketArray = this.buckets[keyHash]
const storedElement = bucketArray.find((element)=>{
return element.key === keyHash
})
if(storedElement){
storedElement.val = value
}else{
this.buckets.push({key:key,val:value})
}
}
get(key){
const keyHash = this.hash(key)
const bucketArray = this.buckets[keyHash]
const storedElement = bucketArray.find((element)=>{
return element.key === key
})
return storedElement
}
showInfo(){
for(const key in this.buckets){
console.log(key,this.buckets[key])
}
}
}
const table = new HashTable()
for (const char of 'abcde') {
table.set(char, char)
}
for (const char of 'fghijk') {
table.set(char, char)
}
for (const char of 'lmnopq') {
table.set(char, char)
}
console.log(table.showInfo())
解决哈希碰撞开放地址法
避免冲突,一旦冲突就去寻找哈希表中没有值的插槽
class HashTable{
constructor(){
this.size = 1000
this.buckets = Array(1000).fill(null)
}
hash(key){
let hash = 0;
for(const char of key){
hash += char.charCodeAt(0)
}
return hash % this.size
}
set(key,value){
let keyHash = this.hash(key)
if(this.buckets[keyHash] === null || this.buckets[keyHash].key === key){
this.buckets[keyHash] = {key:key,value:value}
}
else{
while(this.buckets[keyHash] !== null){
keyHash++
}
this.buckets[keyHash] = {key:key,value:value}
}
}
get(key){
let keyHash = this.hash(key)
for(let i = keyHash;i<this.buckets.length;i++){
if(!this.buckets[i]) continue;
if(this.buckets[i].key === key){
return this.buckets[i].value
}
}
return undefined
}
}
内置js对象底层其实就是哈希表,只是它解决了哈希冲突
哈希表 | 数组 | 对象 | |
---|---|---|---|
元素访问 | 理论上O(1)/ 哈希碰撞O(n) | O(1) | O(1) |
末尾插入 | 理论上O(1)/ 哈希碰撞O(n) | O(1) | O(1) |
头部插入 | 理论上O(1)/ 哈希碰撞O(n) | O(n) | O(1) |
中间插入 | 理论上O(1)/ 哈希碰撞O(n) | O(n) | O(1) |
元素搜索 | 理论上O(1)/ 哈希碰撞O(n) | O(n) | O(1) |
单纯存储数据使用数组,如果想要考虑关联性的话使用哈希表
是否任何场景都可使用对象 ? NO!
- 某些用例中,管理键值对会 导致冗余代码
- 对于数组/列表循环通常更容易
- 对于很多数组/列表,不需要在开头或中间插入大量内容或进行大量搜索