Encuentre el k-ésimo elemento más pequeño en un árbol de búsqueda binaria de manera óptima


112

Necesito encontrar el k-ésimo elemento más pequeño en el árbol de búsqueda binaria sin usar ninguna variable estática / global. ¿Cómo lograrlo de manera eficiente? La solución que tengo en mente es hacer la operación en O (n), el peor de los casos ya que planeo hacer un recorrido en orden de todo el árbol. Pero en el fondo siento que no estoy usando la propiedad BST aquí. ¿Es correcta mi solución supuesta o hay una mejor disponible?


7
¿Está equilibrado el árbol?
kennytm

No es. Pero si estuviera equilibrado, ¿existe una forma óptima?
fanfarrón

1
Si realiza una búsqueda sobre "Estadísticas de pedidos", encontrará lo que necesita.
RAL

Siento que la mayoría de las respuestas a continuación, aunque correctas, son trampas en el sentido de que están usando una variable global de algún tipo (ya sea una referencia a un número entero o una variable que se decrementa y se devuelve). Si absolutamente ninguno de ellos está permitido, usaría la recursividad sin que se pasen referencias.
Henley Chiu

Respuestas:


170

Aquí hay solo un resumen de la idea:

En una BST, el subárbol izquierdo del nodo Tcontiene solo elementos más pequeños que el valor almacenado en T. Si kes menor que el número de elementos del subárbol izquierdo, el kelemento más pequeño debe pertenecer al subárbol izquierdo. De lo contrario, si kes más grande, entonces el kelemento más pequeño está en el subárbol derecho.

Podemos aumentar la BST para que cada nodo almacene el número de elementos en su subárbol izquierdo (suponga que el subárbol izquierdo de un nodo dado incluye ese nodo). Con esta información, es simple recorrer el árbol preguntando repetidamente por el número de elementos en el subárbol izquierdo, para decidir si hacer recursividad en el subárbol izquierdo o derecho.

Ahora, suponga que estamos en el nodo T:

  1. Si k == num_elements (subárbol izquierdo de T) , entonces la respuesta que estamos buscando es el valor en node T.
  2. Si k> num_elements (subárbol izquierdo de T) , entonces obviamente podemos ignorar el subárbol izquierdo, porque esos elementos también serán más pequeños que el kmás pequeño. Entonces, reducimos el problema a encontrar el k - num_elements(left subtree of T)elemento más pequeño del subárbol derecho.
  3. Si k <núm_elementos (subárbol izquierdo de T) , entonces el kth más pequeño está en algún lugar del subárbol izquierdo, por lo que reducimos el problema a encontrar el kelemento más pequeño en el subárbol izquierdo.

Análisis de complejidad:

Esto lleva O(depth of node)tiempo, que es O(log n)en el peor de los casos en una BST equilibrada, o O(log n)en promedio para una BST aleatoria.

Un BST requiere O(n)almacenamiento y se necesita otro O(n)para almacenar la información sobre la cantidad de elementos. Todas las operaciones de BST toman O(depth of node)tiempo y se necesita O(depth of node)más tiempo para mantener la información de "número de elementos" para la inserción, eliminación o rotación de nodos. Por lo tanto, almacenar información sobre el número de elementos en el subárbol izquierdo mantiene la complejidad espacial y temporal de una BST.


59
Para encontrar el enésimo elemento más pequeño, solo necesita almacenar el tamaño del subárbol izquierdo. Usaría el tamaño del subárbol derecho si también quisiera poder encontrar el enésimo elemento más grande. En realidad, puede hacerlo menos costoso: almacene el tamaño total del árbol en la raíz y el tamaño del subárbol izquierdo. Cuando necesite dimensionar el subárbol derecho, puede restar el tamaño de la izquierda del tamaño total.
Jerry Coffin

37
Esta BST aumentada se denomina "árbol de estadísticas de pedidos".
Daniel

10
@Ivlad: en el paso 2: creo que "k - num_elements" debería ser "k - num_elements -1", ya que también debes incluir el elemento raíz.
understack

1
@understack: no si asume que la raíz es parte del subárbol.
IVlad

16
Si el árbol no contiene un campo que contenga el "número de elementos en su subárbol izquierdo y derecho", entonces el método terminará siendo BigO (n), ya que deberá caminar por el subárbol derecho o izquierdo en cada nodo para poder calcular el índice k del nodo actual.
Robert S. Barnes

