数组
数组是一种线性结构,可以在数组的任意位置插入和删除数据
但为了实现某些功能,必须对这种任意性加以限制
而栈和队列就是比较常见的受限的线性结构
删除
splice():该方法向或者从数组中添加或者删除项目,返回被删除的项目。(该方法会改变原数组)
splice(index,howmany,item1,…itemX)
index参数:必须,整数,规定添加或者删除的位置,使用负数,从数组尾部规定位置。
howmany参数:必须,要删除的数量,如果为0,则不删除项目。
tem1,…itemX参数:可选,向数组添加的新项目。
栈(Stack)[LIFO]
栈的限制是仅允许在表的一端进行插入和删除操作,这一端被称为栈顶,另一端被称为栈底
向一个栈插入新的元素称为进栈、入栈、压栈
从一个栈删除元素称为出栈、退栈
应用:函数调用栈
函数之间相互调用:A调用B,B调用C,C调用D
在执行的过程中,会先将A压入栈,A没有执行完,所以不会弹出栈,在A执行过程中调用了B,会将B压入栈,以此类推,将C、D都压入栈,D执行完,弹出栈,C、B、A依次弹出栈
栈的常见操作
push(element):添加一个新元素到栈顶位置
pop():移除栈顶的元素,同时返回被移除的元素
peek():返回栈顶的元素,不对栈做任何修改(不会移除栈顶的元素)
isEmpty():如果栈内没有任何元素返回true,否则返回false
size():返回栈里的元素个数,和数组的length属性类似
toString():将栈中的内容以字符形式返回
栈的封装
function Stack(){
this.items=[];
//栈的相关操作
//1.将元素压入栈
Stack.prototype.push=function(element){
this.items.push(element);
}
//从栈中取出元素
Stack.prototype.pop=function(){
return this.items.pop();
}
//查看一下栈顶元素
Stack.prototype.peek=function(){
return this.items[this.items.length-1];
}
//判断栈是否为空
Stack.prototype.isEmpty=function(){
return this.items.length==0;
}
//获取栈中元素的个数
Stack.prototype.size=function(){
return this.items.length;
}
//toString方法
Stack.prototype.toStringh=function(){
var resultString="";
for(var i=0;i<this.items.length;i++){
resultString += this.items[i];
}
return resultString;
}
}
//栈的使用
var stack=new Stack();
stack.push(100);
应用:十进制转二进制
function decToBin(decNumber){
var stack=new Stack();
//用while循环(不确定循环次数,确定了循环条件)
while(decNumber>0){
stack.push(decNumber%2);//取余
decNumber=Math.floor(decNumber/2);//取整
}
//从栈中取出0和1
var binarrayString="";
while(!stack.isEmpty()){
binarrayString+=stack.pop();
}
return binarrayString;
}
队列(Queue)[FIFO]
队列的限制是指它只允许在表的前端(front)进行删除操作
在表的后端(rear)进行插入操作
队列的常见操作
enqueue(element):向队列尾部添加一个(或多个)新的项
dequeue():移除队列的第一项,并返回被移除的元素
front():返回队列中第一个元素,队列不做任何改动
isEmpty():如果队列内没有任何元素返回true,否则返回false
size():返回队列里的元素个数,和数组的length属性类似
toString():将队列中的内容以字符形式返回
队列的封装
function Queue(){
this.items=[];
//将元素加入到队列中
Queue.prototype.enqueue=function(element){
this.items.push(element);
}
//将第一个元素移除
Queue.prototype.dequeue=function(){
//shift()是将数组第一个元素移除并返回第一个元素
return this.items.shift();
}
//查看第一个元素
Queue.prototype.front=function(){
return this.items[0];
}
//查看队列是否为空
Queue.prototype.isEmpty=function(){
return this.items.length==0;
}
//toString方法
Queue.prototype.toString=function(){
var resultString="";
for(var i=0;i<this.items.length;i++){
resultString += this.items[i];
}
return resultString;
}
}
//使用队列
var queue=new Queue();
queue.enqueue(100);
应用:击鼓传花
//游戏规则:几个朋友围成一圈,开始数数,数到某个数字的人自动淘汰,最后剩下的人取得胜利,请问最后剩下的是原来哪一个位置上的人
//解题思想:封装一个基于队列的函数 参数是所有参与人的姓名,基于的数字 结果是最终剩下一人的姓名
function passGame(nameList,num){
//先创建一个队列,把名字放到队列里
var queue=new Queue();
for(var i=0;i<nameList.length;i++){
queue.enqueue(nameLiat[i]);
}
while(queue.size>1){
//不是num的时候重新添加到队尾 是num的时候将其从队列中删除
for(var i=0;i<num-1;i++){
queue.enqueue(queue.dequeue());
}
queue.dequeue();
}
var endName=queue.front();
alert(endName);
return nameList.indexOf(endName);
}
优先级队列
特点:插入元素的时候会考虑该数据的优先级,和其他数据进行比较,得出这个元素在队列中的正确位置
主要考虑的问题:
1、每个元素不再只是一个数据,还包含数据的优先级
2、在添加方式中,根据优先级放入正确的位置
优先级队列的封装
function PriorityQueue(){
//在PriorityQueue重新创建了一个类,可以理解成内部类
function QueueElement(element,priority){
this.element=element;
this.priority=priority;
}
this.items=[];
//实现插入方法
PriorityQueue.prototype.enqueue=function(element,priority){
var qe=new QueueElement(element,priority);
//判断队列是否为空
if(this.items.length==0){
this.items.push(qe);
}else{
var added=false;
for(var i=0;i<this.items.length;i++){
if(qe.priority<this.items[i].priority){
this.items.splice(i,0,qe);//d
added=true;
break;
}
}
if(!added){
this.items.push(qe);
}
}
}
}
//优先级队列的使用
var pq=new PriorityQueue();
pq.enqueue(abc,1000);
pq.enqueue(bhy,199);
pq.enqueue(iju,66);
pq.enqueue(qwe,1);
链表(Linked List)
链表的每一个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针)组成
与数组的比较
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同
数组的缺点
1、数组的创建通常需要申请一段连续的内存空间,并且大小是固定的,所有当当前数组不能满足容量需求时,需要扩容(一般情况是申请一个更大的数组,比如2倍,然后将数组中的元素复制过去)
2、在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移
链表的优点
1、内存空间不是必须连续的,可以充分利用计算机的内存,实现灵活的内存动态管理
2、链表不必在创建时就确定大小,并且大小可以无限的延伸下去
3、链表在插入和删除数据时,时间复杂度可以达到O(1)
链表的缺点
链表在访问任何一个位置的元素时,都需要从头开始访问
链表常见操作(增删改查)
1、append(element):向列表尾部添加一个新的项
2、insert(position,element):向列表的特定位置插入一个新的项
3、get(position):获取对应位置的元素
4、indexOf(element):返回元素在链表中的索引,如果没有则返回-1
5、update(position,element):修改某个位置的元素
6、removeAt(position):从链表的特定位置移除一项
7、remove(element):从列表中移除一项
8、isEmpty():如果链表中不包含任何元素,返回true,否则返回false
9、size():返回链表包含的元素个数
10、toString():
单链表
单链表的封装
function LinkedList(){
//内部的类:节点类
function Node(data){
this.data=data;
this.next=null;
}
//属性
this.head=null;
this.length=0;
//append()方法
LinkedList.prototype.append=function(data){
//创建新的节点
var newNode=new Node(data);
//判断添加的是否为第一个节点
if(this.length==0){
this.head=newNode;
}else{
//找到最后一个节点
var current=this.head;
while(current.next){
current=current.next;
}
//将最后一个节点的next指向newNode
current.next=newNode;
}
this.length += 1;
}
//toString()方法
LinkedList.prototype.toString=function(){
var resultString="";
var current=this.head;
//循环获取一个个的节点
while(current){
resultString += current.data;
current=current.next;
}
return resultString;
}
//insert()方法
LinkedList.prototype.insert=function(position,data){
//对position进行越界判断
if(position<0 || position>this.length)return false;
//根据data创建新的节点
var newNode=new Node(data);
//判断插入的位置是否为0
if(position==0){
newNode.next=this.head;
this.head=newNode;
}else{
var index=0;
var current=this.head;
var previous=null;
//通过while循环来找到所要插入位置的前后两个元素
while(index++ < position){
previous=current;
current=current.next;
}
newNode.next=current;
previous.next=newNode;
}
this.length += 1;
return true;
}
//get(position)方法
LinkedList.prototype.get=finction(position){
if(position<0||position>=this.length)return null;
var index=0;
var current=this.head;
while(index++<position){
current=current.next;
}
return current.data;
}
//indexOf(element)方法
LinkedList.prototype.indexOf=function(element){
var index=0;
var current=this.head;
while(current){
if(current.data==element){
return index;
}
current=current.next;
index++;
}
//找到最后没有找到,返回-1
return -1;
}
//update()方法
LinkedList.prototype.update=function(position,element){
//越界判断
if(position<0||pisotion>=this.length)return false;
var index=0;
var current=this.head;
while(index++<position){
current=current.next;
}
current.data=element;
return true;
}
//removeAt()方法
LinkedList.prototype.removeAt=function(position){
if(position<0||position>=this.length)return null;
var current=this.head;
//判断删除的是否是第一个节点
if(position==0){
this.head=this.head.next;
}else{
var index=0;
var previous=null;
while(index++<position){
previous=current;
current=current.next;
}
previous.next=current.next;
}
//链表长度减一
this.length -= 1;
return current.data;
}
//remove()方法1
LinkedList.prototype.remove=function(element){
if(this.head.data=element){
this.head=null;
}else{
var current=this.head;
var previous=null;
while(current){
if(current.data==element){
previous.next=current.next;
}
previous=current;
current=current.next;
}
}
this.length -= 1;
}
//remove()方法2--基于前面函数的封装
LinkedList.prototype.remove=function(element){
//先找到该元素的位置
var position=this.inedxOf(element);
//再移除它
return this.removeAt(position);
}
}
//创建LinkedList
var list=new LinkedList();
list.append("abc");
list.append("njy");
list.append("qwe");
双向链表
与单向链表的比较
单项链表有一个明显的缺点:可以轻松的到达下一个节点,但是回到上一个节点是很难的
举个例子:假设一个文本编辑器用单链表来存储文本,每一行用一个String对象存储在链表的一个节点中,当编辑器用户向下移动光标时,链表直接操作到下一个节点即可,但是当用户将光标向上移动时,这时为了回到上一个节点,我们需要从first开始,依次走到想要的节点上
双向链表的优点
1、既可以从头遍历到尾,又可以从尾遍历到头
2、一个节点既有向前连接的引用,也有一个向后连接的引用
双向链表的缺点
1、每次在插入或删除某个节点时,需要处理四个引用,实现起来更困难
2、相对于单向链表,占用内存空间更大一些
双向链表的特点
1、使用一个head和tail分别指向头部和尾部的节点
2、每个节点都有三部分组成:前一个节点的指针(prev)/保存的元素(item)/后一个节点的指针(next)
3、双向链表的第一个节点的prev是null
4、双向链表的最后一个节点的next是null
双向链表的封装
function DoublyLinkedList(){
//内部类:节点类
function Node(data){
this.data=data;
this.prev=null;
this.next=null;
}
//属性
this.head=null;
this.tail=null;
this.length=0;
//append()方法
DoublyLinkedList.prototype.append=function(data){
//创建新的节点
var newNode=new Node(data);
if(this.length==0){
this.head=newNode;
}else{
var current=this.head;
while(current.next){
current=current.next;
}
current.next=newNode;
newNode.prev=current;
}
this.length += 1;
}
//toString()方法:将链表转成字符串
DoublyLinkedList.prototype.toString=function(){
return this.backwardString();
}
//反向遍历
DoublyLinkedList.prototype.forwardString=function(){
if(this.length==0)return null;
var resultString="";
var current=this.tail;
while(current){
resultString += current.data;
current=current.prev;
}
return resultString;
}
//正向遍历
DoublyLinkedList.prototype.backwardString=function(){
if(this.length==0)return null;
var resultString="";
var current=this.head;
while(current){
resultString += current.data;
current=current.next;
}
return resultString;
}
//insert()方法
DoublyLinkedList.prototype.insert=function(position,element){
if(position<0||position>this.length)return false;
var newNode=new Node(element);
if(this.length==0){
this.head=newNode;
this.tail=newNode;
}else{
if(position==0){
this.head.prev=newNode;
newNode.next=this.head;
this.head=newNode;
}else if(position==this.length){
this.tail.next=newNode;
newNode.prev=this.tail;
this.tail=newNode;
}else{
var current=this.head;
var index=0;
while(index++<position){
current=current.next;
}
newNode.next=current;
current.prev=newNode;
current.prev.next=newNode;
newNode.prev=current.prev;
}
this.length += 1;
return true;
}
}
//get(position)方法
DoublyLinkedList.prototype.get=function(position){
if(position<0||position>=this.length)return null;
var current=this.head;
var index=0;
while(index++ < position){
current=current.next;
}
return current.data;
}
//indexOf()方法
DoublyLinkedList.prototype.indexOf=function(element){
var current=this.head;
var index=0;
while(current){
if(current.data==element){
return index;
}
current=current.next;
index++;
}
return -1;
}
//update()方法
DoublyLinkedList.prototype.update=function(position,element){
if(position<0||position>=this.length)return false;
var current=this.head;
var index=0;
while(index++ < position){
current=current.next;
}
current.data=element;
return true;
}
//removeAt()方法
DoublyLinkedList.prototype.removeAt=function(position){
if(position<0||position>=this.length)return null;
var current=this.head;
if(this.length==1){
this.head=null;
this.tail=null;
}else{
if(position==0){
this.head.next.pre=null;
this.head=this.head.next;
}else if(position==this.length-1){
current=this.tail;
this.tail.pre.next=null;
this.tail=this.tail.pre;
}else{
var index=0;
while(index++ < position){
current=current.next;
}
current.prev.next=current.next;
current.next.prev=current.prev;
}
}
this.length -= 1;
return current.data;
}
//remove()方法
DoublyLinkedList.prototype.remove=function(element){
var position=this.indexOf(element);
return this.removeAt(position);
}
}
集合
集合的特点
集合是由一组无序的,不能重复的的元素构成
无序意味着不能通过下标值进行访问,不能重复意味着相同的对象在集合中只会存在一份
集合的常见操作
1、add(value):向集合添加一个新的项
2、remove(value):从集合移除一个值
3、has(value):如果值在集合中,返回true,否则返回false;
4、clear():移除集合中的所有项
5、size():返回集合所包含元素的数量
6、values():返回一个包含集合中所有值的数组
集合的封装
function Set(){
this.items={};//基于Object对象实现
//has()方法
Set.prototype.has=function(value){
return this.items.hasOwnProperty(value);
}
//add()方法
Set.prototype.add=function(value){
//判断当前集合是否已含有该元素
if(this.has(value))return false;
//将元素添加到集合中
this.items[value]=value;
return true;
}
//remove()方法
Set.prototype.remove=function(value){
if(!this.has(value))return false;
delete this.items[value];
return true;
}
//clear()方法
Set.prototype.clear=function(){
this.items={};
}
//size()方法
Set.prototype.size=function(){
//Object keys()处理对象数据时:返回可枚举的属性数组
//Object.values()处理对象数据时:返回可枚举的值组成的数组
return Object.keys(this.items).length;
}
//values()方法
Set.prototype.values=function(){
return Object.keys(this.items);
}
}
集合间的操作
并集
Set.prototype.union=function(otherSet){
//创建一个新的集合
var unionSet=new set();
var values=this.values();
//将本集合元素先放入新的集合
for(var i=0;i<values.length;i++){
unionSet.add(values[i]);
}
values=otherSet.values();
//将别的集合里与本集合不重复的元素放入新集合(add()方法已经实现)
for(var i=0;i<values.length;i++){
unionSet.add(value[i]);
}
return unionSet;
}
交集
Set.prototype.intersection=function(otherSet){
var intersectionSet=new Set();
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){
var differenceSet=new Set();
var values=this.values();
for(var i=0;i<values.length;i++){
var item=values[i];
if(!otherSet.has(item)){
differenceSet.add(item);
}
}
return differenceSet;
}
子集
Set.prototype.subset=function(otherSet){
var values=this.values();
for(var i=0;i<values.length;i++){
var item=values[i];
if(!otherSet.has(item)){
return false;
}
}
return true;
}
字典
特点:一一对应(key–value)
字典中的key是无序的,不可重复的,而value可以重复
1、有些编程语言称映射关系为字典
2、有些编程语言称映射关系为Map(不要翻译为地图,要翻译成映射)
很多编程语言对字典和对象区分比较明显
在JavaScript中,似乎对象本身就是一种字典
字典的封装
function Dictionary(){
this.items={};
//在字典中添加键值对
Dictionary.prototype.set = function(key, value) {
this.items[key] = value;
}
//判断字典中是否有某个key
Dictionary.prototype.has = function(key) {
return this.items.hasOwnProperty(key);
}
//从字典中移除元素
Dictionary.prototype.remove = function(key) {
//先判断字典中是否有这个key
if(!this.has(key))return false;
//从字典中删除
delete this.items[key];
return true;
}
//根据key去获取value
Dictionary.prototype.get = function(key){
return this.has(key) ? this.items[key] : undefined;
}
//获取所有的keys
Dictionary.prototype.keys = function(){
return Object.keys(this.items);
}
//获取所有的values
Dictionary.prototype.values = function(){
return Object.values(this.items);
}
//size()方法
Dictionary.prototype.size = function(){
return this.keys().length;
}
//clear()方法
Dictionary.prototype.clear = function(){
this.items = {};
}
}
哈希表
哈希表通常是基于数组进行实现的
与数组的比较
哈希表的优点
1、它可以提供非常快速的插入-删除-查找操作
2、无论多少数据,插入和删除值选哟接近常量的时间,即O(1)的时间级
3、哈希表的速度比树还要快,编码相对于树来说要容易很多
哈希表的缺点
1、哈希表中的数据是没有顺序的,所以不能以一种固定的方式来遍历元素
2、哈希表中的key是不允许重复的
哈希表到底是什么?
它的结构就是数组,但是它神奇的地方在于对下标值的一种变换,这种变暖我们称之为哈希函数,通过哈希函数可以获取到 HashCode
哈希化
将大数字转化成数组范围内下标的过程,我们称之为哈希化
哈希函数
通常我们将单词转成大数字,大数字在进行哈希化的代码实现放在一个函数中,这个函数我们称之为哈希函数
哈希表
最终将数据插入到的这个数组,对整个结构的封装,我们称之为哈希表
解决冲突的方法
链地址法(拉链法)
1、链地址法解决冲突的办法是每个数组单元中存储的不再是单个数据,而是一个链条
2、这个链条常用的数据结构是数组或者链表
3、比如是链表,也就是每个数组单元中存储着一个链表,一旦发生重复,将重复的元素插入到链表的首端或者末端即可
4、当查询时,先根据哈希化后的下标值找到对应的位置,再取出链表,依次查询数据
开放地址法
线性探测
线性的查找空白的单元
插入32
1、经过哈希化得到的index=2,但是在插入的时候,发现该位置已经有了82,
2、线性探测就是从index位置+1开始一点点查找合适的位置来放置32,
3、空的位置就是合适的位置
查询32
1、首先经过哈希化得到index=2,比较2位置的结果与查询的数值是否相同,相同就返回
2、不相同就线性查找
删除32
1、删除一个数据项时,不可以将这个位置的内容设置为null,
2、将它设置为null可能会影响之后的查询操作,所以删除一个位置的数据时,将它进行特殊处理(比如设置为-1)
3、当我们之后看到-1时,就知道查询时要继续查询,但是插入时这个位置可以放置数据
问题
聚集:一连串填充单元
如果之前的数据是连续插入的,那么新插入的数据可能需要探测很长的距离
二次探测
主要优化的是探测时的步长
线性探测可以看成是步长为1的探测
二次探测:比如从下标值x开始,x+12,x+22,x+3^2……
会造成步长不一的一种聚集
再哈希法
为了消除线性探测和二次探测中无论步长+1还是步长+平方中存在的问题
1、二次探测的算法产生的探测序列步长是固定的1 4 9 16……
2、现在需要一种方法:产生一种依赖关键字的探测序列,而不是每个关键字都一样
3、那么,不同的关键字即使映射到相同的数组下标,也可以使用不同的探测序列
4、再哈希法:把关键字用另外一个哈希函数,再做一次哈希化,用这次哈希化的结果作为步长
5、对于指定的关键字,步长在整个探测中是不变的,不过不同的关键字使用不同的步长
第二次哈希化需要具备如下特点:
1、和第一个哈希函数不同
2、不能输出为0(否则将没有步长,每次探测都是原地踏步,算法进入死循环)
哈希化的效率
平均探测长度以及存取时间,取决于填装因子,随着填装因子的变大,探测长度变长
填装因子表示当前哈希表中已经包含的数据项和整个哈希表长度的比值
开放地址法填装因子最大为1,因为它必须找到空白的单元才能将元素放入
链地址法填装因子可以大于1,因为拉链法可以无限延伸下去
真实开发中,链地址法更常见
优秀的哈希函数
1、快速的计算:秦九韶算法/霍纳算法
2、均匀的分布:在使用常量的地方使用质数
(1)哈希表的长度
(2)N次幂的底数
设计哈希函数
//将字符串转成比较大的数字:hashCode
//将大的数字hahCode压缩到数组范围之内
function hashFunc(str,size){
//定义hashCode变量
var hashCode=0;
//霍纳算法:来计算hashCode的值
for(var i=0;i<str.length;i++){
hashCode=37*hashCode+str.charCodeAt(i);
}
//取余操作
var index=hashCode%size;
return index;
}
创建哈希表
function HashTable(){
//storage表示数组,存放相应的元素
this.storage=[];
//count记录当前数组已经存放了多少数据
this.count=0;
//limit用于标记数组中一共可以存放多少个元素
this.limit=7;
//哈希函数
HashTable.prototype.hashFunc=function(str,size){
//定义hashCode变量
var hashCode=0;
//霍纳算法:来计算hashCode的值
for(var i=0;i<str.length;i++){
hashCode=37*hashCode+str.charCodeAt(i);
}
//取余操作
var index=hashCode%size;
return index;
}
//插入和修改操作
HashTable.prototype.put=function(key,value){
///先根据key得出index
var index=this.hashFunc(key,this.limit);
//根据index取出对应的bucket
var bucket=this.storage[index];
//判断该bucket是否为null
if(bucket==null){
this.storage[index]=[];
}
//判断是否是修改数据
for(var i=0;i<bucket.length;i++){
var tuple=bucket[i];
if(tuple[0]==key){
tuple[1]=value;
return;
}
}
//进行添加操作
bucket.push([key,value]);
this.count += 1;
//判断是否需要进行扩容操作
if(this.count>this.limit*0.75){
var newSize=this.limit*2;
var newPrime=this.getPrime(newSize);
this.resize(newPrime);
}
return;
}
//获取操作
HashTable.prototype.get=function(key){
//根据key得到index
var index=this.hashFunc(key,this,limit);
//根据index得到bucket
var bucket=this.storage[index];
if(bucket==null)return null;
//遍历bucket
for(var i=0;i<bucket.length;i++){
var tuple=bucket[i];
if(tuple[0]==key){
return tuple[1];
}
}
return null;
}
//删除操作
HashTable.prototype.remove=function(key){
//根据key得到index
var index=this.hashFunc(key,this.limit);
var bucket=this.storage[index];
if(bucket==null)return null;
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];
}
}
//缩小容量
if(this.limit>7&&this.count<this.limit*0.25){
var newSize=Math.floor(this.limit/2);
var newPrime=this.getPrime(newSize);
this.resize(newPrime);
}
return null;
}
}
哈希表的扩容/缩容
为什么
我们使用的是链地址法,填装因子可以大于1,哈希表可以无限制的插入新数据
随着数据量的增多,每一个index对应的bucket会越来越长,造成效率降低
所以,在合适的情况对数组进行扩容
怎么做
扩容时,所有的数据像一定要同时进行修改,也就是重新调用哈希函数,来获取到不同的位置
什么时候
loadFactor>0.75时
代码实现
HashTable.prototype.resize=function(newLimit){
var oldStorage=this.storage;
//新的数组属性
this.storage=[];
this.count=0;
this.limit=newLimit;
//遍历
for(var i=0;i<oldStorage.length;i++){
var bucket=oldStorage[i];
//判断bucket是否为null
if(bucket==null)continue;
for(var j=0;j<bucket.length;j++){
var tuple=bucket[j];
//调用哈希表的插入函数
this.put(tuple[0],tuple[1]);
}
}
}
//代码实现容量恒为质数
//先判断某个数字是否为质数(与开平方后的
HashTable.prototype.isPrime=function(num){
var temp=parseInt(Math.sqrt(num));
for(var i=2;i<=temp;i++){
if(num%i==0)
return false;
}
return true;
}
//获取质数的方法
HashTable.prototype.getPrime=function(num){
while(!this.isPrime(num)){
num++;
}
return num;
}
树
二叉树的存储常见方式是数组和链表
使用数组:完全二叉树
(非完全二叉树使用数组会造成很大的空间浪费)
使用链表:二叉树
二叉搜索树(BST)
又叫二叉排序树、二叉查找树
性质
1、非空左子树的所有键值都小于其根结点的键值
2、非空右子树的所有键值都大于其根结点的键值
3、左右子树本身也是二叉搜索树
常见操作
insert(key):像树中插入一个新的键
search(key):在树中查找一个键,如果节点存在,返回true
preOrderTraverse():前序遍历
inOrderTraverse():中序遍历
postOrderTraverse():后序遍历
min:返回树中最小的值
max:返回树中最大的值
remove(key):从树中移除某个键
封装
function BinarySearcchTree(){
//内部节点类
function Node(key){
this.key=key;
this.left=null;
this.right=null;
}
//属性
this.root=null;
//插入节点
BinarySearchTree.prototype.insert=function(key){
var newNode=new Node(key);
if(this.root==null){
this.root=newNode;
}else{
this.insertNode(this.root,newNode);
}
}
BinarySearchTree.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(newNode.key>node.key){ //向右查找
if(node.right==null){
node.right=newNode;
}else{
this.insertNode(node.right,newNode)
}
}
}
}
//先序遍历
BinarySearchTree.prototype.preOrd erTraversal=function(){
this.preOrderTraversalNode(this.root);
}
BinarySearchTree.prototype.preOrderTraversalNode=function(node){
if(node!=null){
alert(node.key);
this.preOrderTraversalNode(node.left);
this.preOrderTraversalNode(node.right);
}
}
//中序遍历
BinarySearchTree.prototype.midOrderTraversal=function(){
this.midOrderTraversalNode(this.root);
}
BinarySearchTree.prototype.midOrderTraversalNode=function(node){
if(node!=null){
this.midOrderTraversalNode(node.left);
alert(node.key);
this.midOrderTraversalNode(node.right);
}
}
//后序遍历
BinarySearchTree.prototype.postOrderTraversal=function(){
this.postOrderTraversalNode(this.root);
}
BinarySearchTree.prototype.postOrderTraversalNode=function(node){
if(node!=null){
this.postOrderTraversalNode(node.left);
this.postOrderTraversalNode(node.right);
alert(node.key);
}
}
//获取最小值
BinarySearchTree.prototype.min=function(){
var key=null;
var node=this.root;
while(node!=null){
key=node.key;
node=node.left;
}
return key;
}
//获取最大值
BinarySearchTree.prototype.max=function(){
var node=this.root;
while(node.right){
node=node.right;
}
return node.key;
}
//搜索某一个数
BinarySearchTree.prototype.search=function(key){
var node=this.root;
while(node!=null){
if(key<node.key){
node=node.left;
}else if(key>node.key){
node=node.right;
}else{
return true;
}
}
return false;
}
//删除某一个节点
BinarySearchTree.prototype.remove=function(key){
//定义变量,保存一些信息
var current=this.root;
var parent=null;
var isLeftChild=true;
//开始寻找删除的节点
while(current.key!=key){
parent=current;
if(key<current.key){
isLeftChild=true;
current=parent.left;
}else{
isLeftChild=false;
current=parent.right;
}
//没有找到节点
if(current==null)return false;
}
//删除的节点是叶子节点
if(current.left==null&¤t.right==null){
if(current==this.root){
this.root=null;
}else if(isLeftChild){
parent.left=null;
}else{
parent.right=null;
}
}
//删除的节点有一个子节点
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;
}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{
//获取后继节点
var successor=this.getSuccessor(current);
//判断是否是根节点
if(current==this.root){
this.root=successor;
}else if(isLeftChild){
parent.left=successor;
}else{
parent.right=successor;
}
successor.left=current.left;
}
}
//找后继节点的方法
BinarySearchTree.prototype.getSuccssor=function(delNode){
//定义变量,保存找到的后继
var successor=delNode;
var current=delNode.right;
var successorParent=delNode;
//循环查找
while(current!=null){
successorParent=successor;
successor=current;
current=current.left;
}
//判断找到的后继节点是否是delNode的right节点
if(successor!=delNode.right){
successorParent.left=successor.right;
successor.right=delNode.right;
}
return successor;
}
}
平衡二叉树
比较好的二叉搜索树数据应该是左右分布均匀,但是插入连续数据后,分布可能不均匀,这种树为非平衡树。一颗平衡二叉树时间复杂度为O(logN),而非平衡二叉树,相当于一个链表,时间复杂度是O(N)
平衡二叉搜索树:红黑树
红黑树(未完)
性质
1、节点是红色或黑色,根节点是黑色
2、每个叶子节点都是黑色的空节点(NIL节点)
3、每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
4、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
特性
1、从根到叶子的最长可能路径,不超过最短可能路径的两倍长
【性质4决定了路径不能有两个相连的红色节点,最短的可能路径都是黑色节点,最长的可能路径是红色和黑色交替】
【性质5所有路径都有相同数目的黑色节点,这就表明没有路径能多于任何其他路径的两倍】
2、这个树基本是平衡的
3、虽然没有做到绝对的平衡,但是可以保证在最坏的情况下,依然是高效的
图论
欧拉七桥
连通图可以一笔画的条件是:
1、奇点的数目是0个或2个(连到一点的边的数目是奇数,称该点为奇点)
2、想要一笔画成,必须中间点全是偶点
3、也就是有来路必有另一条去路,奇点只可能在两端,因此任何图能一笔画成,奇点要么没有要么在两端
图的封装(未完)
function Graph() {
//属性:顶点和边
this.vertexes = [];//数组
this.edges = new Dictionary();//字典
//添加方法
//1、添加顶点的方法
Graph.prototype.addVertex = function(v){
this.vertexes.push(v);
this.edges.set(v, []);
}
//2、添加边的方法
Graph.prototype.addEdge = function(v1, v2){
this.edges.get(v1).push(v2);
this.edges.get(v2).push(v1);//无向图为例
}
}
排序(快排有问题)
大O表示法
O(1):常数的
O(log(n)):对数的
O(n):线性的
O(nlog(n)):线性和对数乘积
O(n^2):平方
O(2^n):指数的
创建列表类
(列表即动态数组)
function ArrayList(){
//属性
this.array = [];
//方法
//将数据插入数组
ArrayList.prototype.insert = function(item){
this.array.push(item);
}
//toString()
ArrayList.prototype.toString = function(){
return this.array.join("-");
}
//封装两数交换
ArrayList.prototype.swap = function(m,n){
var temp = this.array[m];
this.array[m] = this.array[n];
this.array[n] = temp;
}
//冒泡排序
ArrayList.prototype.bubbleSort = function(){
var length = this.array.length;
for(var j=length-1; j>=0; j--){
for(var i=0; i<j; i++){
if(this.array[i] > this.array[i+1])
this.swap(i, i+1);
}
}
}
//选择排序
ArrayList.prototype.selectionSort = function(){
var length = this.array.length;
for(var j=0; j<length-1; j++){
min = j;
for(var i=min+1; i<length; i++){
if(this.array[min]>this.array[i])
min = i;
}
this.swap(min, j);
}
}
//插入排序(核心:局部有序)
//思想:标记符左边部分的数据项是排好序的,右边部分是无序的,取出标记符指向的数据项,将其存储在一个临时变量中,从它左边第一个单元开始,每次将有序的数据项向右移动一个单元,直到存储在临时变量中的数据项可以成功插入
ArrayList.prototype.insertionSort = function(){
var length = this.array.length;
for(var i=1; i<length; i++){
var temp = this.array[i];
var j = i;
while(this.array[j-1]>temp && j>0){
this.array[j] = this.array[j-1];
j--;
}
//将选出的j位置,放入temp元素
this.array[j] = temp;
}
}
//希尔排序(插入排序升级版)
ArrayList.prototype.shellSort = function(){
var length = this.array.length;
//初始化增量
var gap = Math.floor(length/2);
while(gap>=1){
for(var i=gap; i<length; i++){
var temp = this.array[i];
var j = i;
while(this.array[j-gap]>temp && j>gap-1){
this.array[j] = this.array[j-gap];
j -= gap;
}
this.array[j] = temp;
}
gap = Math.floor(gap/2);
}
}
//快速排序(冒泡排序升级版)
//思想:分而治之
//选择枢纽
ArrayList.prototype.median = function(left, right){
var center = Math.floor((left+right)/2);
if(this.array[left]>this.array[center]){
this.swap(left, center);
}
if(this.array[center]>this.array[right]){
this.swap(center, right);
}
if(this.array[left]>this.array[right]){
this.swap(left, right);
}
this.swap(center, right-1);
return this.array[right-1];
}
ArrayList.prototype.quickSort = function(){
var length = this.array.length;
this.quick(0, length-1);
}
ArrayList.prototype.quick = function(left, right){
//结束条件
if(left >= right)return ;
//获取枢纽
var pivot = this.median(left, right);
//定义变量,用于记录当前找到的位置
var i = left;
var j = right-1;
//开始进行交换
while(true){
while(this.array[++i] < pivot){}
while(this.array[--j] > pivot){}
if(i < j){
this.swap(i, j);
}else{
break;
}
}
//将枢纽放在正确的位置,i的位置
this.swap(i, right-1);
//分而治之
this.quick(left, i-1);
this.quick(i+1, right);
}
}
冒泡排序比较次数O(n^2); 交换次数O(n^2)
选择排序比较次数O(n^2); 交换次数O(n)
所以选择排序在执行效率上是高于冒泡排序的
插入排序效率高于冒泡排序和选择排序
一般情况下 希尔排序效率高于插入排序 其时间复杂度超过了O(n^2)
快速排序时间复杂度是O(nlog(n))