JavaScript diagrama de estructura de ejecución

JavaScript diagrama de estructura de ejecución

En primer lugar, la teoría de grafos

Introducción 1.1. Mapa

¿Qué es una vista?

  • estructura de la figura es una la estructura de árbol algo similar a una estructura de datos;
  • La teoría de grafos es una rama de las matemáticas, y, en matemáticas, un gráfico de árbol;
  • La imagen muestra la teoría de grafos para el estudio, la investigación vértices y bordes compuestas de gráfica teoría matemática y métodos;
  • El objetivo principal del estudio es: conexiones entre las cosas , el ápice representante de cosas , secundarios representantes entre dos cosas las relaciones ;

Figura características:

  • Un conjunto de vértices : por lo general V (Vertex) denota el conjunto de vértices;
  • Un conjunto de aristas : por lo general E representa el conjunto de bordes (borde);
    • Bordes trazada entre el vértice y el vértice;
    • Lado se puede dirigir también ser dirigidos. A ---- B representa ejemplo no dirigidos, A ---> B representa dirigida;

La figura utiliza comúnmente términos:

  • Vértices: representa un gráfico de los nodos ;

  • Edge: representa el vértice y vértices entre una conexión ;

  • vértices adyacentes: están conectados por un borde con vértice refiere vértices adyacentes ;

  • De: un vértice de un número de vértices adyacentes ;

  • ruta de acceso:

    • camino simple: una petición de ruta sencilla no contiene vértices duplicados;
    • Loop: El primero y último vértice de un vértice misma trayectoria a lo largo del bucle;
  • grafo no dirigido: la figura todos los bordes son sin dirección;

  • grafo dirigido: todos los bordes en el gráfico son allí direcciones;

  • Figura ningún derecho: no hay bordes derecho en el gráfico no detecta ningún pesos pesados;

  • La figura ponderado: tener ciertos significados pesos de grafo ponderado;

Representa 1.2. Figura.

matriz de adyacencia

La figura expresa comúnmente como: matriz de adyacencia .

  • Bidimensional matriz puede ser usado para representar la matriz de adyacencia;

  • Deje matriz de adyacencia para cada nodo y un número entero asociado , el valor entero como un subíndice de una matriz ;

  • Usando una matriz de dos dimensiones para representar los vértices entre la conexión ;

imagen-20200303213913574

Como se muestra en la figura:

  • array bidimensional de 0 indica que no hay conexión, 1 indica que hay conexión;
  • Tales como: A [0] [3] = 1, representa la conexión entre A y C;
  • valores de la matriz de adyacencia Diagonal son cero, mostrando A - A, B - B, y similares desde el circuito no están conectados (sin conexión entre él y ellos mismos);
  • Si, la matriz de adyacencia es un gráfico no dirigido debe ser simétrica en los elementos de matriz diagonales son todas 0;

matriz de adyacencia del problema:

  • Si un gráfico es grafos dispersos , la matriz de adyacencia en la presencia de un gran número de 0 , lo que resulta en pérdida de espacio de almacenamiento;
lista de adyacencia

Figura Otra forma común de representar: mesa de adyacencia .

  • La figura mesa adyacente en cada uno de los vértices y los vértices adyacentes y una lista de vértices composición;
  • Esta lista se almacena en un número de maneras, tales como: una matriz / lista / diccionario (tabla hash) y así pueden;

imagen-20200303215312091

Como se muestra en la figura:

  • Se puede ver claramente en la Fig. A adyacente a la B, C, D , A y si queremos representar los vértices adyacentes al vértice (borde), se almacenan en un valor correspondiente a la (valor) de la Un array / cadena / diccionario de.
  • Entonces, A se puede retirar muy fácilmente por una clave correspondiente de datos (tecla);

lista de adyacencia de preguntas:

  • Adyacencia tabla puede ser simplemente extrae fuera grado , es decir, un número de puntos de vértice a otro vértice;
  • Sin embargo, el cálculo de la tabla adyacente -Grado (el número de otro punto de vértice a un vértice se llama el grado del vértice) difícil. En este caso necesidad de construir una tabla de adyacencia inversa para calcular el grado efectivo;

