Membalik daftar tertaut di Java, secara rekursif


101

Saya telah mengerjakan proyek Java untuk kelas untuk sementara waktu sekarang. Ini adalah implementasi dari daftar tertaut (di sini disebut AddressList, berisi node sederhana yang disebut ListNode). Hasil tangkapannya adalah bahwa semuanya harus dilakukan dengan algoritma rekursif. Saya bisa melakukan semuanya dengan baik tanpa satu metode:public AddressList reverse()

ListNode:

public class ListNode{
  public String data;
  public ListNode next;
}

Saat ini reversefungsi saya hanya memanggil fungsi pembantu yang membutuhkan argumen untuk memungkinkan rekursi.

public AddressList reverse(){
  return new AddressList(this.reverse(this.head));
}

Dengan fungsi pembantu saya yang memiliki tanda tangan private ListNode reverse(ListNode current).

Saat ini, saya memilikinya bekerja secara iteratif menggunakan tumpukan, tetapi ini bukan yang dibutuhkan spesifikasi. Saya telah menemukan algoritme dalam C yang secara rekursif membalikkan dan mengubahnya menjadi kode Java dengan tangan, dan berhasil, tetapi saya tidak memahaminya.

Sunting: Tidak apa-apa, saya menemukan jawabannya sementara itu.

private AddressList reverse(ListNode current, AddressList reversedList){
  if(current == null) 
      return reversedList;
  reversedList.addToFront(current.getData());
  return this.reverse(current.getNext(), reversedList);
}

Selama saya di sini, apakah ada yang melihat ada masalah dengan rute ini?


2
Tidak, tidak ada masalah dengan solusi Anda. Sebaliknya, ini bahkan "lebih baik" daripada solusi "Little Lisper" yang disukai karena membiarkan daftar asli tetap utuh. Ini akan sangat berguna dalam pengaturan multi-inti, di mana nilai yang tidak dapat diubah sangat disukai.
Ingo

Jawaban:


318

Ada kode dalam satu jawaban yang menjelaskannya, tetapi Anda mungkin merasa lebih mudah untuk memulai dari bawah ke atas, dengan mengajukan dan menjawab pertanyaan-pertanyaan kecil (ini adalah pendekatan dalam The Little Lisper):

  1. Apa kebalikan dari null (daftar kosong)? batal.
  2. Apa kebalikan dari daftar satu elemen? elemen.
  3. Apa kebalikan dari daftar elemen n? kebalikan dari sisa daftar diikuti oleh elemen pertama.

public ListNode Reverse(ListNode list)
{
    if (list == null) return null; // first question

    if (list.next == null) return list; // second question

    // third question - in Lisp this is easy, but we don't have cons
    // so we grab the second element (which will be the last after we reverse it)

    ListNode secondElem = list.next;

    // bug fix - need to unlink list from the rest or you will get a cycle
    list.next = null;

    // then we reverse everything from the second element on
    ListNode reverseRest = Reverse(secondElem);

    // then we join the two lists
    secondElem.next = list;

    return reverseRest;
}

30
Wow, aku suka semua soal "Tiga pertanyaan" itu.
sdellysse

4
Terima kasih. Hal kecil yang seharusnya menjadi dasar belajar Lisp. Ini juga merupakan cara untuk menyembunyikan induksi dari newbs, yang pada dasarnya adalah pola ini. Saya merekomendasikan membaca Little Lisper jika Anda benar-benar ingin mengatasi jenis masalah ini.
alas

44
pengecualian untuk keadaan luar biasa. Mengapa menggunakan tangkapan untuk kondisi yang diketahui yang dapat diuji oleh if?
Luke Schafer

4
Saya yakin Anda tidak perlu membuat variabel: secondElem karena list.next masih secondElem. Setelah "ListNode reverseRest = Reverse (secondElem);", pertama Anda dapat melakukan "list.next.next = list" dan kemudian "list.next = null". Dan itu dia.
ChuanRocks

3
Bisakah Anda menjelaskan mengapa list.next = null? Saya mencoba memahami siklus tetapi tidak mengerti.
Rohit

29

Saya ditanyai pertanyaan ini pada sebuah wawancara dan kesal karena saya meraba-raba karena saya sedikit gugup.

