简体   繁体   English

链表中的递归

[英]Recurse in Linked List

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.我想知道一种进行递归的方法,这样就不必通过链接列表对 go 使用“while”,我已经通过 ZA3CBC3F9D0CE1F2C155CZE1D6 的 ZA3CBC3F9D0CE2F2C155CZE1B6 使用了对 go 的递归它失败。

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);

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).这个想法是递归虽然偶尔与面向对象(OO)系统一起使用,但与函数式编程(FP)更紧密地联系在一起。 So if you're going to use recursion on your lists, you might as well use it with FP lists.因此,如果您要在列表中使用递归,不妨将其与 FP 列表一起使用。

Creating and manipulating lists is one of the strengths of FP, and we can write your code much more simply.创建和操作列表是 FP 的优势之一,我们可以更简单地编写您的代码。 We create a bare list of one item, 42 by calling const list1 = ins (42) (null) .我们通过调用const list1 = ins (42) (null)创建一个项目的裸列表42 We prepend that with 17 by calling const list2 = ins (17) (list1) .我们通过调用const list2 = ins (17) (list1)在前面加上17 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:这可能是ins的样子:

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

We could choose to write this as (data, list) =>... instead of (data) => (list) =>... .我们可以选择将其写为(data, list) =>...而不是(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 nullnull

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.请注意,所有这些函数,除了insfind都是直接递归的。 They all call themselves.他们都称自己。 And find simply delegates the recursive work to search .find只是将递归工作委托给search

It's too much to try to describe all of these functions, but lets look at two.试图描述所有这些功能太多了,但让我们看两个。 print is a simple function. print是一个简单的 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:我们构建 output 字符串,包括我们的数据,后跟以下两项之一:

  • an empty string, if next is null一个空字符串,如果nextnull
  • '=>' plus the recursive print call on next , otherwise. '=>'加上next上的递归print调用,否则。

del is a somewhat more complex function: del是一个更复杂的 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 .如果是,我们只需返回存储为next的列表。

If not, we check whether next is null.如果不是,我们检查next是否是 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 .如果不是,那么我们返回一个由当前数据形成的新列表,并通过递归调用从存储为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.)如果你想了解更多关于这些想法的信息,你可能想搜索“Cons lists”(这里的“con”不是“pro”的反义词,而是与“construct”一些东西有关。)

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.如果你遇到carcdr ,它们分别相当于我们的datanext

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.有一部分展示了如何将 OOP 风格与功能性(持久性)数据结构结合起来,你会发现它很有帮助。

Similar to Scott's answer, we start by writing plain functions, no classes or methods.与 Scott 的回答类似,我们从编写普通函数开始,没有类或方法。 I'm going to place mine in a module named list.js -我将把我的放在一个名为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.现在我们可以实现我们的 OOP 风格的List接口。 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 - List接口让你可以用熟悉的OOP方式做事——

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.我认为这里重要的教训是,模块功能可以定义一次,之后可以添加OOP接口。


A series of tests ensures the information in this answer is correct.一系列测试确保此答案中的信息是正确的。 We start writing the plain function tests -我们开始编写简单的 function 测试 -

// 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接口的行为相应 -

// 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.raise - 允许您使用表达式而不是throw语句来引发错误

// func.js

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

// ...

export { ..., raise }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM