在本文中,我将实现8 种图算法,探索 JavaScript 中图的搜索和组合问题(图遍历、最短路径和匹配)。

这些问题是从《Java编程面试要素》一书中借来的。本书中的解决方案是用 Java、Python 或 C++ 编写的,具体取决于书的版本。

尽管问题建模背后的逻辑与语言无关,但我在本文中提供的代码片段使用了一些 JavaScript 警告。

每个问题的每个解决方案都分为 3 个部分:解决方案概述、伪代码,最后是 JavaScript 中的实际代码。

要测试代码并查看它执行预期的操作,您可以使用Chrome 的开发工具在浏览器本身上运行代码段,或使用 NodeJS 从命令行运行它们。

图实现

图的两种最常用的表示是邻接表和邻接矩阵。

我要解决的问题是稀疏图(很少的边),邻接表方法中的顶点操作采用常数(添加一个顶点,O(1))和线性时间(删除一个顶点,O(V+E) ))。所以我会在很大程度上坚持这个实现。

让我们使用adjacency list用一个简单的无向、未加权的图实现来解决这个问题。我们将维护一个对象 (adjacencyList),该对象将包含我们图中的所有顶点作为键。这些值将是所有相邻顶点的数组。在下面的示例中,顶点 1 连接到顶点 2 和 4,因此 adjacencyList: { 1 : [ 2, 4 ] } 等其他顶点。

为了构建图形,我们有两个函数:addVertexaddEdge。addVertex 用于向列表中添加一个顶点。addEdge 用于通过将相邻顶点添加到源和目标数组来连接顶点,因为这是一个无向图。要制作有向图,我们可以简单地删除下面代码中的第 14-16 和 18 行。

在移除一个顶点之前,我们需要遍历相邻顶点的数组并移除所有可能与该顶点的连接。

使用邻接表实现的无向无权图

class Graph {constructor() {this.adjacencyList = {};}addVertex(vertex) {if (!this.adjacencyList[vertex]) {this.adjacencyList[vertex] = [];}}addEdge(source, destination) {if (!this.adjacencyList[source]) {this.addVertex(source);}if (!this.adjacencyList[destination]) {this.addVertex(destination);}this.adjacencyList[source].push(destination);this.adjacencyList[destination].push(source);}removeEdge(source, destination) {this.adjacencyList[source] = this.adjacencyList[source].filter(vertex => vertex !== destination);this.adjacencyList[destination] = this.adjacencyList[destination].filter(vertex => vertex !== source);}removeVertex(vertex) {while (this.adjacencyList[vertex]) {const adjacentVertex = this.adjacencyList[vertex].pop();this.removeEdge(vertex, adjacentVertex);}delete this.adjacencyList[vertex];}
}

图遍历

在上一节中实现图的基础上,我们将实现图遍历:广度优先搜索和深度优先搜索。

广度优先搜索

BFS 一次访问一层节点。为了防止多次访问同一个节点,我们将维护一个访问过的对象。

由于我们需要以先进先出的方式处理节点,因此队列是要使用的数据结构的一个很好的竞争者。时间复杂度为 O(V+E)。

function BFSInitialize an empty queue, empty 'result' array & a 'visited' mapAdd the starting vertex to the queue & visited mapWhile Queue is not empty:- Dequeue and store current vertex- Push current vertex to result array- Iterate through current vertex's adjacency list:- For each adjacent vertex, if vertex is unvisited:- Add vertex to visited map- Enqueue vertexReturn result array

深度优先搜索

DFS 深度访问节点。由于我们需要以后进先出的方式处理节点,因此我们将使用堆栈

从一个顶点开始,我们将相邻的顶点推送到我们的堆栈中。每当一个顶点被弹出时,它就会在我们的访问对象中被标记为已访问。它的相邻顶点被推入堆栈。由于我们总是弹出一个新的相邻顶点,我们的算法将始终探索一个新的级别

我们还可以使用内部堆栈调用来递归地实现 DFS。逻辑是一样的。

时间复杂度和BFS一样,O(V+E)。