68

Una solución más sencilla sería hacer un recorrido en orden y realizar un seguimiento del elemento que se va a imprimir actualmente (sin imprimirlo). Cuando llegamos a k, imprimimos el elemento y saltamos el resto del recorrido del árbol.

void findK(Node* p, int* k) {
  if(!p || k < 0) return;
  findK(p->left, k);
  --k;
  if(k == 0) { 
    print p->data;
    return;  
  } 
  findK(p->right, k); 
}

1
+1: La idea va en la dirección correcta, pero es posible que sea necesario ajustar algunos cabos sueltos; ver stackoverflow.com/a/23069077/278326
Arun

1
Me gusta esta solución, dado que BST ya está ordenado, un recorrido debería ser suficiente.
Merlin

3
Si n está cerca del número total de nodos en este árbol, su algoritmo tardará O (n) en finalizar, lo cual es malo para la respuesta seleccionada-O (log n)
Spark8006

13
public int ReturnKthSmallestElement1(int k)
    {
        Node node = Root;

        int count = k;

        int sizeOfLeftSubtree = 0;

        while(node != null)
        {

            sizeOfLeftSubtree = node.SizeOfLeftSubtree();

            if (sizeOfLeftSubtree + 1 == count)
                return node.Value;
            else if (sizeOfLeftSubtree < count)
            {
                node = node.Right;
                count -= sizeOfLeftSubtree+1;
            }
            else
            {
                node = node.Left;
            }
        }

        return -1;
    }

esta es mi implementación en C # basada en el algoritmo anterior, solo pensé en publicarlo para que la gente pueda entender mejor que funciona para mí

gracias IVlad


11

Una solución más sencilla sería realizar un recorrido en orden y realizar un seguimiento del elemento que se va a imprimir actualmente con un contador k. Cuando llegamos a k, imprime el elemento. El tiempo de ejecución es O (n). Recuerde que el tipo de retorno de la función no se puede anular, debe devolver su valor actualizado de k después de cada llamada recursiva. Una mejor solución para esto sería una BST aumentada con un valor de posición ordenado en cada nodo.

public static int kthSmallest (Node pivot, int k){
    if(pivot == null )
        return k;   
    k = kthSmallest(pivot.left, k);
    k--;
    if(k == 0){
        System.out.println(pivot.value);
    }
    k = kthSmallest(pivot.right, k);
    return k;
}

Supongo que su solución es mejor en términos de complejidad de espacio, en comparación con la BST aumentada.
zach

La búsqueda no se detiene incluso después de encontrar el k-ésimo elemento más pequeño.
Vineeth Chitteti

10

// agrega una versión de Java sin recursividad

public static <T> void find(TreeNode<T> node, int num){
    Stack<TreeNode<T>> stack = new Stack<TreeNode<T>>();

    TreeNode<T> current = node;
    int tmp = num;

    while(stack.size() > 0 || current!=null){
        if(current!= null){
            stack.add(current);
            current = current.getLeft();
        }else{
            current = stack.pop();
            tmp--;

            if(tmp == 0){
                System.out.println(current.getValue());
                return;
            }

            current = current.getRight();
        }
    }
}

Me gusta esta solución y la recursiva correspondiente. Honestamente, la mayoría de las respuestas a esta pregunta son demasiado confusas / complejas para leer.
Henley Chiu

¡Amo esta solución! ¡Claro y genial!
Rugal

Esta solución es atravesar el árbol 'en orden' y disminuir un contador después de visitar el nodo, para luego detenerse cuando el contador se iguala a cero. El peor caso es entonces de orden O (n). No es el más óptimo en comparación con las soluciones recursivas de @ IVlad cuyo peor caso toma O (log n)
Jorge P.


4

Dado solo un árbol de búsqueda binario simple, todo lo que puede hacer es comenzar desde el más pequeño y recorrer hacia arriba para encontrar el nodo correcto.

Si va a hacer esto con mucha frecuencia, puede agregar un atributo a cada nodo que indique cuántos nodos hay en su subárbol izquierdo. Con eso, puede descender el árbol directamente al nodo correcto.


4

Paseo recursivo en orden con contador

