Recurse in Linked List

爷,独闯天下 提交于 2021-02-05 11:47:04

问题


I have been practicing the linked list and wanted to implement the recurse on it, although in some cases I was able to implement it efficiently, in other cases I failed miserably at doing so. I would like to know a method to do the recursive so as not to have to use the "while" to go through the Linked List, I have used the recurse to go through the arrays but when I wanted to do it similar in this case it fails.

I don't have much experience in implementing recursion and wanted to apply it in this method to get more experience with it, but at least it helped me understand the Linked List more by having to do it over and over again. Thank you.

class Node {
    // Accept arguments (the second one could be optional)
    constructor(data, next) {
        this.data = data; 
        this.next = next;

    }
    lastNode() { // new method that uses recursion
        return this.next?.lastNode() || this;
    }
}
class ListRecurse {
    constructor() {
        this.head = null;
        this.size = 0;
    }
    add(data) {
        let newNode = new Node(data); // No second argument. It has a default value
        if (this.head === null) {
            this.head = newNode;
        } else {
            // The lastNode implementation uses recursion:
            this.head.lastNode().next = newNode;
        }
        this.size ++;
        return this; // to allow chaining
    }
    insertAdd(data, index) {
        if (index < 0 || index > this.size) {
            return null;
        }
        let newNode = new Node(data);
        let current = this.head;
        let prev;
        if (index === 0) {
            newNode.next = current;
            this.head = newNode;
        }
        else {
            for (let i = 0; i < index; i++) {
                prev = current;
                current = current.next;
            }
            this.head.lastNode().next = current;
            prev.next = newNode;
        }
        this.size++;
        return this;
    }
    Print() {
        if (!this.size) {
            return null;
        }
        let current = this.head;
        let result = "";
        while(current) {
            result += current.data += "=>";
            current = current.next;
        }
        result += "X";
        return result;
        }
    DeletexData(data) {
           let current = this.head;
           let prev = null;
           if (this.head === null) {
               return null;
           }
           else if (current.data === data) {
               if(!prev) {
                   this.head = this.head.next;
               }
               else
               prev.next = current.next
           }
           return this.SearchDelete(data)
           }
    SearchDelete (data) {
            let current = this.head;
            let prev = null;
            while(current != null) {
                if (current.data === data) {
                    if (!current.next) prev.next = null
                else prev.next = current.next
                    this.size--;
                    return data;
                    }
                prev = current;
                current = current.next;
            }
        return null;
        }
    DeleteLastNode() {
        let current = this.head;
        if (current === null) {
            return 1
        }
        else if (current.next === null) {
            this.head = null;
        }
        else return this.LastNode()
        };
    LastNode() {
        let current = this.head;
        while (current.next.next != null) {
            current = current.next;
        }
        current.next = null;
        this.size--;
    }
    Search(data) {
        let current = this.head;
        if (current === null) {
            return null;
        }
        else 
        return this.RainbowSix(data)
    }
    RainbowSix(data) {
        let current = this.head; 
        while (current) {
            if (current.data === data) {
                return current;
            }
            current = current.next;
        }
        return null;
    }
    Size(){
        return this.size
    }
}
let list = new ListRecurse();
list.add(1).add(2).add(3).add(44).add(66);
list.insertAdd(33,0)
list.DeleteLastNode()
console.log(list.Search(3))
console.log(list.Size())
console.log(list.Print())
console.log(list);

回答1:


I was working on this answer as Scott made his post, making most of this information redundant. There is a portion which shows how to couple OOP-style with functional (persistent) data structures which you should find helpful.

Similar to Scott's answer, we start by writing plain functions, no classes or methods. I'm going to place mine in a module named list.js -

// list.js
import { raise } from "./func"

const nil =
  Symbol("nil")

const isNil = t =>
  t === nil

const node = (value, next) =>
  ({ node, value, next })

const singleton = v =>
  node(v, nil)

const fromArray = a =>
  a.reduceRight((r, _) => node(_, r), nil)

const insert = (t, v, i = 0) =>
  isNil(t)
    ? singleton(v)
: i > 0
    ? node(t.value, insert(t.next, v, i - 1))
: node(v, t)

const last = t =>
  isNil(t)
    ? raise("cannot get last element of empty list")
: isNil(t.next)
    ? t.value
: last(t.next)

const search = (t, q) =>
  isNil(t)
    ? undefined
: t.value === q
    ? t
: search(t.next, q)

const size = t =>
  isNil(t)
    ? 0
    : 1 + size(t.next)

