集合结构
- 集合比较常见的实现方式是哈希表
- 集合通常是一组无需的,不能重复的元素构成
- 在计算机中,集合通常表示的结构中元素是不允许重复的
特殊的数组:
特殊之处在于里面的元素没有顺序,也不能重复
没有顺序意味着不能通过下表进行访问,不能重复意味着相同的对象在集合中只会存在一份
集合常见的操作
| add(value) | 向集合中添加一个新的项 |
|---|---|
| remove(value) | 从集合中移除一个值 |
| has(value) | 如果值在集合中,返回true,否则返回false |
| clear() | 移除集合中的所有项 |
| size() | 返回集合所包含元素的数量,与数组的length属性类似 |
| values() | 返回一个包含集合中所有值得数组 |
add方法,has方法,remove方法,clear方法,size方法,
//add方法
Set.prototype.add = function (value) {
if (this.has(value)) {
return false;
}
//蒋所有缘宿添加到集合中
this.items[value] = value;
return true;
};
//has方法
Set.prototype.has = function (value) {
return this.hasOwnProperty(value);
};
//remove方法
Set.prototype.remove = function (value) {
//1.判断该集合中是都含有该元素
if (!this.has(value)) {
return false;
}
//2.将元素从属性中删除
delete this.items[value];
return true;
};
//clear方法
Set.prototype.clear = function () {
this.item = {};
};
//size方法
Set.prototype.size = function () {
return Object.keys(this.items).length;
};
//获取集合中的所有值
Set.prototype.values = function () {
return Object.keys(this.items);
};
集合之间的操作
- 并集:对于给定的两个结合,返回一个包含集合中所有元素的新集合
- 交集:对于给定的两个集合,返回包含两个结合中共有的新集合
- 差集:对于给定的两个集合,返回一个所有存在于第一个集合且不存在与第二个集合的元素的新集合
- 验证一个给定集合是否是另一个集合的子集
//集合之间交集的操作
Set.prototype.intersection = function (otherSet) {
//this:集合A
//otherSet:集合B
//1.创建新集合
var intersectionSet = new Set();
//2.从A中取出一个元素,判断是否同时存在于B中,存在放入新集合中
var values = this.values();
for (var i = 0; i < values.length; i++) {
var item = values[i];
if (otherSet.has(item)) {
intersectionSet.add(item);
}
}
return intersectionSet;
};
//集合之差集实现
Set.prototype.difference = function (otherSet) {
//this:集合A
//otherSet:集合B
//1.创建新集合
var differenceSet = new Set();
//2.从A中取出一个元素,判断是否同时存在于B中,不存在B中,则存在放入新集合中
var values = this.values();
for (var i = 0; i < values.length; i++) {
var item = values[i];
if (!otherSet.has(item)) {
difference.add(item);
}
}
return differenceSet;
};
//子集
Set.prototype.subset = function (otherSet) {
//this:集合A
//otherSet:集合B
//遍历集合A中所有的元素,如果发现集合A中元素在集合B中不存在,那么false
//如果遍历完了整个集合,依然没有返回false,那么返回true
var values = this.values();
for (var i = 0; i < values.length; i++) {
var item = values[i];
if (!otherSet.has(item)) {
return false;
}
}
return true;
};
字典
数字-集合-字典
- 在JavaScript中默认提供了数组,ES6中增加了集合和字典
- 字典的特点:对应关系
-
- 比如保存一个人得信息,在合适的情况下取出这些信息
- 使用数组的方式:[18,"xiaowanzi",165]可以通过下标值找到
- 使用字典的方式:{"age":18,"name":"xiaowanzi","height":165}可以通过key取出value
- 另外字典中的key是不可以重复的,而value可以重复,并且字典中的key是无序的
字典和映射的关系
有些编程语言中称这种映射关系为字典,因为他确实和生活中的字典比较相似
有些编程语言中称这种映射关系为Map,
字典和数组
字典和数组对比的话,字典可以非常方便的通过key来搜索对应的value,key可以包含特殊含义,也更容易被人记住
字典和对象
- 很多编程语言中字典和对象的区分比较明显,对象通产士一种在编译期就确定下来的结构,不可以动态的添加过着删除属性,而字典通常会使用类似于哈希表的数据结构去实现一种可以动态添加数据的结构
- 但是在JavaScript中,似乎对象就是一种字典,所有在早期的JavaScript中,没有字典这种数据类型,应为完全可以使用对象去代替
哈希表
数组进行插入操作时,效率比较低
数组进行查找的效率
- 如果是基于索引查找,效率非常的高
- 基于内容去查找
数组进行删除操作的话,效率也不高
哈希表到底是什么
结构就是数组,但是神奇的地方在于他对下标值的一种变换,我们称之为哈希函数,送过哈希函数可以获得到HashCode
//封装哈希表
function HashTable() {
this.storage = [];
this.count = 0;
this.limit = 7;
//方法
//哈希函数
function ha法shFunc(str, size) {
//1.定义hashCode变量
var hashCode = 0;
//2.霍纳算法,来计算hashCode的值
//cats->Unicode编码
for (var i = 0; i < str.length; i++) {
hashCode = 37 * hashCode + str.charCodeAt(i);
}
//3.取余操作
var index = hashCode % size;
return index;
}
//插入&修改操作
HashTable.prototype.put = function (key, value) {
//1.根据key获取对应的Index
var index = this.hashFunc(key, this.limit);
//2.根据Index取出对应的bucket
var bucket = this.storage[index];
//3.判断bucket是否为null
if (bucket == null) {
bucket = [];
this.storage[index] = bucket;
}
//4.判断是否修改数据
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
tuple[1] = value;
return;
}
}
};
//获取方法
HashTable.prototype.get = function (key) {
//1.根据key获取对应的Index
var index = this.hashFunc(key, this.limit);
//2.根据Index取出对应的bucket
var bucket = this.storage[index];
//3.判断bucket是否为null
if (bucket == null) {
return null;
}
//4.有bucket
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
return tuple[1];
}
}
//依然没有找到,返回null
return null;
};
//删除方法
HashTable.prototype.remove = function (key) {
//1.根据key获取对应的Index
var index = this.hashFunc(key, this.limit);
//2.根据Index取出对应的bucket
var bucket = this.storage[index];
//3.判断bucket是否为null
if (bucket == null) {
return null;
}
//有bucket,那么进行线性查找,并且删除
for (var i = 0; i < bucket.length; i++) {
var tuple = bucket[i];
if (tuple[0] == key) {
bucket.splice(i, 1);
this.count--;
return tuple[1];
}
}
//5.依然没有找到
return null;
};
//其他方法
//判断哈希表是否为空
HashTable.prototype.isEmpty = function () {
return this.count == 0;
};
//获取哈希表中元素的个数
HashTable.prototype.size = function () {
return this.count;
};
}
树结构
树的术语
- 节点的度(degree):节点的子树个数
- 树的度:树的所有节点中最大的度数
- 叶节点(Leaf):度位0的节点,也成为了叶子节点
- 父节点:有子树节点,是其子树的根节点的父节点
- 子节点(Child):若A节点是B节点的父节点,则称B节点是A的子节点;子节点也称为孩子节点
- 兄弟节点(sibling):具有同一父节点的各节点彼此都是兄弟节点
- 路径和路径长度:从节点n1到nk的路径为一个节点序列n1,n2,...nk是ni+1的父节点,路径包含边的个数为路径的长度
- 节点的层次(Level):规定根节点在1层,其任一节点的层数是其父节点的层数加一
- 数的深度(Depth):树种所有节点的最大层次是这棵树的深度
二叉搜索树常见操作
| insert(key) | 向树中插入一个新的键 |
|---|---|
| search(key) | 在数中查找一个键,如果节点存在,则返回true,如果不存在,则返回false |
| inOrderTraverse | 通过中序遍历方式遍历所有节点 |
| preOrderTraverse | 通过先序遍历方式遍历所有节点 |
| postOrderTraverse | 通过后序遍历方式遍历所有节点 |
| min | 返回树中最小的值/键 |
| max | 返回树中最大的值/键 |
| remove(key) | 从树中移除某个键 |
封装二叉搜索树
//封装二叉搜索树
function BinarySerachTree() {
function Node(key) {
this.key = key;
this.left = left;
this.right = right;
}
//属性
this.root = null;
//方法
//插入数据:对外给数据调用的方法
BinarySrarchTree.prototype.insert = function (key) {
//1.根据key创建节点
var newNode = new Node(key);
//2.判断节点是否有值
if (this.root == null) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
};
BinarySerachTree.prototype.insertNode = function (node, newNode) {
if (newNode.key < node.key) {
//向左查找
if (node.left == null) {
node.left == newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
//向右查找
if (node.right == null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
};
}
遍历二叉搜索树
二叉树常见的三种遍历方式
- 先序遍历
- 中序遍历
- 后序遍历
先序遍历过程
- 访问根节点
- 先序遍历其左子树
- 先序遍历其右子树
先序遍历
//1.先序遍历
BinarySerachTree.prototype.preOrderTraversal = function (handler) {
this.preOrderTraversalNode(this.root, handler);
};
BinarySerachTree.prototype.preOrderTraversalNode = function (
node,
handler
) {
if (node !== null) {
//1.处理经过的节点
handler(node.key);
}
//处理经过节点的子节点
this.preOrderTraversalNode(node.left, handler);
//处理经过节点的右节点
this.preOrderTraversalNode(no.right, handler);
};
中序遍历
//2.中序遍历
BinarySerachTree.prototype.midOrderTraversal = function (handler) {
this.preOrderTraversalNode(this.root, handler);
};
BinarySerachTree.prototype.midOrderTraversalNode = function (
node,
handler
) {
if (node !== null) {
//1.处理经过的节点
handler(node.key);
}
//处理经过节点的子节点
this.midOrderTraversalNode(node.left, handler);
//处理经过节点的右节点
this.midOrderTraversalNode(node.right, handler);
};
后续遍历
//后续遍历
BinarySerachTree.prototype.postOrderTraversal = function (handler) {
this.postOrderTraversalNode(this.root, handler);
};
BinarySerachTree.prototype.postOrderTraversalNode = function (
node,
handler
) {
if (node !== null) {
//处理经过节点的子节点
this.postOrderTraversalNode(node.left, handler);
//处理经过节点的右节点
this.postOrderTraversalNode(node.right, handler);
}
handler(node.key);
};
寻找最大值和最小值
//寻找最值
//寻找最大值
BinarySerachTree.prototype.max = function () {
//1.求根节点
var node = this.root;
//2.一次向下查找,直到节点为null
while (node !== null) {
key = node.key;
node = node.right;
}
return key;
};
BinarySerachTree.prototype.min = function () {
//1.求根节点
var node = this.root;
while (node !== null) {
key = node.key;
node = node.left;
}
return key;
};
搜索某个特定的key
BinarySerachTree.prototype.search = function (key) {
//1.获取根节点
var node = this.root;
//2.循环搜索key
while (node != null) {
if (key < node.key) {
node = node.left;
} else if (key > node.key) {
node = node.right;
} else {
return true;
}
}
return false;
};
删除一个节点
//删除一个节点
BinarySerachTree.prototype.remove = function (key) {
//1.寻找要删除的节点
//1.1定义变量,保存一些信息
var current = this.root;
var parent = null;
var isLeftChild = true;
//开始寻找要删除的节点
while (current.key != key) {
parent = current;
if (key < current.key) {
isLeftChild = true;
current = current.left;
} else {
isLeftChild = false;
current = current.right;
}
//某种情况:已经找到了最后的节点,依然没有找到==key
if (current == null) return false;
}
//2.根据对应的情况删除节点
//2.1删除的节点是叶子结点(没有子节点)
if (current.left == null && current.right == null) {
if (current == this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
//2.2删除的节点有一个子节点
else if(current.right==null){
if(current==this.root){
this.root=current.left
}else if(isLeftChild){
parent.left=current.left
}else{
parent.right=current.left
}
}else if(current.left==null){
if(current==this.root){
this.root=current.right
}else if(isLeftChild){
parent.left=current.right
}else{
parent.right=current.right
}
}
//2.3删除节点的两个子节点
else {
//1.获取后继节点
var success = this.getSuccessor(current)
//2.判断是否根节点
if(current==this.root){
this.root=successor
}else if(isLeftChild){
parent.left=successor
}else{
parent.right=successor
}
//3.将删除节点的左子树=current.left
successor.left=current.left
}
//找后继方法
BinarySerachTree.prototype.getSuccessor=function(delNode){
//1.定义变量
var successor=delNode
var current=delNode.right
var successorParent=delNode
//2.循环查找
while(current!=null){
successor=current
current=current.left
}
//3.判断寻找的后继节点是否直接就是delNode的right节点
if(success!=delNode.right){
successorParent.right=successor.right
successor.right=delNode.right
}
return successor
}
}}
\