Ini harus membalikkan daftar tertaut tunggal, yang disebut dengan reverse (head, NULL); jadi jika ini adalah daftar Anda:

1-> 2-> 3-> 4-> 5-> null
itu akan menjadi:
5-> 4-> 3-> 2-> 1-> null

    //Takes as parameters a node in a linked list, and p, the previous node in that list
    //returns the head of the new list
    Node reverse(Node n,Node p){   
        if(n==null) return null;
        if(n.next==null){ //if this is the end of the list, then this is the new head
            n.next=p;
            return n;
        }
        Node r=reverse(n.next,n);  //call reverse for the next node, 
                                      //using yourself as the previous node
        n.next=p;                     //Set your next node to be the previous node 
        return r;                     //Return the head of the new list
    }
    

edit: ive selesai seperti 6 suntingan ini, menunjukkan bahwa itu masih sedikit rumit bagi saya lol


2
Saya akan sedikit jengkel dengan persyaratan "harus rekursif" dalam sebuah wawancara, jujur ​​saja, jika Java ditentukan. Jika tidak, saya akan memilih p = null; sementara (n.next! = null) {n2 = n.next; n.next = p; p = n; n = n2;} n.berikutnya = p; kembali n ;. O (N) stack untuk burung.
Steve Jessop

Oh ya, cek nol di kepala juga, ini adalah Java.
Steve Jessop

23

Saya sudah setengah jalan (sampai null, dan satu node seperti yang disarankan oleh alas), tetapi kehilangan jejak setelah melakukan panggilan rekursif. Namun, setelah membaca pos demi tiang, inilah yang saya dapatkan:

Node reverse(Node head) {
  // if head is null or only one node, it's reverse of itself.
  if ( (head==null) || (head.next == null) ) return head;

  // reverse the sub-list leaving the head node.
  Node reverse = reverse(head.next);

  // head.next still points to the last element of reversed sub-list.
  // so move the head to end.
  head.next.next = head;

  // point last node to nil, (get rid of cycles)
  head.next = null;
  return reverse;
}

sangat bagus. seperti melakukan kontra :)
Karthikeyan D

9

Inilah solusi rekursif lainnya. Ini memiliki lebih sedikit kode dalam fungsi rekursif daripada yang lain, jadi mungkin sedikit lebih cepat. Ini C # tapi saya yakin Java akan sangat mirip.

class Node<T>
{
    Node<T> next;
    public T data;
}

class LinkedList<T>
{
    Node<T> head = null;

    public void Reverse()
    {
        if (head != null)
            head = RecursiveReverse(null, head);
    }

    private Node<T> RecursiveReverse(Node<T> prev, Node<T> curr)
    {
        Node<T> next = curr.next;
        curr.next = prev;
        return (next == null) ? curr : RecursiveReverse(curr, next);
    }
}

8

Algo perlu bekerja pada model berikut,

  • melacak kepala
  • Ulangi sampai akhir daftar tautan
  • Hubungan terbalik

Struktur:

Head    
|    
1-->2-->3-->4-->N-->null

null-->1-->2-->3-->4-->N<--null

null-->1-->2-->3-->4<--N<--null

null-->1-->2-->3<--4<--N<--null

null-->1-->2<--3<--4<--N<--null

null-->1<--2<--3<--4<--N<--null

null<--1<--2<--3<--4<--N
                       |
                       Head

Kode:

public ListNode reverse(ListNode toBeNextNode, ListNode currentNode)
{               
        ListNode currentHead = currentNode; // keep track of the head

        if ((currentNode==null ||currentNode.next==null )&& toBeNextNode ==null)return currentHead; // ignore for size 0 & 1

        if (currentNode.next!=null)currentHead = reverse(currentNode, currentNode.next); // travarse till end recursively

        currentNode.next = toBeNextNode; // reverse link

        return currentHead;
}

Keluaran:

head-->12345

head-->54321

7

Saya rasa ini adalah solusi yang lebih bersih, yang menyerupai LISP

// Example:
// reverse0(1->2->3, null) => 
//      reverse0(2->3, 1) => 
//          reverse0(3, 2->1) => reverse0(null, 3->2->1)
// once the first argument is null, return the second arg
// which is nothing but the reveresed list.