const toString = t =>
  isNil(t)
    ? "Nil"
    : `${t.value}->${toString(t.next)}`

const toArray = t =>
  isNil(t)
    ? []
    : [ t.value, ...toArray(t.next) ]

Now we can implement our OOP-style, List interface. This gives you the chaining behaviour you want. Notice how the methods are simple wrappers around the plain functions we wrote earlier -

// list.js (continued)

class List
{ constructor(t = nil)
  { this.t = t }

  isNil()
  { return isNil(this.t) }

  size()
  { return size(this.t) }

  add(v)
  { return new List(node(v, this.t)) }

  insert(v, i)
  { return new List(insert(this.t, v, i)) }

  toString()
  { return toString(this.t) }
}

Finally, make sure to export the parts of your module

// list.js (continued)

export { nil, isNil, node, singleton, fromArray, insert, last, search, size, toArray, toString }

export default List

The List interface allows you to do things in the familiar OOP ways -

import List from "../list"

const t = (new List).add(3).add(2).add(1)

console.log(t.toString())
// 1->2->3->Nil

console.log(t.insert(9, 0).toString())
// 9->1->2->3->Nil

console.log(t.isNil())
// false

console.log(t.size())
// 3

Or you can import your module and work in a more functional way -

import * as list from "../list"

const t = list.fromArray([1, 2, 3])


console.log(list.toString(t))
// 1->2->3->Nil

console.log(list.isNil(t))
// true

console.log(list.size(t))
// 3

I think the important lesson here is that the module functions can be defined once, and then the OOP interface can be added afterwards.


A series of tests ensures the information in this answer is correct. We start writing the plain function tests -

// list_test.js

import List, * as list from "../list.js"
import * as assert from '../assert.js'
import { test, symbols } from '../test.js'

await test("list.isNil", _ => {
  assert.pass(list.isNil(list.nil))
  assert.fail(list.isNil(list.singleton(1)))
})

await test("list.singleton", _ => {
  const [a] = symbols()
  const e = list.node(a, list.nil)
  assert.equal(e, list.singleton(a))
})

await test("list.fromArray", _ => {
  const [a, b, c] = symbols()
  const e = list.node(a, list.node(b, list.node(c, list.nil)))
  const t = [a, b, c]
  assert.equal(e, list.fromArray(t))
})

await test("list.insert", _ => {
  const [a, b, c, z] = symbols()
  const t = list.fromArray([a, b, c])
  assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, 0))
  assert.equal(list.fromArray([a,z,b,c]), list.insert(t, z, 1))
  assert.equal(list.fromArray([a,b,z,c]), list.insert(t, z, 2))
  assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 3))
  assert.equal(list.fromArray([a,b,c,z]), list.insert(t, z, 99))
  assert.equal(list.fromArray([z,a,b,c]), list.insert(t, z, -99))
})

await test("list.toString", _ => {
  const e = "1->2->3->Nil"
  const t = list.fromArray([1,2,3])
  assert.equal(e, list.toString(t))
  assert.equal("Nil", list.toString(list.nil))
})

await test("list.size", _ => {
  const [a, b, c] = symbols()
  assert.equal(0, list.size(list.nil))
  assert.equal(1, list.size(list.singleton(a)))
  assert.equal(2, list.size(list.fromArray([a,b])))
  assert.equal(3, list.size(list.fromArray([a,b,c])))
})

await test("list.last", _ => {
  const [a, b, c] = symbols()
  const t = list.fromArray([a,b,c])
  assert.equal(c, list.last(t))
  assert.throws(Error, _ => list.last(list.nil))
})

await test("list.search", _ => {
  const [a, b, c, z] = symbols()
  const t = list.fromArray([a, b, c])
  assert.equal(t, list.search(t, a))
  assert.equal(list.fromArray([b, c]), list.search(t, b))
  assert.equal(list.singleton(c), list.search(t, c))
  assert.equal(undefined, list.search(t, z))
})

await test("list.toArray", _ => {
  const [a,b,c] = symbols()
  const e = [a,b,c]
  const t = list.fromArray(e)
  assert.equal(e, list.toArray(t))
})

Next we ensure the List interface behaves accordingly -

// list_test.js (continued)

await test("List.isNil", _ => {
  assert.pass((new List).isNil())
  assert.fail((new List).add(1).isNil())
})