function DFSInitialize an empty stack, empty 'result' array & a 'visited' mapAdd the starting vertex to the stack & visited mapWhile Stack is not empty:- Pop and store current vertex- Push current vertex to result array- Iterate through current vertex's adjacency list:- For each adjacent vertex, if vertex is unvisited:- Add vertex to visited map- Push vertex to stackReturn result array
Graph.prototype.bfs = function(start) {const queue = [start];const result = [];const visited = {};visited[start] = true;let currentVertex;while (queue.length) {currentVertex = queue.shift();result.push(currentVertex);this.adjacencyList[currentVertex].forEach(neighbor => {if (!visited[neighbor]) {visited[neighbor] = true;queue.push(neighbor);}});}return result;
}
Graph.prototype.dfsRecursive = function(start) {const result = [];const visited = {};const adjacencyList = this.adjacencyList;(function dfs(vertex){if (!vertex) return null;visited[vertex] = true;result.push(vertex);adjacencyList[vertex].forEach(neighbor => {if (!visited[neighbor]) {return dfs(neighbor);}})})(start);return result;
}
Graph.prototype.dfsIterative = function(start) {const result = [];const stack = [start];const visited = {};visited[start] = true;let currentVertex;while (stack.length) {currentVertex = stack.pop();result.push(currentVertex);this.adjacencyList[currentVertex].forEach(neighbor => {if (!visited[neighbor]) {visited[neighbor] = true;stack.push(neighbor);}});}return result;
}

搜索迷宫

问题陈述:

给定一个黑色和白色条目的二维数组,表示具有指定入口和出口点的迷宫,如果存在的话,找到从入口到出口的路径。– 阿齐兹、阿德南等人。编程面试的要素

我们将用 0 表示白色条目,用 1 表示黑色条目。白色条目代表开放区域,黑色条目墙壁。入口点和出口点由一个数组表示,第 0 个索引和第 1 个索引分别填充了行和列索引。

由二维数组表示的迷宫

解决方案:

  • 为了移动到不同的位置,我们将硬编码方向数组中的四种可能的移动(右、下、左和上;没有对角线移动):
[ [0,1], [1,0], [0,-1], [-1,0] ]
  • 为了跟踪我们已经访问过的单元格,我们将用黑色条目 ( 1's )替换白色条目 ( 0's )。我们基本上是递归地使用DFS来遍历迷宫。将结束递归的基本情况是,我们已经到达退出点并返回 true ,或者我们已经访问了每个白色条目并返回 false
  • 另一个需要跟踪的重要事情是确保我们一直在迷宫的边界内,并且只有在我们处于白色入口时才继续前进。该isFeasible函数将采取照顾。
  • 时间复杂度:O(V+E)

伪代码:

function hasPathStart at the entry pointWhile exit point has not been reached1. Move to the top cell2. Check if position is feasible (white cell & within boundary)3. Mark cell as visited (turn it into a black cell)4. Repeat steps 1-3 for the other 3 directions
var hasPath = function(maze, start, destination) {maze[start[0]][start[1]] = 1;return searchMazeHelper(maze, start, destination);
};
function searchMazeHelper(maze, current, end) { // dfsif (current[0] == end[0] && current[1] == end[1]) {return true;}let neighborIndices, neighbor;// Indices: 0->top,1->right, 2->bottom, 3->left let directions = [ [0,1] , [1,0] , [0,-1] , [-1,0] ];for (const direction of directions) {neighborIndices = [current[0]+direction[0], current[1]+direction[1]];if (isFeasible(maze, neighborIndices)) {maze[neighborIndices[0]][neighborIndices[1]] = 1;if (searchMazeHelper(maze, neighborIndices, end)) {return true;}}}return false;
}
function isFeasible(maze, indices) {let x = indices[0], y = indices[1];return x >= 0 && x < maze.length && y >= 0 && y < maze[x].length && maze[x][y] === 0;
}
var maze = [[0,0,1,0,0],[0,0,0,0,0],[0,0,0,1,0],[1,1,0,1,1],[0,0,0,0,0]]
hasPath(maze, [0,4], [3,2]);