Link reverse0(Link f, Link n) {
    if (f != null) {
        Link t = new Link(f.data1, f.data2); 
        t.nextLink = n;                      
        f = f.nextLink;             // assuming first had n elements before, 
                                    // now it has (n-1) elements
        reverse0(f, t);
    }
    return n;
}

7

Saya tahu ini adalah posting lama, tetapi sebagian besar jawaban tidak rekursif ekor yaitu mereka melakukan beberapa operasi setelah kembali dari panggilan rekursif, dan karenanya bukan yang paling efisien.

Berikut adalah versi rekursif ekor:

public Node reverse(Node previous, Node current) {
    if(previous == null)
        return null;
    if(previous.equals(head))
        previous.setNext(null);
    if(current == null) {    // end of list
        head = previous;
        return head;
    } else {
                    Node temp = current.getNext();
        current.setNext(previous);
        reverse(current, temp);
    }
    return null;    //should never reach here.
} 

Telepon dengan:

Node newHead = reverse(head, head.getNext());

9
Anda mereferensikan variabel yang disebut "head" dalam metode Anda, tetapi itu tidak dideklarasikan di mana pun.
maraton

ini mungkin metode untuk kelas List yang berisi atribut kepala Node
ChrisMcJava

4
void reverse (node1, node2) {
if (node1.next! = null)
      mundur (node1.next, node1);
   node1.next = node2;
}
panggil metode ini sebagai kebalikan (start, null);

4
public Node reverseListRecursive(Node curr)
{
    if(curr == null){//Base case
        return head;
    }
    else{
        (reverseListRecursive(curr.next)).next = (curr);
    }
    return curr;
}

3
public void reverse() {
    head = reverseNodes(null, head);
}

private Node reverseNodes(Node prevNode, Node currentNode) {
    if (currentNode == null)
        return prevNode;
    Node nextNode = currentNode.next;
    currentNode.next = prevNode;
    return reverseNodes(currentNode, nextNode);
}

Saya rasa ini adalah solusi terbaik ... sederhana, rekursi ekor dapat dioptimalkan dan hanya satu pemeriksaan nol.
sdanzig

2
public static ListNode recRev(ListNode curr){

    if(curr.next == null){
        return curr;
    }
    ListNode head = recRev(curr.next);
    curr.next.next = curr;
    curr.next = null;

    // propogate the head value
    return head;

}

Ini adalah solusi terbaik, tetapi bukan jawaban terbaik karena tidak ada penjelasan yang diberikan :). Saya mendapatkan solusi serupa pada awalnya tetapi kehilangan referensi kepala. Solusi ini menyelesaikannya.
OpenUserX03

2

Terbalik dengan algo rekursif.

public ListNode reverse(ListNode head) {
    if (head == null || head.next == null) return head;    
    ListNode rHead = reverse(head.next);
    rHead.next = head;
    head = null;
    return rHead;
}

Secara berulang

public ListNode reverse(ListNode head) {
    if (head == null || head.next == null) return head;    
    ListNode prev = null;
    ListNode cur = head
    ListNode next = head.next;
    while (next != null) {
        cur.next = prev;
        prev = cur;
        cur = next;
        next = next.next;
    }
    return cur;
}

Sayangnya kebalikan rekursif Anda salah !!
Sree Aurovindh

@SreeAurovindh - Mengapa?
rayryeng

2

Solusi ini menunjukkan bahwa tidak diperlukan argumen.

/**
 * Reverse the list
 * @return reference to the new list head
 */
public LinkNode reverse() {
    if (next == null) {
        return this; // Return the old tail of the list as the new head
    }
    LinkNode oldTail = next.reverse(); // Recurse to find the old tail
    next.next = this; // The old next node now points back to this node
    next = null; // Make sure old head has no next
    return oldTail; // Return the old tail all the way back to the top
}

Berikut adalah kode pendukung, untuk menunjukkan bahwa ini berfungsi:

public class LinkNode {
    private char name;
    private LinkNode next;

    /**
     * Return a linked list of nodes, whose names are characters from the given string
     * @param str node names
     */
    public LinkNode(String str) {
        if ((str == null) || (str.length() == 0)) {
            throw new IllegalArgumentException("LinkNode constructor arg: " + str);
        }
        name = str.charAt(0);
        if (str.length() > 1) {
            next = new LinkNode(str.substring(1));
        }
    }