await test("List.size", _ => {
  const [a,b,c] = symbols()
  const t1 = new List
  const t2 = t1.add(a)
  const t3 = t2.add(b)
  const t4 = t3.add(c)
  assert.equal(0, t1.size())
  assert.equal(1, t2.size())
  assert.equal(2, t3.size())
  assert.equal(3, t4.size())
})

await test("List.toString", _ => {
  const t1 = new List
  const t2 = (new List).add(3).add(2).add(1)
  assert.equal("Nil", t1.toString())
  assert.equal("1->2->3->Nil", t2.toString())
})

await test("List.insert", _ => {
  const t = (new List).add(3).add(2).add(1)
  assert.equal("9->1->2->3->Nil", t.insert(9, 0).toString())
  assert.equal("1->9->2->3->Nil", t.insert(9, 1).toString())
  assert.equal("1->2->9->3->Nil", t.insert(9, 2).toString())
  assert.equal("1->2->3->9->Nil", t.insert(9, 3).toString())
  assert.equal("1->2->3->9->Nil", t.insert(9, 99).toString())
  assert.equal("9->1->2->3->Nil", t.insert(9, -99).toString())
})

Dependencies used in this post -

func.raise - allows you to raise an error using an expression instead of a throw statement

// func.js

const raise = (msg = "", E = Error) => // functional throw
  { throw E(msg) }

// ...

export { ..., raise }



回答2:


This may or may not help. It suggests a substantially different way to build your lists.

The idea is that recursion, although occasionally used with Object-Oriented (OO) systems, is much more closely tied to Functional Programming (FP). So if you're going to use recursion on your lists, you might as well use it with FP lists.

Creating and manipulating lists is one of the strengths of FP, and we can write your code much more simply. We create a bare list of one item, 42 by calling const list1 = ins (42) (null). We prepend that with 17 by calling const list2 = ins (17) (list1). Or we can write a whole chain of these like this:

const list3 = ins (1) (ins (2) (ins (3) (ins (4) (ins (5) (null)))))

There are many differences from your code, but one of the most fundamental, is that this treats lists as immutable objects. None of our code will change a list, it will just create a new one with the altered properties.

This is what ins might look like:

const ins = (data) => (list) => 
  ({data, next: list})

We could choose to write this as (data, list) => ... instead of (data) => (list) => .... That's just a matter of personal preference about style.

But the basic construction is that a list is

  • a value
  • followed by either
    • another list
    • or null

Here is an implementation of these ideas:

const ins = (data) => (list) => 
  ({data, next: list})

const del = (target) => ({data, next}) =>
  target == data ? next : next == null ? {data, next} : {data, next: del (target) (next)}  

const delLast = ({data, next}) =>
  next == null ? null : {data, next: delLast (next)}

const size = (list) => 
  list == null ? 0 : 1 + size (list.next)

const search = (pred) => ({data, next}) => 
  pred (data) ? {data, next} : next != null ? search (pred) (next) : null

const fnd = (target) => 
  search ((data) => data == target)

const print = ({data, next}) => 
  data + (next == null ? '' : ('=>' + print (next)))    

const list1 = ins (1) (ins (2) (ins (3) (ins (44) (ins (66) (null)))))
const list2 = ins (33) (list1)
const list3 = delLast (list2)

console .log (fnd (3) (list3))
console .log (size (list3))
console .log (print (list3))
console .log (list3)
.as-console-wrapper {max-height: 100% !important; top: 0}

Note that all of these functions, except for ins and find are directly recursive. They all call themselves. And find simply delegates the recursive work to search.

It's too much to try to describe all of these functions, but lets look at two. print is a simple function.

const print = ({data, next}) => 
  data + (next == null ? '' : ('=>' + print (next)))    

We build our output string by including our data followed by one of two things:

  • an empty string, if next is null
  • '=>' plus the recursive print call on next, otherwise.

del is a somewhat more complex function:

const del = (target) => ({data, next}) =>
  target == data
    ? next 
    : next == null 
      ? {data, next: null} 
      : {data, next: del (target) (next)}  

We test if our current data is the target we want to delete. If it is, we simply return the list stored as next.

If not, we check whether next is null. If it is, we return (a copy of) the current list. If it is not, then we return a new list formed by our current data and a recursive call to delete the target from the list stored as next.


If you want to learn more about these ideas, you probably want to search for "Cons lists" ("con" here is not the opposite of "pro", but has to do with "construct"ing something.)

I used different terms than are most commonly used there, but the ideas are much the same. If you run across the terms car and cdr, they are equivalent to our data and next, respectively.



来源:https://stackoverflow.com/questions/65290209/recurse-in-linked-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!