Time Complexity: O( N ), N is the number of nodes
Space Complexity: O( 1 ), excluding the function call stack

La idea es similar a la solución @prasadvk, pero tiene algunas deficiencias (consulte las notas a continuación), por lo que estoy publicando esto como una respuesta separada.

// Private Helper Macro
#define testAndReturn( k, counter, result )                         \
    do { if( (counter == k) && (result == -1) ) {                   \
        result = pn->key_;                                          \
        return;                                                     \
    } } while( 0 )

// Private Helper Function
static void findKthSmallest(
    BstNode const * pn, int const k, int & counter, int & result ) {

    if( ! pn ) return;

    findKthSmallest( pn->left_, k, counter, result );
    testAndReturn( k, counter, result );

    counter += 1;
    testAndReturn( k, counter, result );

    findKthSmallest( pn->right_, k, counter, result );
    testAndReturn( k, counter, result );
}

// Public API function
void findKthSmallest( Bst const * pt, int const k ) {
    int counter = 0;
    int result = -1;        // -1 := not found
    findKthSmallest( pt->root_, k, counter, result );
    printf("%d-th element: element = %d\n", k, result );
}

Notas (y diferencias con la solución de @ prasadvk):

  1. if( counter == k )La prueba se requiere en tres lugares: (a) después del subárbol izquierdo, (b) después de la raíz y (c) después del subárbol derecho. Esto es para asegurar que se detecte el k-ésimo elemento para todas las ubicaciones , es decir, independientemente del subárbol en el que se encuentre.

  2. if( result == -1 )prueba requerida para garantizar que solo se imprima el elemento del resultado ; de lo contrario, se imprimen todos los elementos desde el k-ésimo más pequeño hasta la raíz.


La complejidad del tiempo para esta solución es O(k + d)dónde destá la profundidad máxima del árbol. Por lo tanto, usa una variable global counterpero es ilegal para esta pregunta.
Valentin Shergin

Hola Arun, ¿podrías explicarnos con un ejemplo? No entiendo este en particular su primer punto.
Andy897

3

Para un árbol de búsqueda no equilibrado, se necesita O (n) .

Para un árbol de búsqueda equilibrado , se necesita O (k + log n) en el peor de los casos, pero solo O (k) en el sentido Amortizado .

Tener y administrar el número entero adicional para cada nodo: el tamaño del subárbol proporciona complejidad de tiempo O (log n) . Este árbol de búsqueda equilibrado se suele llamar RankTree.

En general, hay soluciones (basadas no en árbol).

Saludos.


1

Esto funciona bien: estado: es la matriz que contiene si se encuentra el elemento. k: es el k-ésimo elemento que se encuentra. count: realiza un seguimiento del número de nodos atravesados ​​durante el recorrido del árbol.

int kth(struct tree* node, int* status, int k, int count)
{
    if (!node) return count;
    count = kth(node->lft, status, k, count);  
    if( status[1] ) return status[0];
    if (count == k) { 
        status[0] = node->val;
        status[1] = 1;
        return status[0];
    }
    count = kth(node->rgt, status, k, count+1);
    if( status[1] ) return status[0];
    return count;
}

1

Si bien esta definitivamente no es la solución óptima al problema, es otra solución potencial que pensé que algunas personas podrían encontrar interesante:

/**
 * Treat the bst as a sorted list in descending order and find the element 
 * in position k.
 *
 * Time complexity BigO ( n^2 )
 *
 * 2n + sum( 1 * n/2 + 2 * n/4 + ... ( 2^n-1) * n/n ) = 
 * 2n + sigma a=1 to n ( (2^(a-1)) * n / 2^a ) = 2n + n(n-1)/4
 *
 * @param t The root of the binary search tree.
 * @param k The position of the element to find.
 * @return The value of the element at position k.
 */
public static int kElement2( Node t, int k ) {
    int treeSize = sizeOfTree( t );

    return kElement2( t, k, treeSize, 0 ).intValue();
}

/**
 * Find the value at position k in the bst by doing an in-order traversal 
 * of the tree and mapping the ascending order index to the descending order 
 * index.
 *
 *
 * @param t Root of the bst to search in.
 * @param k Index of the element being searched for.
 * @param treeSize Size of the entire bst.
 * @param count The number of node already visited.
 * @return Either the value of the kth node, or Double.POSITIVE_INFINITY if 
 *         not found in this sub-tree.
 */