绘制布尔矩阵

问题陈述:

实现一个例程,该例程将 n X m 布尔数组 A 与条目 (x, y) 一起使用,并翻转与 (x, y) 关联的区域的颜色。–阿齐兹、阿德南等人。编程面试的要素

这两种颜色将由 0 和 1 表示。

在下面的示例中,我们从数组 ([1,1]) 的中心开始。请注意,从那个位置,我们只能到达最左边的三角形矩阵。无法到达最右边、最低的位置 ([2,2])。因此,在过程结束时,它是唯一没有翻转的颜色。

nxm 颜色翻转前后的布尔数组

解决方案:

  • 和上一个问题一样,我们将编写一个数组来定义 4 种可能的移动。
  • 我们将使用 BFS 来遍历图。
  • 我们将稍微修改 isFeasible 函数。它仍然会检查新位置是否在矩阵的边界内。另一个要求是新位置的颜色与以前的位置相同。如果新位置符合要求,则其颜色翻转。
  • 时间复杂度:O(mn)

伪代码:

function flipColorStart at the passed coordinates and store the colorInitialize queueAdd starting position to queueWhile Queue is not empty:- Dequeue and store current position- Move to the top cell1. Check if cell is feasible2. If feasible,- Flip color- Enqueue cell3. Repeat steps 1-2 for the other 3 directions
function flipColor(image, x, y) {let directions = [ [0,1] , [1,0] , [0,-1] , [-1,0] ];let color = image[x][y];let queue = [];image[x][y] = Number(!color);queue.push([x,y]);let currentPosition, neighbor;while (queue.length) {currentPosition = queue.shift();for (const direction of directions) {neighbor = [currentPosition[0]+direction[0], currentPosition[1]+direction[1]];if (isFeasible(image, neighbor, color)) {image[neighbor[0]][neighbor[1]] = Number(!color);queue.push([neighbor[0], neighbor[1]]);}}}return image;
}
function isFeasible(image, indices, color) {let x = indices[0], y = indices[1];return x >= 0 && x < image.length && y >= 0 && y < image[x].length && image[x][y] == color;
}
var image = [[1,1,1],[1,1,0],[1,0,1]];
flipColor(image,1,1);

计算封闭区域

问题陈述:

设 A 是一个二维数组,其条目为 W 或 B。编写一个程序,其中 A 将无法到达边界的所有 W 替换为 B。——Aziz、Adnan 等。编程面试的要素

封闭前后的网格

解决方案:

  • 与其遍历所有条目以找到封闭的 W 条目,不如从边界 W 条目开始,遍历图形并标记连接的 W 条目。这些标记的条目保证不被封闭,因为它们连接到板边界上的 W 条目。这种预处理基本上是程序必须实现的内容的补充
  • 然后,再次迭代 A 并将未标记的W 条目(将是封闭的条目)更改为B 条目
  • 我们将使用与 A 相同维度的布尔数组来跟踪标记和未标记的 W 条目。标记的条目将设置为 true。
  • 时间复杂度:O(mn)

伪代码:

function fillSurroundedRegions1. Initialize a 'visited' array of same length as the input arraypre-filled with 'false' values2. Start at the boundary entries3. If the boundary entry is a W entry and unmarked:Call markBoundaryRegion function4. Iterate through A and change the unvisited W entry to B
function markBoundaryRegionStart with a boundary W entryTraverse the grid using BFSMark the feasible entries as true
function fillSurroundedRegions(board) {if (!board.length) {return;}const numRows = board.length, numCols = board[0].length;let visited = [];for (let i=0; i<numRows; i++) {visited.push(new Array(numCols).fill(false, 0, numCols));}for (let i=0; i<board.length; i++) {if (board[i][0] == 'W' && !visited[i][0]) {markBoundaryRegion(i, 0, board, visited);}if (board[i][board.length-1] == 'W' && !visited[i][board.length-1]) {markBoundaryRegion(i, board.length-1, board, visited);}}for (let j=0; j<board[0].length; j++) {if (board[0][j] == 'W' && !visited[0][j]) {markBoundaryRegion(0, j, board, visited);}if (board[board.length-1][j] == 'W' && !visited[board.length-1][j]) {markBoundaryRegion(board.length-1, j, board, visited);}}for (let i=1; i<board.length-1; i++) {for (let j=1; j<board.length-1; j++) {if (board[i][j] == 'W' && !visited[i][j]) {board[i][j] = 'B';}}}return board;
}
function markBoundaryRegion(i, j, board, visited) {let directions = [ [0,1] , [1,0] , [0,-1] , [-1,0] ];const queue = [];queue.push([i,j]);visited[i][j] = true;let currentPosition, neighbor;while (queue.length) {currentPosition = queue.shift();for (const direction of directions) {neighbor = [i+direction[0], j+direction[1]];if (isFeasible(board,visited,neighbor)) {visited[neighbor[0]][neighbor[1]] = true;queue.push(neighbor);}}}
}
function isFeasible(board, visited, neighbor) {let x = neighbor[0], y = neighbor[1];return x >= 0 && x < board.length && y >= 0 && y < board[x].length && board[x][y] == 'W';
}
var board = [['B','B','B','B'],['W','B','W','B'],['B','W','W','B'],['B','B','B','B']];
fillSurroundedRegions(board);

死锁检测(有向图中的循环)

问题陈述:

一种死锁检测算法使用“等待”图来跟踪进程当前阻塞的其他进程。在等待图中,进程被表示为节点,从进程 P 到 0 的边意味着 0 持有 P 需要的资源,因此 P 正在等待 0 释放对该资源的锁。此图中的循环意味着可能出现死锁。这引发了以下问题。
编写一个程序,将一个有向图作为输入并检查该图是否包含一个环。–阿齐兹、阿德南等人。编程面试的要素

在上面的等待图中,我们的死锁检测程序将检测到至少一个循环并返回 true。

对于这个算法,我们将使用有向图的稍微不同的实现来探索其他数据结构。我们仍然使用邻接表来实现它,但是我们将顶点存储在一个数组中,而不是一个对象(地图)。

这些进程将被建模为从第 0 个进程开始的顶点。的依赖关系的处理之间将被建模为边缘的顶点之间。的边缘(相邻顶点)将被存储在一个链表,依次存储在索引处对应于所述处理号。

class Node {constructor(data) {this.data = data;this.next = null;}
}
class LinkedList {constructor() {this.head = null;}insertAtHead(data) {let temp = new Node(data);temp.next = this.head;this.head = temp;return this;}getHead() {return this.head;}
}
class Graph {constructor(vertices) {this.vertices = vertices;this.list = [];for (let i=0; i<vertices; i++) {let temp = new LinkedList();this.list.push(temp);}}addEdge(source, destination) {if (source < this.vertices && destination < this.vertices) {this.list[source].insertAtHead(destination);}return this;}
}

等待图(一)实现

解决方案:

  • 每个顶点将被分配3 种不同的颜色:白色、灰色和黑色。最初所有顶点都将被着色为白色。当一个顶点被处理时,它会被着色为灰色,处理后会被着色为黑色
  • 使用深度优先搜索遍历图形。
  • 如果从一个灰色顶点到另一个灰色顶点有一条边,我们就发现了一条后边(一个自环或一条连接到其祖先之一的边),因此检测到一个循环
  • 时间复杂度:O(V+E)

伪代码:

function isDeadlockedColor all vertices whiteRun DFS on the vertices1. Mark current node Gray2. If adjacent vertex is Gray, return true3. Mark current node BlackReturn false
const Colors = {WHITE: 'white', GRAY: 'gray', BLACK: 'black'
}
Object.freeze(Colors);
function isDeadlocked(g) {let color = [];for (let i=0; i<g.vertices; i++) {color[i] = Colors.WHITE;}for (let i=0; i<g.vertices; i++) {if (color[i] == Colors.WHITE) {if (detectCycle(g, i, color)) {return true;}   }}return false;
};
function detectCycle(g, currentVertex, color) {color[currentVertex] = Colors.GRAY;let neighbor;let nextNode = g.list[currentVertex].getHead();while (nextNode !== null) {neighbor = nextNode.data;if (color[neighbor] == Colors.GRAY) {return true;}if (color[neighbor] == Colors.WHITE && detectCycle(g, neighbor, color)) {return true;}}color[currentVertex] = Colors.BLACK;return false;
}
let g = new Graph(3);
g.addEdge(0,1);
g.addEdge(0,2);
isDeadlocked(g);