En segundo lugar, la estructura del paquete de la figura.

En el proceso de implementación utilizando la lista de adyacencia de aristas que representan manera, utilizando la clase diccionario para almacenar la tabla adyacente.

2.1. Añadiendo el diccionario colas de clase y de clase

En primer lugar necesidad de introducir, para ser seguido clases diccionarios y las colas de clase utilizados antes de la aplicación:

    //封装字典类
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){
    //1.判断字典中是否有这个key
    if(!this.has(key)) return false

    //2.从字典中删除key
    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)
  }

  //六.size方法
  Dictionary.prototype.keys = function(){
    return this.keys().length
  }

  //七.clear方法
  Dictionary.prototype.clear = function(){
    this.items = {}
  }
}

   // 基于数组封装队列类
    function Queue() {
    // 属性
      this.items = []
    // 方法
    // 1.将元素加入到队列中
    Queue.prototype.enqueue = element => {
      this.items.push(element)
    }

    // 2.从队列中删除前端元素
    Queue.prototype.dequeue = () => {
      return this.items.shift()
    }

    // 3.查看前端的元素
    Queue.prototype.front = () => {
      return this.items[0]
    }

    // 4.查看队列是否为空
    Queue.prototype.isEmpty = () => {
      return this.items.length == 0;
    }

    // 5.查看队列中元素的个数
    Queue.prototype.size = () => {
      return this.items.length
    }

    // 6.toString方法
    Queue.prototype.toString = () => {
      let resultString = ''
        for (let i of this.items){
          resultString += i + ' '
        }
        return resultString
      }
    }

2.2. Creación de gráficos

Crear gráficos Gráfico, y añadir propiedades básicas, método comúnmente utilizado de la re-implementación de la clase de dibujo:

    //封装图类
    function Graph (){
      //属性:顶点(数组)/边(字典)
      this.vertexes = []  //顶点
      this.edges = new Dictionary() //边
      }

2.3 Añadir el vértice y el borde

Como se muestra:

imagen-20200303235132868

Crear un mapa de memoria vértices objeto Array vértice, la creación de un diccionario bordes laterales objeto de mapa de memoria, en el que la clave de vértice, almacenamiento de valor de matriz vértice clave de vértices adyacentes.

la implementación del código:

      //添加方法
      //一.添加顶点
      Graph.prototype.addVertex = function(v){
        this.vertexes.push(v)
        this.edges.set(v, []) //将边添加到字典中,新增的顶点作为键,对应的值为一个存储边的空数组
      }
      //二.添加边
      Graph.prototype.addEdge = function(v1, v2){//传入两个顶点为它们添加边
        this.edges.get(v1).push(v2)//取出字典对象edges中存储边的数组,并添加关联顶点
        this.edges.get(v2).push(v1)//表示的是无向表,故要添加互相指向的两条边
      }

2.4. Convertir una cadena a partir de la

La figura añadiendo Graph método toString clase, implementado en la forma de la lista de adyacencia de cada figura vértice de salida.

la implementación del código:

      //三.实现toString方法:转换为邻接表形式
      Graph.prototype.toString = function (){
        //1.定义字符串,保存最终结果
        let resultString = ""

        //2.遍历所有的顶点以及顶点对应的边
        for (let i = 0; i < this.vertexes.length; i++) {//遍历所有顶点
          resultString += this.vertexes[i] + '-->'
          let vEdges = this.edges.get(this.vertexes[i])
          for (let j = 0; j < vEdges.length; j++) {//遍历字典中每个顶点对应的数组
            resultString += vEdges[j] + '  ';
          }
          resultString += '\n'
        }
        return resultString
      }

Código de ensayo:

   //测试代码
    //1.创建图结构
    let graph = new Graph()

    //2.添加顶点
    let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }

    //3.添加边
    graph.addEdge('A', 'B')
    graph.addEdge('A', 'C')
    graph.addEdge('A', 'D')
    graph.addEdge('C', 'D')
    graph.addEdge('C', 'G')
    graph.addEdge('D', 'G')
    graph.addEdge('D', 'H')
    graph.addEdge('B', 'E')
    graph.addEdge('B', 'F')
    graph.addEdge('E', 'I')

    //4.输出结果
    console.log(graph.toString());