private static Double kElement2( Node t, int k, int treeSize, int count ) {
    // Double.POSITIVE_INFINITY is a marker value indicating that the kth 
    // element wasn't found in this sub-tree.
    if ( t == null )
        return Double.POSITIVE_INFINITY;

    Double kea = kElement2( t.getLeftSon(), k, treeSize, count );

    if ( kea != Double.POSITIVE_INFINITY )
        return kea;

    // The index of the current node.
    count += 1 + sizeOfTree( t.getLeftSon() );

    // Given any index from the ascending in order traversal of the bst, 
    // treeSize + 1 - index gives the
    // corresponding index in the descending order list.
    if ( ( treeSize + 1 - count ) == k )
        return (double)t.getNumber();

    return kElement2( t.getRightSon(), k, treeSize, count );
}

1

firma:

Node * find(Node* tree, int *n, int k);

llamar como:

*n = 0;
kthNode = find(root, n, k);

definición:

Node * find ( Node * tree, int *n, int k)
{
   Node *temp = NULL;

   if (tree->left && *n<k)
      temp = find(tree->left, n, k);

   *n++;

   if(*n==k)
      temp = root;

   if (tree->right && *n<k)
      temp = find(tree->right, n, k);

   return temp;
}

1

Bueno, aquí están mis 2 centavos ...

int numBSTnodes(const Node* pNode){
     if(pNode == NULL) return 0;
     return (numBSTnodes(pNode->left)+numBSTnodes(pNode->right)+1);
}


//This function will find Kth smallest element
Node* findKthSmallestBSTelement(Node* root, int k){
     Node* pTrav = root;
     while(k > 0){
         int numNodes = numBSTnodes(pTrav->left);
         if(numNodes >= k){
              pTrav = pTrav->left;
         }
         else{
              //subtract left tree nodes and root count from 'k'
              k -= (numBSTnodes(pTrav->left) + 1);
              if(k == 0) return pTrav;
              pTrav = pTrav->right;
        }

        return NULL;
 }

0

Esto es lo que pensé y funciona. Se ejecutará en o (log n)

public static int FindkThSmallestElemet(Node root, int k)
    {
        int count = 0;
        Node current = root;

        while (current != null)
        {
            count++;
            current = current.left;
        }
        current = root;

        while (current != null)
        {
            if (count == k)
                return current.data;
            else
            {
                current = current.left;
                count--;
            }
        }

        return -1;


    } // end of function FindkThSmallestElemet

3
No creo que esta solución funcione. ¿Qué pasa si el K-ésimo más pequeño está en el subárbol derecho del nodo del árbol?
Anil Vishnoi

0

Bueno, simplemente podemos usar el recorrido en orden y empujar el elemento visitado a una pila. pop k varias veces, para obtener la respuesta.

también podemos detenernos después de k elementos


1
esta no es una solución óptima
bragboy

0

Solución para caja BST completa: -

Node kSmallest(Node root, int k) {
  int i = root.size(); // 2^height - 1, single node is height = 1;
  Node result = root;
  while (i - 1 > k) {
    i = (i-1)/2;  // size of left subtree
    if (k < i) {
      result = result.left;
    } else {
      result = result.right;
      k -= i;
    }  
  }
  return i-1==k ? result: null;
}

0

El kernel de Linux tiene una excelente estructura de datos de árbol rojo-negro aumentada que admite operaciones basadas en rangos en O (log n) en linux / lib / rbtree.c.

También se puede encontrar un puerto Java muy burdo en http://code.google.com/p/refolding/source/browse/trunk/core/src/main/java/it/unibo/refolding/alg/RbTree.java , junto con RbRoot.java y RbNode.java. El enésimo elemento se puede obtener llamando a RbNode.nth (nodo RbNode, int n), pasando la raíz del árbol.


0

Aquí hay una versión concisa en C # que devuelve el k-ésimo elemento más pequeño, pero requiere pasar k como un argumento de referencia (es el mismo enfoque que @prasadvk):

Node FindSmall(Node root, ref int k)
{
    if (root == null || k < 1)
        return null;

    Node node = FindSmall(root.LeftChild, ref k);
    if (node != null)
        return node;

    if (--k == 0)
        return node ?? root;
    return FindSmall(root.RightChild, ref k);
}

Es O (log n) para encontrar el nodo más pequeño, y luego O (k) para atravesar el k-ésimo nodo, por lo que es O (k + log n).


¿Qué tal la versión de Java?
Henley Chiu


0

No pude encontrar un algoritmo mejor ... así que decidí escribir uno :) Corrígeme si esto está mal.