克隆图

问题陈述:

考虑有向图的顶点类型,其中有两个字段:整数标签和对其他顶点的引用列表。设计一个算法,该算法引用顶点 u,并在可从 u 到达的顶点上创建图的副本。返回你的副本。–阿齐兹、阿德南等人。编程面试的要素

解决方案:

  • 维护一个地图原来的顶点与其对应映射。复制边缘。
  • 使用 BFS 访问相邻的顶点(边)。
  • 时间复杂度:O(n),其中 n 是节点总数。

伪代码:

function cloneGraphInitialize an empty mapRun BFSAdd original vertex as key and clone as value to mapCopy over edges if vertices exist in mapReturn clone
class GraphVertex {constructor(value) {this.value = value;this.edges = [];}
}
function cloneGraph(g) {if (g == null) {return null;}let vertexMap = {};let queue = [g];vertexMap[g] = new GraphVertex(g.value);while (queue.length) {let currentVertex = queue.shift();currentVertex.edges.forEach(v => {if (!vertexMap[v]) {vertexMap[v] = new GraphVertex(v.value);queue.push(v);}vertexMap[currentVertex].edges.push(vertexMap[v]);});}return vertexMap[g];
}
let n1 = new GraphVertex(1);
let n2 = new GraphVertex(2);
let n3 = new GraphVertex(3);
let n4 = new GraphVertex(4);
n1.edges.push(n2, n4);
n2.edges.push(n1, n3);
n3.edges.push(n2, n4);
n4.edges.push(n1, n3);
cloneGraph(n1);

进行有线连接

问题陈述:

设计一种算法,该算法采用一组引脚和一组连接引脚对的电线,并确定是否可以将一些引脚放置在 PCB 的左半部分,其余的放置在右半部分,这样每根导线都是左右两半之间。如果存在,则返回这样的除法。–阿齐兹、阿德南等人。编程面试的要素

这种划分的一个例子

解决方案:

  • 将集合建模为图形。引脚由顶点表示,连接它们的线是边。我们将使用边列表来实现图。

问题陈述中描述的配对只有当顶点(引脚)可以分为“2 个独立的集合,U 和 V,使得每条边 (u,v) 要么连接从 U 到 V 的顶点,要么连接来自 V 的顶点给你。” (来源)这样的图被称为二部图

为了检查图是否是二部图,我们将使用图着色技术。由于我们需要两组引脚,我们必须检查图形是否为 2 色(我们将其表示为 0 和 1)。

最初,所有顶点都未着色 (-1)。如果相邻顶点被分配相同的颜色,则该图不是二部图。不可能仅使用 2 种颜色为具有奇数长度循环的图形交替分配两种颜色,因此我们可以贪婪地为图形着色。

额外步骤:我们将处理未连接的图的情况。外部 for 循环通过迭代所有顶点来处理这个问题。

  • 时间复杂度:O(V+E)

伪代码:

function isBipartite1. Initialize an array to store uncolored vertices2. Iterate through all vertices one by one3. Assign one color (0) to the source vertex4. Use DFS to reach the adjacent vertices5. Assign the neighbors a different color (1 - current color)6. Repeat steps 3 to 5 as long as it satisfies the two-colored     constraint7. If a neighbor has the same color as the current vertex, break the loop and return false
function isBipartite(graph) {let color = [];for (let i=0; i<graph.length; i++) {color[i] = -1;}for (let i=0; i<graph.length; i++) {if (color[i] == -1) {let stack = [];stack.push(i);color[i] = 0;let node;while (stack.length) {node = stack.pop();for (const neighbor of graph[node]) {if (color[neighbor] == -1) {stack.push(neighbor);color[neighbor] = 1 - color[node];}else if (color[neighbor] == color[node]) {return false;}}}}}return true;
}
isBipartite([[],[2,4,6],[1,4,8,9],[7,8],[1,2,8,9],[6,9],[1,5,7,8,9],[3,6,9],[2,3,4,6,9],[2,4,5,6,7,8]]);