Resultados del ensayo:

imagen-20200303233737451

2.5. Traversal Graph

Gráfico recorrido pensó:

  • Como transversal poligonal las ideas y los pensamientos del diagrama de árbol, lo que significa que necesitará calcular todos los vértices son visitados nuevamente, y no se puede tener acceso duplicado (método toString anterior se repetirá visitas);

Gráfico de recorrido de dos algoritmos:

  • BFS (Amplitud - First Search, abreviado BFS );
  • La búsqueda en profundidad (profundidad - First Search, referido como el DFS );
  • Hay dos tipos de algoritmos de recorrido necesitan especificar el primer vértice de ser visitados ;

Con el fin de registrar si el vértice se ha visitado, usando tres colores para indicar su estado

  • Blanco : indica que el vértice no ha sido visitada;
  • Gris : indica que el vértice es visitado, pero no es vértices completamente adyacentes visitaron;
  • Negro : indica que el vértice es visitado, y todos sus vértices vecinos han sido visitados;

Primero de todo método vértices encapsulación initializeColor inicialización figura blanca, el código para lograr lo siguiente:

      //四.初始化状态颜色
      Graph.prototype.initializeColor = function(){
        let colors = []
        for (let i = 0; i < this.vertexes.length; i++) {
           colors[this.vertexes[i]] = 'white';
        }
        return colors
      }
BFS

Ideas algoritmo de búsqueda en anchura:

  • Primero en amplitud algoritmo de búsqueda comenzará a recorrer el mapa desde el primer vértice especifica para acceder a todos sus vértices vecinos, como una visita a una figura;
  • Se puede decir de profundidad después de la primera anchura de cada vértice que atraviesa el dibujo;

imagen-20200303233840691

Realización de las ideas:

Sobre la base de cola puede simplemente realizar el algoritmo de búsqueda en anchura:

  • En primer lugar crear una cola Q (en la cola, la cabecera);
  • La llamada a un método de envasado initializeColor todos los vértices se inicializa en blanco;
  • Especifica el primer vértice A, marcado de A a gris (nodo visitado), y se coloca en la cola Q, A;
  • Ciclo a través de los elementos en la cola, la cola, siempre que Q no está vacío, realiza las siguientes operaciones:
    • Una primera Q gris se extrae de la cabecera;
    • Después de retirar el A, no todo sido visitada (blanco) se añadieron secuencialmente vértices adyacentes cola de A, Q de la cola cola, y se convierte en gris. Con el fin de asegurar, no se repetirán grises vértices adyacentes en cola;
    • Después de la adición de todos los nodos vecinos A, Q, A a negro, en el siguiente ciclo se elimina fuera de Q;

la implementación del código:

      //五.实现广度搜索(BFS)
      //传入指定的第一个顶点和处理结果的函数
      Graph.prototype.bfs = function(initV, handler){
        //1.初始化颜色
        let colors = this.initializeColor()

        //2.创建队列
        let que = new Queue()

        //3.将顶点加入到队列中
        que.enqueue(initV)

        //4.循环从队列中取出元素,队列为空才停止
        while(!que.isEmpty()){
          //4.1.从队列首部取出一个顶点
          let v = que.dequeue()

          //4.2.从字典对象edges中获取和该顶点相邻的其他顶点组成的数组
          let vNeighbours = this.edges.get(v)

          //4.3.将v的颜色变为灰色
          colors[v] = 'gray'

          //4.4.遍历v所有相邻的顶点vNeighbours,并且加入队列中
          for (let i = 0; i < vNeighbours.length; i++) {
            const a = vNeighbours[i];
            //判断相邻顶点是否被探测过,被探测过则不加入队列中;并且加入队列后变为灰色,表示被探测过
            if (colors[a] == 'white') {
              colors[a] = 'gray'
              que.enqueue(a)
            }
          }

          //4.5.处理顶点v
          handler(v)

          //4.6.顶点v所有白色的相邻顶点都加入队列后,将顶点v设置为黑色。此时黑色顶点v位于队列最前面,进入下一次while循环时会被取出
          colors[v] = 'black'
        }
      }