    public String toString() {
        return name + ((next == null) ? "" : next.toString());
    }

    public static void main(String[] args) {
        LinkNode head = new LinkNode("abc");
        System.out.println(head);
        System.out.println(head.reverse());
    }
}

2

Berikut ini pendekatan berulang sederhana:

public static Node reverse(Node root) {
    if (root == null || root.next == null) {
        return root;
    }

    Node curr, prev, next;
    curr = root; prev = next = null;
    while (curr != null) {
        next = curr.next;
        curr.next = prev;

        prev = curr;
        curr = next;
    }
    return prev;
}

Dan inilah pendekatan rekursif:

public static Node reverseR(Node node) {
    if (node == null || node.next == null) {
        return node;
    }

    Node next = node.next;
    node.next = null;

    Node remaining = reverseR(next);
    next.next = node;
    return remaining;
}

1

Karena Java selalu pass-by-value, untuk secara rekursif membalikkan daftar tertaut di Java, pastikan untuk mengembalikan "kepala baru" (node ​​kepala setelah pengembalian) di akhir rekursi.

static ListNode reverseR(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    ListNode first = head;
    ListNode rest = head.next;

    // reverse the rest of the list recursively
    head = reverseR(rest);

    // fix the first node after recursion
    first.next.next = first;
    first.next = null;

    return head;
}

1

PointZeroTwo punya jawaban elegan & sama di Jawa ...

public void reverseList(){
    if(head!=null){
        head = reverseListNodes(null , head);
    }
}

private Node reverseListNodes(Node parent , Node child ){
    Node next = child.next;
    child.next = parent;
    return (next==null)?child:reverseListNodes(child, next);
}

Ini sempurna karena Anda tidak selalu ingin metode daftar itu mengambil daftar sebagai argumen tetapi membalikkan dirinya sendiri dengan anaknya sendiri, terima kasih
Manu

0
public class Singlelinkedlist {
  public static void main(String[] args) {
    Elem list  = new Elem();
    Reverse(list); //list is populate some  where or some how
  }

  //this  is the part you should be concerned with the function/Method has only 3 lines

  public static void Reverse(Elem e){
    if (e!=null)
      if(e.next !=null )
        Reverse(e.next);
    //System.out.println(e.data);
  }
}

class Elem {
  public Elem next;    // Link to next element in the list.
  public String data;  // Reference to the data.
}

0
public Node reverseRec(Node prev, Node curr) {
    if (curr == null) return null;  

    if (curr.next == null) {
        curr.next = prev;
        return curr;

    } else {
        Node temp = curr.next; 
        curr.next = prev;
        return reverseRec(curr, temp);
    }               
}

panggilan menggunakan: head = reverseRec (null, head);


0

Apa yang dilakukan orang lain, di posting lain adalah permainan konten, yang saya lakukan adalah permainan linkedlist, membalikkan anggota LinkedList bukan membalikkan Nilai anggota.

Public LinkedList reverse(LinkedList List)
{
       if(List == null)
               return null;
       if(List.next() == null)
              return List;
       LinkedList temp = this.reverse( List.next() );
       return temp.setNext( List );
}

sry saya lupa Anda juga membutuhkan metode helper untuk mengatur ekor berikutnya, dengan nilai null
Nima Ghaedsharafi

0
package com.mypackage;
class list{

    node first;    
    node last;

    list(){
    first=null;
    last=null;
}

/*returns true if first is null*/
public boolean isEmpty(){
    return first==null;
}
/*Method for insertion*/

public void insert(int value){

    if(isEmpty()){
        first=last=new node(value);
        last.next=null;
    }
    else{
        node temp=new node(value);
        last.next=temp;
        last=temp;
        last.next=null;
    }

}
/*simple traversal from beginning*/
public void traverse(){
    node t=first;
    while(!isEmpty() && t!=null){
        t.printval();
        t= t.next;
    }
}
/*static method for creating a reversed linked list*/
public static void reverse(node n,list l1){

    if(n.next!=null)
        reverse(n.next,l1);/*will traverse to the very end*/
    l1.insert(n.value);/*every stack frame will do insertion now*/

}
/*private inner class node*/
private class node{
    int value;
    node next;
    node(int value){
        this.value=value;
    }
    void printval(){
        System.out.print(value+" ");
    }
}

 }