将一个字符串转换为另一个字符串

问题陈述:

给定字典 D 和两个字符串 s 和 f,编写一个程序来确定 s 是否生成 t。假设所有字符都是小写字母。如果s确实产生f,则输出最短产生序列的长度;否则,输出-1。–阿齐兹、阿德南等人。编程面试的要素

例如,如果字典 D 是 ["hot", "dot", "dog", "lot", "log", "cog"],则 s 是“hit”,t 是“cog”,则最短的生产顺序是 5.
"hit" -> "hot" -> "dot" -> "dog" -> "cog"

解决方案:

  • 表示字符串作为顶点在无向,非加权曲线图,一个边缘2个顶点之间如果相应的字符串中不同的一个字符最多。我们将实现一个函数 (compareStrings) 来计算两个字符串之间的字符差异。
  • 捎带上一个例子,我们图中的顶点将是
{hit, hot, dot, dog, lot, log, cog}

我们在第 0 节图实现中讨论的邻接表方法表示的边将是:

{"hit": ["hot"],"hot": ["dot", "lot"],"dot": ["hot", "dog", "lot"],"dog": ["dot", "lot", "cog"],"lot": ["hot", "dot", "log"],"log": ["dog", "lot", "cog"],"cog": ["dog", "log"]
}
  • 一旦我们完成了图的构建,问题就归结为找到从开始节点到结束节点的最短路径。这可以使用广度优先搜索自然计算。
  • 时间复杂度:O(M x M x N),其中 M 是每个单词的长度,N 是字典中的单词总数。

伪代码:

function compareStringsCompare two strings char by charReturn how many chars differ
function transformString1. Build graph using compareStrings function. Add edges if and only if  the two strings differ by 1 character2. Run BFS and increment length3. Return length of production sequence
function transformString(beginWord, endWord, wordList) {let graph = buildGraph(wordList, beginWord);if (!graph.has(endWord)) return 0;let queue = [beginWord];let visited = {};visited[beginWord] = true;let count = 1;while (queue.length) {let size = queue.length;for (let i=0; i<size; i++) {let currentWord = queue.shift();if (currentWord === endWord) {return count;}graph.get(currentWord).forEach( neighbor => {if (!visited[neighbor]) {queue.push(neighbor);visited[neighbor] = true;}})}count++;}return 0;
};function compareStrings (str1, str2) {let diff = 0;for (let i=0; i<str1.length; i++) {if (str1[i] !== str2[i]) diff++}return diff;
}function buildGraph(wordList, beginWord) {let graph = new Map();wordList.forEach( (word) => {graph.set(word, []);wordList.forEach( (nextWord) => {if (compareStrings(word, nextWord) == 1) {graph.get(word).push(nextWord);}})})if (!graph.has(beginWord)) {graph.set(beginWord, []);wordList.forEach( (nextWord) => {if (compareStrings(beginWord, nextWord) == 1) {graph.get(beginWord).push(nextWord);}})}return graph;
}

希望在本文结束时,您已经意识到图问题中最具挑战性的部分是确定如何将问题建模为图。从那里,您可以使用/修改两个图形遍历来获得预期的输出。

您的工具包中可以使用的其他图形算法包括:

  • 拓扑排序
  • 最短路径算法(Dijkstra 和 Floyd Warshall)
  • 最小生成树算法(Prim 和 Kruskal)

最后,喜欢本文的小伙伴记得给小普一个赞~更多干货关注公公众号:普适极客。