proceso detallada:

Cuando se especifica el recorrido para el primer vértice A:

  • Como se muestra en la Fig., Los bordes extraídos en el diccionario con el A adyacente y no ha sido visitado blanco vértice B, C, D Que en la cola y se vuelve de color gris a negro y luego desencolan A ;
  • A continuación, como se muestra en b, los bordes se extraen en el diccionario adyacente a B y no ha sido visitado vértices blancas E, F Que en la cola y se vuelve de color gris a negro y luego quitadas de la cola B ;

imagen-20200306144336380

  • Como se muestra en la Fig. C, el diccionario será eliminada junto a los bordes de la C y no ha sido visitado vértice blanco G (A, D se ha vuelto gris, sino también adyacente, no se pone en cola) en la cola en la cola y se vuelve gris a negro y luego quitado de la cola C;
  • A continuación, como se muestra en la Fig. D, tomada a cabo en los bordes de diccionario adyacentes a D y no ha sido visitada en una cola Que ápice blanco H y convertirse en gris, a negro y luego D elimina de la cola.

imagen-20200306144427242

Este ciclo hasta que el elemento de la cola es 0, es decir, todos los vértices ennegrecidas cola y retirados después de la parada cuando todos los vértices en el gráfico que ha sido atravesado.

Código de ensayo:

    //测试代码
    //1.创建图结构
    let graph = new Graph()

    //2.添加顶点
    let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }

    //3.添加边
    graph.addEdge('A', 'B')
    graph.addEdge('A', 'C')
    graph.addEdge('A', 'D')
    graph.addEdge('C', 'D')
    graph.addEdge('C', 'G')
    graph.addEdge('D', 'G')
    graph.addEdge('D', 'H')
    graph.addEdge('B', 'E')
    graph.addEdge('B', 'F')
    graph.addEdge('E', 'I')
    
    //4.测试bfs遍历方法
    let result = ""
    graph.bfs(graph.vertexes[0], function(v){
      result += v + "-"
    })
    console.log(result);

Resultados del ensayo:

imagen-20200304120023711

Visible orden de búsqueda, primero en amplitud se instala no se repetirá de desplazamiento de todos los vértices.

La búsqueda en profundidad

Ideas algoritmo primero en amplitud:

  • El algoritmo de búsqueda en profundidad se iniciará desde un primer desplazamiento figura especificada vértice recorrido hasta el último vértice de la ruta se han visitado hasta el momento a lo largo de un camino;
  • Y luego de retorno a explorar el camino, es decir, a lo largo de la ruta original para el ancho y profundo para atravesar cada vértice en el gráfico;

imagen-20200304120355088

Realización de las ideas:

  • Se puede utilizar la pila estructura para lograr el algoritmo de búsqueda en profundidad;
  • Para recorrido en profundidad del algoritmo de búsqueda del árbol de búsqueda binario en el recorrido en preorden es similar, también se puede utilizar de forma recursiva para lograr (la naturaleza recursiva es una función de la pila de llamadas).

DFS define un método para llamada recursiva dfsVisit, un método es definido para cada vértice dfsVisit figuras Descenso: recursivo algoritmo de búsqueda en profundidad.

En el método dfs:

  • En primer lugar, llame al método initializeColor todos los vértices se inicializa en blanco;
  • A continuación, llamar a los vértices de los métodos gráfico de recorrido dfsVisit;

En el método dfsVisit:

  • En primer lugar, el nodo de entrada v especificado etiquetado gris ;
  • A continuación, el proceso de vértice V;
  • Entonces, el acceso a los vértices adyacentes V;
  • Finalmente, el vértice v marcado negro;