class KthLargestBST{
protected static int findKthSmallest(BSTNode root,int k){//user calls this function
    int [] result=findKthSmallest(root,k,0);//I call another function inside
    return result[1];
}
private static int[] findKthSmallest(BSTNode root,int k,int count){//returns result[]2 array containing count in rval[0] and desired element in rval[1] position.
    if(root==null){
        int[]  i=new int[2];
        i[0]=-1;
        i[1]=-1;
        return i;
    }else{
        int rval[]=new int[2];
        int temp[]=new int[2];
        rval=findKthSmallest(root.leftChild,k,count);
        if(rval[0]!=-1){
            count=rval[0];
        }
        count++;
        if(count==k){
            rval[1]=root.data;
        }
        temp=findKthSmallest(root.rightChild,k,(count));
        if(temp[0]!=-1){
            count=temp[0];
        }
        if(temp[1]!=-1){
            rval[1]=temp[1];
        }
        rval[0]=count;
        return rval;
    }
}
public static void main(String args[]){
    BinarySearchTree bst=new BinarySearchTree();
    bst.insert(6);
    bst.insert(8);
    bst.insert(7);
    bst.insert(4);
    bst.insert(3);
    bst.insert(4);
    bst.insert(1);
    bst.insert(12);
    bst.insert(18);
    bst.insert(15);
    bst.insert(16);
    bst.inOrderTraversal();
    System.out.println();
    System.out.println(findKthSmallest(bst.root,11));
}

}


0

Aquí está el código de Java,

max (raíz de nodo, int k) - para encontrar el kth más grande

min (raíz de nodo, int k) - para encontrar kth más pequeño

static int count(Node root){
    if(root == null)
        return 0;
    else
        return count(root.left) + count(root.right) +1;
}
static int max(Node root, int k) {
    if(root == null)
        return -1;
    int right= count(root.right);

    if(k == right+1)
        return root.data;
    else if(right < k)
        return max(root.left, k-right-1);
    else return max(root.right, k);
}

static int min(Node root, int k) {
    if (root==null)
        return -1;

    int left= count(root.left);
    if(k == left+1)
        return root.data;
    else if (left < k)
        return min(root.right, k-left-1);
    else
        return min(root.left, k);
}

0

esto también funcionaría. simplemente llame a la función con maxNode en el árbol

def k_largest (self, node, k): if k <0: return None
if k == 0: return node else: k - = 1 return self.k_largest (self.predecessor (node), k)


0

Creo que esto es mejor que la respuesta aceptada porque no necesita modificar el nodo del árbol original para almacenar el número de sus nodos secundarios.

Solo necesitamos usar el recorrido en orden para contar el nodo más pequeño de izquierda a derecha, dejar de buscar una vez que el recuento sea igual a K.

private static int count = 0;
public static void printKthSmallestNode(Node node, int k){
    if(node == null){
        return;
    }

    if( node.getLeftNode() != null ){
        printKthSmallestNode(node.getLeftNode(), k);
    }

    count ++ ;
    if(count <= k )
        System.out.println(node.getValue() + ", count=" + count + ", k=" + k);

    if(count < k  && node.getRightNode() != null)
        printKthSmallestNode(node.getRightNode(), k);
}

0

El mejor enfoque ya está ahí, pero me gustaría agregar un código simple para eso

int kthsmallest(treenode *q,int k){
int n = size(q->left) + 1;
if(n==k){
    return q->val;
}
if(n > k){
    return kthsmallest(q->left,k);
}
if(n < k){
    return kthsmallest(q->right,k - n);
}

}

int size(treenode *q){
if(q==NULL){
    return 0;
}
else{
    return ( size(q->left) + size(q->right) + 1 );
}}

0

Usar la clase de resultado auxiliar para rastrear si se encuentra el nodo y el k actual.