0

Solusinya adalah:

package basic;

import custom.ds.nodes.Node;

public class RevLinkedList {

private static Node<Integer> first = null;

public static void main(String[] args) {
    Node<Integer> f = new Node<Integer>();
    Node<Integer> s = new Node<Integer>();
    Node<Integer> t = new Node<Integer>();
    Node<Integer> fo = new Node<Integer>();
    f.setNext(s);
    s.setNext(t);
    t.setNext(fo);
    fo.setNext(null);

    f.setItem(1);
    s.setItem(2);
    t.setItem(3);
    fo.setItem(4);
    Node<Integer> curr = f;
    display(curr);
    revLL(null, f);
    display(first);
}

public static void display(Node<Integer> curr) {
    while (curr.getNext() != null) {
        System.out.println(curr.getItem());
        System.out.println(curr.getNext());
        curr = curr.getNext();
    }
}

public static void revLL(Node<Integer> pn, Node<Integer> cn) {
    while (cn.getNext() != null) {
        revLL(cn, cn.getNext());
        break;
    }
    if (cn.getNext() == null) {
        first = cn;
    }
    cn.setNext(pn);
}

}


0
static void reverseList(){

if(head!=null||head.next!=null){
ListNode tail=head;//head points to tail


ListNode Second=head.next;
ListNode Third=Second.next;
tail.next=null;//tail previous head is poiniting null
Second.next=tail;
ListNode current=Third;
ListNode prev=Second;
if(Third.next!=null){



    while(current!=null){
    ListNode    next=current.next;
        current.next=prev;
        prev=current;
        current=next;
    }
    }
head=prev;//new head
}
}
class ListNode{
    public int data;
    public ListNode next;
    public int getData() {
        return data;
    }

    public ListNode(int data) {
        super();
        this.data = data;
        this.next=null;
    }

    public ListNode(int data, ListNode next) {
        super();
        this.data = data;
        this.next = next;
    }

    public void setData(int data) {
        this.data = data;
    }
    public ListNode getNext() {
        return next;
    }
    public void setNext(ListNode next) {
        this.next = next;
    }





}

0
private Node ReverseList(Node current, Node previous)
    {
        if (current == null) return null;
        Node originalNext = current.next;
        current.next = previous;
        if (originalNext == null) return current;
        return ReverseList(originalNext, current);
    }

mulai dengan ReverseList (head, null)
tepuk

0
//this function reverses the linked list
public Node reverseList(Node p) {
    if(head == null){
        return null;
    }
    //make the last node as head
    if(p.next == null){
        head.next = null;
        head = p;
        return p;
    }
    //traverse to the last node, then reverse the pointers by assigning the 2nd last node to last node and so on..
    return reverseList(p.next).next = p;
}

0
//Recursive solution
class SLL
{
   int data;
   SLL next;
}

SLL reverse(SLL head)
{
  //base case - 0 or 1 elements
  if(head == null || head.next == null) return head;

  SLL temp = reverse(head.next);
  head.next.next = head;
  head.next = null;
  return temp;
}

0

Terinspirasi oleh artikel yang membahas implementasi permanen dari struktur data rekursif, saya menyusun solusi alternatif menggunakan Swift.

Solusi dokumen jawaban terkemuka dengan menyoroti topik-topik berikut:

  1. Apa kebalikan dari nil (daftar kosong)?
    • Tidak masalah di sini, karena kami memiliki perlindungan nihil di Swift.
  2. Apa kebalikan dari daftar satu elemen?
    • Elemen itu sendiri
  3. Apa kebalikan dari daftar elemen n?
    • Kebalikan dari elemen kedua di diikuti oleh elemen pertama.

Saya telah menyebutkan ini di mana berlaku dalam solusi di bawah ini.

/**
 Node is a class that stores an arbitrary value of generic type T 
 and a pointer to another Node of the same time.  This is a recursive 
 data structure representative of a member of a unidirectional linked
 list.
 */
public class Node<T> {
    public let value: T
    public let next: Node<T>?

    public init(value: T, next: Node<T>?) {
        self.value = value
        self.next = next
    }