la implementación del código:

      //六.实现深度搜索(DFS)
      Graph.prototype.dfs = function(initV, handler){
        //1.初始化顶点颜色
        let colors = this.initializeColor()

        //2.从某个顶点开始依次递归访问
        this.dfsVisit(initV, colors, handler)
      }

      //为了方便递归调用,封装访问顶点的函数,传入三个参数分别表示:指定的第一个顶点、颜色、处理函数
      Graph.prototype.dfsVisit = function(v, colors, handler){
        //1.将颜色设置为灰色
        colors[v] = 'gray'

        //2.处理v顶点
        handler(v)

        //3.访问V的相邻顶点
        let vNeighbours = this.edges.get(v)
        for (let i = 0; i < vNeighbours.length; i++) {
          let a = vNeighbours[i];
          //判断相邻顶点是否为白色,若为白色,递归调用函数继续访问
          if (colors[a] == 'white') {
            this.dfsVisit(a, colors, handler)
          }
          
        }

        //4.将v设置为黑色
        colors[v] = 'black'
      }

proceso detallada:

Aquí para explicar por qué el código Paso 3: Ir a los vértices vértices adyacentes especificados.

  • Un vértice para especificar, por ejemplo, empezar por matriz de memoria adyacente compuesto de vértices A y los vértices correspondientes del objeto diccionario bordes vértices adyacentes eliminan:

imagen-20200304155916036

  • Paso : Un vértice se vuelve gris, a continuación, entra en el primer bucle, atravesando los vértices adyacentes blancas A: B, C, D; en el primer ciclo para el ciclo (ejecución B), B satisface el vértice : == colores "blanco", un disparador recursiva, vuelva a invocar el método;
  • Paso : B convierte vértice gris, a continuación, entra en el segundo bucle, atravesando los vértices adyacentes blanco B: E, F; (ejecución E), E vértices se encuentran para el primer ciclo del ciclo: Colores == "blanco", un disparador recursiva, re-invocar el método;
  • Paso : E se convierte en vértice gris, a continuación, en el tercer bucle para atravesar vértices adyacentes E blanco: I; (realizar I), encuentro en el vértice del primer ciclo para el ciclo: colores == "blanco", un disparador recursiva, re-invocar el método;
  • Paso cuatro : I vértice en gris, a continuación, entró en el cuarto de bucle, porque los vértices adyacentes E no satisface el vértice I: == colores "blanco", detienen la llamada recursiva. Proceso se muestra a continuación:

imagen-20200304160536187

  • Paso cinco : Después de la recursión toda la parte posterior hacia arriba, de vuelta a la primera tercera para el bucle continuar con el segundo y tercer ciclos ... que, durante cada ciclo de ejecución de la misma razón que anteriormente, hasta el final de la recursión de nuevo después y, a continuación, volver a la segunda para bucle continúa donde los ciclos segundo y tercero .... y así sucesivamente ... hasta que todos los vértices en la gráfica de un acceso completo hasta ahora.

La siguiente figura muestra un recorrido completo del proceso de elaboración de cada vértice:

  • Encontrado indica el vértice visitado estado se convierte en gris ;
  • Exploración representa tanto visitó el vértice, también visitado el vértice de todos los vértices adyacentes, el estado se convierte en negro ;
  • Puesto que el vértice se vuelve gris después de controlador de controlador de llamada, el método de control de salida para encontrar el orden de secuencia de los vértices, a saber: A, B, E, I, F, C, D, G, H.

imagen-20200304154745646

Código de ensayo:

    //测试代码
    //1.创建图结构
    let graph = new Graph()

    //2.添加顶点
    let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']
    for (let i = 0; i < myVertexes.length; i++) {
      graph.addVertex(myVertexes[i])
    }

    //3.添加边
    graph.addEdge('A', 'B')
    graph.addEdge('A', 'C')
    graph.addEdge('A', 'D')
    graph.addEdge('C', 'D')
    graph.addEdge('C', 'G')
    graph.addEdge('D', 'G')
    graph.addEdge('D', 'H')
    graph.addEdge('B', 'E')
    graph.addEdge('B', 'F')
    graph.addEdge('E', 'I')
    
    //4.测试dfs遍历顶点
    let result = ""
    graph.dfs(graph.vertexes[0], function(v){
      result += v + "-"
    })
    console.log(result);
    

Resultados del ensayo:

imagen-20200304125313739

