算法
leetcode刷题笔记javascript
JavaScript算法与数据结构
冒泡、简单选择、直接插入、希尔、快速、归并、堆排序算法:JavaScript实现+分析
二叉树相关JavaScript实现:中序、先序、后序、层次遍历
leetcode刷题笔记javascript:链表相关
Leetcode刷题JavaScript:括号相关
背包问题(JavaScript实现动态规划解决)
补充用JavaScript写KMP算法
数据结构
注意结合使用场景思考操作性能,e.g:
队列的插入和删除
链表与数组,分别更易于删除与插入
双向链表,删除与插入的复杂度是 O(1)
有序数组的二分查找,但是有序数组的插入和删除缓慢
散列表的读取和插入的复杂度是 O(1),但是它不保持顺序。
所以二叉树:
插入O(logN)
链表
JavaScript中数组的主要问题是,数组被实现成了对象,效率很低
所以可以考虑在 不需要随机访问的场景里,用链表代替数组
function Node(element) {
this.element = element;
this.next = null;
}
function LList() {
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
function remove(item) {
var prevNode = this.findPrevious(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
function findPrevious(item) {
var currNode = this.head;
while (!(currNode.next == null) &&
(currNode.next.element != item)) {
currNode = currNode.next;
}
return currNode;
}
function display() {
var currNode = this.head;
while (!(currNode.next == null)) {
console.log(currNode.next.element);
currNode = currNode.next;
}
}
function find(item) {
var currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
}
function insert(newElement, item) {
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
var cities = new LList();
cities.insert("Conway", "head");
cities.insert("Russellville", "Conway");
cities.insert("Carlisle", "Russellville");
cities.insert("Alma", "Carlisle");
cities.display();
console.log();
cities.remove("Carlisle");
cities.display();
栈
function Stack() {
let items=[];
this.push=function(ele){
return items.push(ele);
};
this.pop=function() {
return items.pop();
};
this.peek=function() {
return items[items.length-1];
};
this.isEmpty=function() {
return items.length===0
};
this.clear=function() {
items=[];
};
}
let temp=new Stack();
temp.push(9);
console.log(temp.peek());
栈:十进制=>二进制
function Stack() {
let items=[];
this.push=function(ele){
return items.push(ele);
};
this.pop=function() {
return items.pop();
};
this.peek=function() {
return items[items.length-1];
};
this.isEmpty=function() {
return items.length===0
};
this.clear=function() {
items=[];
};
this.toString=function(){
return items.toString();
}
}
let temp=new Stack();
temp.push(9);
console.log(temp.peek());
function bin(num){
let remStack=new Stack();
let rem=undefined;
let binaryString='';
console.log('temp.peek()');
while(num>0){
rem=Math.floor(num%2);//除以2取余数
remStack.push(rem);
num=Math.floor(num/2);
}
while(!remStack.isEmpty()){//倒徐输出余数
binaryString+=remStack.pop();
}
return binaryString;
}
console.log(bin(4));
用栈模拟递归
function fact(n){
if(n===0){
return 1;
}else{
return n*fact(n-1);
}
}
function fact(n){
let s=new Stack();
while(n>1){
s.push(n--);
}
let product=1;
while(s.length()>0){
product*=s.pop();
}
return product;
}
队列
用于基数排序
queue[nums[i]%10].enqueue(nums[i]);
优先队列
实现一个优先队列:
设置优先级,在对应位置添加元素
添加优先级的数据记录,dequeue前顺序查找最高优先级
function PriorityQueue (){
let items=[];
function QueueEle(ele,pri){//单个队节点
this.ele=ele;
this.pri=pri;
}
this.enqueue=function(ele,pri){
let queueEle=new QueueEle(ele,pri);
if (!items.length){
items.push(queueEle);
}else{
let added=false;
for(let i=0;i<items.length;i++){
if(queueEle.pri<items[i],pri){//插入中间
items.splice(i,0,queueEle);
added=true;
break;
}
}
if(!added){
items.push(queueEle);
}
}
};
}
let temp=new PriorityQueue();
temp.enqueue("john",2)
temp.enqueue("jack",4)
temp.enqueue("hh",5)
console.log(temp);
循环队列:击鼓传花
function Queue(){
let items=[];
this.enqueue=function(ele){
items.push(ele);
};
this.dequeue=function(){
return items.shift();
};
this.front=function(){
return items[0];
};
this.isEmpty=function(){
return items.lenght==0;
};
this.clear=function(){
items=[];
};
this.size=function(){
return items.lenght;
};
}
function hotPotato (nameList,num){
let queue=new Queue();
for(let i=0; i<nameList.length; i++){
queue.enqueue(nameList[i]);
}
let out= '';
while (queue.size()>1){
for(let i=0;i<num;i++){
queue.enqueue(queue.dequeue());
}
out=queue.dequeue();
console.log(out+'淘汰');
}
return queue.dequeue();
}
let names=['John','Jack','Camila'];
let winner=hotPotato(names,3);
console.log('胜利者:'+winner);
散列表
散列数组的长度最好是质数(和散列函数取余相关)
散列函数:给定一个键值,然后返回值在表中的作用
散列表和散列集合就是利用散列函数去获取位置进行增删查改的数据结构
解决哈希冲突:分离链接/开链法(特定位置-链表/数组)、线性探查。数组大小与待存储数据的多少来选择
function HashTable() {
this.table = new Array(137);
this.simpleHash = simpleHash;
this.betterHash = betterHash;
this.showDistro = showDistro;
this.put = put;
this.get = get;
}
// put for linear probing
function put(key, data) {
var pos = this.betterHash(key);
if (this.table[pos] == undefined) {
this.table[pos] = key; // table存放key
this.values[pos] = data; // values 存放值
}
else {
while (this.table[pos] != undefined) {
pos++;
}
this.table[pos] = key;
this.values[pos] = data;
}
}
// get for linear probing
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[i] == key) {
return this.values[i];
}
}
}
return undefined;
}
// put for separate chaining
function put(key, data) {
var pos = this.betterHash(key);
var index = 0;
if (this.table[pos][index] == undefined) {
this.table[pos][index] = data;
}
++index;
else {
while (this.table[pos][index] != undefined) {
++index;
}
this.table[pos][index] = data;
}
}
// get for separate chaining
function get(key) {
var index = 0;
var hash = this.betterHash(key);
if (this.table[pos][index] = key) {
return this.table[pos][index+1];
}
index += 2;
else {
while (this.table[pos][index] != key) {
index += 2;
}
return this.table[pos][index+1];
}
return undefined;
}
散列表:去重优化算法
散列表,读取和插入的复杂度是 O(1)
function hasDuplicate(array){
let existingValues= {};
for(let i=0;i<array.length;i++){
if(existingValue[array[i]]===undefined) {
existingValue[array[i]]=1;
}else{
//existingValue[array[i]]++;不返回可以实现计数,用于计分
return true;
}
}
return false;
}
散列表实现图
树
BST搜索二叉树
图
深度优先:通过将顶点存入栈,顶点是
广度优先:通过及那个顶点存入队列,
顶点有三个状态:未访问white、未探索grey、已访问且探索black。采用辅助数组state
队列_实现广度优先
需要借助队列
function Queue(){
let items=[];
this.enqueue=function(ele){
items.push(ele);
};
this.dequeue=function(){
return items.shift();
};
this.front=function(){
return items[0];
};
this.isEmpty=function(){
return items.lenght==0;
};
this.clear=function(){
items=[];
};
this.size=function(){
return items.lenght;
};
}
function Graph() {
let vertices=[];// 所有顶点
let adjList=new Map();// 邻接表
this.addVertex=function(v){
vertices.push(v);
adjList.set(v,[]);
};
this.addEdge=function(v,w){
adjList.get(v).push(w);
adjList.get(w).push(v);
};
this.bfs=function (v,callback){
const initialState=function(){
let state=[];
for(let i=0;i<vertices.length;i++){
state[vertices[i]]='white';
}
return state;
}
let state=initialState();
let queue=new Queue();
for(let i=0;i<vertices.length;i++){
queue.enqueue(vertices[i]);
}
while(!queue.isEmpty()){
let u=queue.dequeue();
let neighbors=adjList.get(u);
state[u]='grey';
for(let i=0;i<neighbors.length;i++){
let w=neighbors[i];
if(state[w]==='white'){
state[w]='grey';
queue.enqueue(w);
}
}
state[u]='black';
if(callback){
callback(u);
}
}
}
}
let g=new Graph();
let node=['a','b','c'];
for(let i=0;i<node.length;i++){
g.addVertex(node[i]);
}
g.addEdge('a','b');
g.addEdge('a','c');
g.addEdge('b','c');
console.log(g);
console.log(g.bfs(g));
深度优先遍历
function Graph(v) {
this.vertices = v; // 五个点
this.edges = 0; // 记录边的条数
this.adj = []; // 五个点的邻点数组
for (let i = 0; i < this.vertices; ++i) {
this.adj[i] = [];
//this.adj[i].push("");
}
this.marked = [];
for (let i = 0; i < this.vertices; ++i) { // 初始化marked数组
this.marked[i] = false;
}
this.addEdge = function addEdge(v,w) {
this.adj[v].push(w);
this.adj[w].push(v);
this.edges++;
}
this.showGraph = function showGraph() {
for (let i = 0; i < this.vertices; ++i) {
console.log(i + " -> ");
for (let j = 0; j< this.vertices; ++j) {
if (this.adj[i][j] != undefined)
console.log(this.adj[i][j]);
}
}
}
this.dfs = function dfs(v) {
console.log("mar: " + this.marked);
console.log("adj: " + this.adj[v]);
if (!this.marked[v]) {
console.log("Visited vertex: " + v);
this.marked[v] = true;
}
for (let w in this.adj[v]) {
if (!this.marked[w]) {
console.log("w: " + w);
this.dfs(w);
}
}
}
}
g = new Graph(5);
g.addEdge(0,1);
g.addEdge(0,2);
g.addEdge(1,3);
g.addEdge(2,4);
g.showGraph();
g.dfs(0);
递归
几乎所有的循环都可以用递归代替
注意计算机是用栈来记录每个调用中的函数,无限递归很危险=>栈溢出
预防递归
递归适用于无法预估计算深度的问题
JavaScript中,用递归解决问题简洁但是不高效。多数指令式编程语言和面向对象的编程语言对递归的实现不够完善,没有把递归作为高级编程的特性,不能高效地将递归代码解释为机器代码。
===>递归动态规划
快速排序在日常的平均情况中,表现优异。
function quick_part(arr,left,right){
//留下left和right后面递归
var l = left;
var r = right;
var basic= arr[left]; //arr[left]即basic的原位置
if(left >= right){ //如果数组只有一个元素
return;
}
while(l!=r){//两者相遇,意味着一直到找到basic的位置
while(arr[r] > basic && l < r){ //l<r随时注意严格控制哨兵不能错过,从右边向左找第一个比key小的数,找到或者两个哨兵相碰,跳出循环
r--;
}
while(arr[l] <= basic && l < r){ //这里的=号保证在本轮循环结束前,key的位置不变,否则的话跳出循环,交换l和left的位置的时候,left位置的上元素有可能不是key
l++;
}
//1、两个哨兵到找到了目标值。2、j哨兵找到了目标值。3、两个哨兵都没找到(key是当前数组最小值)
if(l!=r){ //交换两个元素的位置
const swap = arr[l];
arr[l] = arr[r];
arr[r] = swap;
}
}
arr[left] = arr[l] //arr[left]即basic的原位置
arr[l] = basic;
quick_part(arr,left,l-1);
quick_part(arr,l+1,right);
}
function quickSort(arr){
quick_part(arr,0,arr.length-1);
}