    public func reversedList() -> Node<T> {
        if let next = self.next {
            // 3. The reverse of the second element on followed by the first element.
            return next.reversedList() + value
        } else {
            // 2. Reverse of a one element list is itself
            return self
        }
    }
}

/**
 @return Returns a newly created Node consisting of the lhs list appended with rhs value.
 */
public func +<T>(lhs: Node<T>, rhs: T) -> Node<T> {
    let tail: Node<T>?
    if let next = lhs.next {
        // The new tail is created recursively, as long as there is a next node.
        tail = next + rhs
    } else {
        // If there is not a next node, create a new tail node to append
        tail = Node<T>(value: rhs, next: nil)
    }
    // Return a newly created Node consisting of the lhs list appended with rhs value.
    return Node<T>(value: lhs.value, next: tail)
}

0

Membalik daftar tertaut menggunakan rekursi. Idenya adalah menyesuaikan tautan dengan membalik tautan.

  public ListNode reverseR(ListNode p) {

       //Base condition, Once you reach the last node,return p                                           
        if (p == null || p.next == null) { 
            return p;
        }
       //Go on making the recursive call till reach the last node,now head points to the last node

        ListNode head  = reverseR(p.next);  //Head points to the last node

       //Here, p points to the last but one node(previous node),  q points to the last   node. Then next next step is to adjust the links
        ListNode q = p.next; 

       //Last node link points to the P (last but one node)
        q.next = p; 
       //Set the last but node (previous node) next to null
        p.next = null; 
        return head; //Head points to the last node
    }

1
Bisakah Anda menjelaskan lebih lanjut jawaban Anda dengan menambahkan sedikit lebih banyak deskripsi tentang solusi yang Anda berikan?
abarisone

1
Saya menambahkan komentar. Terima kasih banyak
gurubelli

0
public void reverseLinkedList(Node node){
    if(node==null){
        return;
    }

    reverseLinkedList(node.next);
    Node temp = node.next;
    node.next=node.prev;
    node.prev=temp;
    return;
}

-1
public void reverse(){
    if(isEmpty()){
    return;
     }
     Node<T> revHead = new Node<T>();
     this.reverse(head.next, revHead);
     this.head = revHead;
}

private Node<T> reverse(Node<T> node, Node<T> revHead){
    if(node.next == null){
       revHead.next = node;
       return node;
     }
     Node<T> reverse = this.reverse(node.next, revHead);
     reverse.next = node;
     node.next = null;
     return node;
}

-1

Berikut adalah referensi jika seseorang mencari implementasi Scala:

scala> import scala.collection.mutable.LinkedList
import scala.collection.mutable.LinkedList

scala> def reverseLinkedList[A](ll: LinkedList[A]): LinkedList[A] =
         ll.foldLeft(LinkedList.empty[A])((accumulator, nextElement) => nextElement +: accumulator)
reverseLinkedList: [A](ll: scala.collection.mutable.LinkedList[A])scala.collection.mutable.LinkedList[A]

scala> reverseLinkedList(LinkedList("a", "b", "c"))
res0: scala.collection.mutable.LinkedList[java.lang.String] = LinkedList(c, b, a)

scala> reverseLinkedList(LinkedList("1", "2", "3"))
res1: scala.collection.mutable.LinkedList[java.lang.String] = LinkedList(3, 2, 1)

Saya akan dengan senang hati meningkatkan jawaban saya jika orang yang tidak memberikan suara positif memberi saya penjelasan atas tindakannya. Bagaimanapun, ini masih berfungsi untuk saya di Scala :)
Venkat Sudheer Reddy Aedama

Asal tahu saja downvoter, ini adalah solusi rekursif (sebenarnya, rekursif ekor).
Venkat Sudheer Reddy Aedama

Scala bukanlah Java, meskipun keduanya berjalan di JVM.
Bill Lynch

@sharth Wow, senang mengetahuinya. Apakah Anda repot-repot membaca baris pertama pada jawaban saya?
Venkat Sudheer Reddy Aedama

@VenkatSudheerReddyAedama Anda mendapat suara negatif karena pertanyaan asli meminta penerapan di Jawa. Meskipun Scala berjalan di JVM, ini tidak membantu menjawab pertanyaan ... meskipun cukup elegan. FWIW, saya tidak meremehkan Anda.
rayryeng
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.