public class KthSmallestElementWithAux {

public int kthsmallest(TreeNode a, int k) {
    TreeNode ans = kthsmallestRec(a, k).node;
    if (ans != null) {
        return ans.val;
    } else {
        return -1;
    }
}

private Result kthsmallestRec(TreeNode a, int k) {
    //Leaf node, do nothing and return
    if (a == null) {
        return new Result(k, null);
    }

    //Search left first
    Result leftSearch = kthsmallestRec(a.left, k);

    //We are done, no need to check right.
    if (leftSearch.node != null) {
        return leftSearch;
    }

    //Consider number of nodes found to the left
    k = leftSearch.k;

    //Check if current root is the solution before going right
    k--;
    if (k == 0) {
        return new Result(k - 1, a);
    }

    //Check right
    Result rightBalanced = kthsmallestRec(a.right, k);

    //Consider all nodes found to the right
    k = rightBalanced.k;

    if (rightBalanced.node != null) {
        return rightBalanced;
    }

    //No node found, recursion will continue at the higher level
    return new Result(k, null);

}

private class Result {
    private final int k;
    private final TreeNode node;

    Result(int max, TreeNode node) {
        this.k = max;
        this.node = node;
    }
}
}

0

Complejidad del tiempo de la solución de Python: O (n) Complejidad del espacio: O (1)

La idea es utilizar Morris Inorder Traversal

class Solution(object):
def inorderTraversal(self, current , k ):
    while(current is not None):    #This Means we have reached Right Most Node i.e end of LDR traversal

        if(current.left is not None):  #If Left Exists traverse Left First
            pre = current.left   #Goal is to find the node which will be just before the current node i.e predecessor of current node, let's say current is D in LDR goal is to find L here
            while(pre.right is not None and pre.right != current ): #Find predecesor here
                pre = pre.right
            if(pre.right is None):  #In this case predecessor is found , now link this predecessor to current so that there is a path and current is not lost
                pre.right = current
                current = current.left
            else:                   #This means we have traverse all nodes left to current so in LDR traversal of L is done
                k -= 1
                if(k == 0):
                    return current.val
                pre.right = None       #Remove the link tree restored to original here 
                current = current.right
        else:               #In LDR  LD traversal is done move to R 
            k -= 1
            if(k == 0):
                return current.val
            current = current.right

    return 0

def kthSmallest(self, root, k):
    return self.inorderTraversal( root , k  )

-1

Escribí una función ordenada para calcular el k-ésimo elemento más pequeño. Utilizo el recorrido en orden y se detiene cuando alcanza el k-ésimo elemento más pequeño.

void btree::kthSmallest(node* temp, int& k){
if( temp!= NULL)   {
 kthSmallest(temp->left,k);       
 if(k >0)
 {
     if(k==1)
    {
      cout<<temp->value<<endl;
      return;
    }

    k--;
 }

 kthSmallest(temp->right,k);  }}

No se proporcionan métricas de por qué esto es óptimo. Tanto en casos grandes como pequeños
Woot4Moo

-1
int RecPrintKSmallest(Node_ptr head,int k){
  if(head!=NULL){
    k=RecPrintKSmallest(head->left,k);
    if(k>0){
      printf("%c ",head->Node_key.key);
      k--;
    }
    k=RecPrintKSmallest(head->right,k);
  }
  return k;
}

2
Siempre acompañe el código con algún nivel de descripción sobre lo que hace y cómo ayuda a resolver el problema.
Ren

-1
public TreeNode findKthElement(TreeNode root, int k){
    if((k==numberElement(root.left)+1)){
        return root;
    }
    else if(k>numberElement(root.left)+1){
        findKthElement(root.right,k-numberElement(root.left)-1);
    }
    else{
        findKthElement(root.left, k);
    }
}

public int numberElement(TreeNode node){
    if(node==null){
        return 0;
    }
    else{
        return numberElement(node.left) + numberElement(node.right) + 1;
    }
}

-1
public static Node kth(Node n, int k){
    Stack<Node> s=new Stack<Node>();
    int countPopped=0;
    while(!s.isEmpty()||n!=null){
      if(n!=null){
        s.push(n);
        n=n.left;
      }else{
        node=s.pop();
        countPopped++;
        if(countPopped==k){
            return node;
        }
        node=node.right;

      }
  }

}

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.