2.6 La plena realización

    //封装图结构
    function Graph (){
      //属性:顶点(数组)/边(字典)
      this.vertexes = []  //顶点
      this.edges = new Dictionary() //边

      //方法
      //添加方法
      //一.添加顶点
      Graph.prototype.addVertex = function(v){
        this.vertexes.push(v)
        this.edges.set(v, []) //将边添加到字典中,新增的顶点作为键,对应的值为一个存储边的空数组
      }
      //二.添加边
      Graph.prototype.addEdge = function(v1, v2){//传入两个顶点为它们添加边
        this.edges.get(v1).push(v2)//取出字典对象edges中存储边的数组,并添加关联顶点
        this.edges.get(v2).push(v1)//表示的是无向表,故要添加互相指向的两条边
      }

      //三.实现toString方法:转换为邻接表形式
      Graph.prototype.toString = function (){
        //1.定义字符串,保存最终结果
        let resultString = ""

        //2.遍历所有的顶点以及顶点对应的边
        for (let i = 0; i < this.vertexes.length; i++) {//遍历所有顶点
          resultString += this.vertexes[i] + '-->'
          let vEdges = this.edges.get(this.vertexes[i])
          for (let j = 0; j < vEdges.length; j++) {//遍历字典中每个顶点对应的数组
            resultString += vEdges[j] + '  ';
          }
          resultString += '\n'
        }
        return resultString
      }

      //四.初始化状态颜色
      Graph.prototype.initializeColor = function(){
        let colors = []
        for (let i = 0; i < this.vertexes.length; i++) {
           colors[this.vertexes[i]] = 'white';
        }
        return colors
      }

      //五.实现广度搜索(BFS)
      //传入指定的第一个顶点和处理结果的函数
      Graph.prototype.bfs = function(initV, handler){
        //1.初始化颜色
        let colors = this.initializeColor()

        //2.创建队列
        let que = new Queue()

        //3.将顶点加入到队列中
        que.enqueue(initV)

        //4.循环从队列中取出元素
        while(!que.isEmpty()){
          //4.1.从队列中取出一个顶点
          let v = que.dequeue()

          //4.2.获取和顶点相相邻的其他顶点
          let vNeighbours = this.edges.get(v)

          //4.3.将v的颜色变为灰色
          colors[v] = 'gray'

          //4.4.遍历v所有相邻的顶点vNeighbours,并且加入队列中
          for (let i = 0; i < vNeighbours.length; i++) {
            const a = vNeighbours[i];
            //判断相邻顶点是否被探测过,被探测过则不加入队列中;并且加入队列后变为灰色,表示被探测过
            if (colors[a] == 'white') {
              colors[a] = 'gray'
              que.enqueue(a)
            }
          }

          //4.5.处理顶点v
          handler(v)

          //4.6.顶点v所有白色的相邻顶点都加入队列后,将顶点v设置为黑色。此时黑色顶点v位于队列最前面,进入下一次while循环时会被取出
          colors[v] = 'black'
        }
      }

      //六.实现深度搜索(DFS)
      Graph.prototype.dfs = function(initV, handler){
        //1.初始化顶点颜色
        let colors = this.initializeColor()

        //2.从某个顶点开始依次递归访问
        this.dfsVisit(initV, colors, handler)
      }

      //为了方便递归调用,封装访问顶点的函数,传入三个参数分别表示:指定的第一个顶点、颜色、处理函数
      Graph.prototype.dfsVisit = function(v, colors, handler){
        //1.将颜色设置为灰色
        colors[v] = 'gray'

        //2.处理v顶点
        handler(v)

        //3.访问v相连的其他顶点
        let vNeighbours = this.edges.get(v)
        for (let i = 0; i < vNeighbours.length; i++) {
          let a = vNeighbours[i];
          //判断相邻顶点是否为白色,若为白色,递归调用函数继续访问
          if (colors[a] == 'white') {
            this.dfsVisit(a, colors, handler)
          }
          
        }

        //4.将v设置为黑色
        colors[v] = 'black'
      }
    }

Supongo que te gusta

Origin www.cnblogs.com/AhuntSun-blog/p/12636692.html
Recomendado
Clasificación