必看干货:如何在 JavaScript 中实现 8 种基本图形算法相关推荐

  1. regexp 好汉字符串_如何在JavaScript中使用RegExp确认字符串的结尾

    regexp 好汉字符串 by Catherine Vassant (aka Codingk8) 由凯瑟琳·瓦森(Catherine Vassant)(又名Codingk8) 如何在JavaScrip ...

  2. !! javascript_产量! 产量! 生成器如何在JavaScript中工作。

    !! javascript by Ashay Mandwarya ?️?? 由Ashay Mandwarya提供吗? 产量! 产量! 生成器如何在JavaScript中工作. (Yield! Yiel ...

  3. 如何在JavaScript中实现堆栈和队列?

    本文翻译自:How do you implement a Stack and a Queue in JavaScript? What is the best way to implement a St ...

  4. 如何在JavaScript中比较数组?

    本文翻译自:How to compare arrays in JavaScript? I'd like to compare two arrays... ideally, efficiently. 我 ...

  5. 如何在JavaScript中验证电子邮件地址

    如何在JavaScript中验证电子邮件地址? #1楼 与squirtle相比 ,这是一个复杂的解决方案,但是在正确验证电子邮件方面做得非常出色: function isEmail(email) { ...

  6. 如何在JavaScript中区分深层副本和浅层副本

    by Lukas Gisder-Dubé 卢卡斯·吉斯杜比(LukasGisder-Dubé) 如何在JavaScript中区分深层副本和浅层副本 (How to differentiate betw ...

  7. vj节点_创意编码—如何在JavaScript中创建VJ引擎

    vj节点 by George Gally 通过乔治·加利 创意编码-如何在JavaScript中创建VJ引擎 (Creative Coding - How to create a VJ engine ...

  8. javascript编写_如何在JavaScript中使用解构来编写更简洁,功能更强大的代码

    javascript编写 by Ashay Mandwarya ?️?? 由Ashay Mandwarya提供吗? 如何在JavaScript中使用解构来编写更简洁,功能更强大的代码 (How to ...

  9. 如何在 JavaScript 中实现拖放

    来源:http://www.javaeye.com/post/152142 译者说明: 终于完成了全文的翻译,由于时间比较参促,文章没有过多的校正与润色,阅读过程中难免会有些许生硬或不准确的感觉,请大 ...

最新文章

  1. 传统软件的云计算之路
  2. [转]深刻理解Python中的元类(metaclass)
  3. build FLTK 1.1.7 with gcc 2.95.3 on Fedora7
  4. 程序、进程以及线程的爱恨情仇
  5. 爱因斯坦一生最伟大的问候
  6. Linux进阶之路————进程与服务管理
  7. [编辑修订01]git 常用命令和Gerrit的联合使用常用命令
  8. 基于数据库数据增量同步_基于 Flink SQL CDC 的实时数据同步方案
  9. MLP、RBF、SVM网络比较及其应用前景
  10. Innosetup打包自动下载.net framework 动态库及替换卸载程序图标.
  11. Unity3D C#数学系列之求点到直线的距离
  12. CMOS模拟集成电路笔记(第一部分)
  13. html 空格怎么打,空白空格怎么打?
  14. css去掉按钮默认样式教程_50个针对设计师CSS3按钮教程
  15. RDL 报表 - 查询条件使用下拉框
  16. 如何安装wordcloud?
  17. Mysql数据库可视化工具Navicat使用
  18. Oracle甲骨文(北京中关村)授权学习中心 简介
  19. cocos2dx 更改帧频
  20. 行列向量的维数和个数的关系【三秩相等作为桥梁】

热门文章

  1. 面试大厂背怼!这都搞不定,你只能做“搬运工”!
  2. 肖仰华:知识图谱构建的三要素、三原则和九大策略 | AI ProCon 2019
  3. 来学习几个简单的Hive函数吧!
  4. 特斯拉被曝储存大量未加密个人数据 | 极客头条
  5. 扎克伯格、张一鸣、宿华都公开道歉,AI算法不灵了吗?
  6. 快讯 | 百度发布Apollo1.5 开放五大核心能力,未来3年花100亿投资100家公司
  7. 监控告警满飞天,运维在家睡到自然醒...
  8. Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!
  9. 面试官问:Kafka 会不会丢消息?怎么处理的?
  10. 组合特征(五)countvector(w)+doc(w)+hash(w)