我的方法无法消除单链表中最大重复元素序列。

huangapple go评论62阅读模式
英文:

My method for eliminating the largest sequence of repeated elements on singly linked list does not work

问题

这段代码应该在一个节点列表中搜索最长的重复元素序列然后消除它每个节点只连接到下一个元素是一个单向链表)。执行过程卡在列表的最后一个元素上没有被打印我不明白为什么),代码永远不会完成永远不会打印出最后的print语句)。我的代码确实正确地计算了每个元素的位置但它卡在了最后一个元素上在之前调试代码时要么没有消除最长序列要么就是删除了整个列表

```python
from slistH import SList
from slistH import SNode
import sys

class SList2(SList):
    def delLargestSeq(self):
        # 在这里实现你的解决方案
        previous_to_seq = self._head  # 我们从所有指针的第一个位置开始
        first_after_sequence = self._head
        pointer2 = self._head
        pointer = self._head
        new_pointer = self._head  # 这个指针将存储重复次数最多的节点

        a = 0  # 这个变量控制条件是否第一次被满足
        count = 0

        first = 0  # 这个变量控制序列的第一个元素的位置
        last = 0
        prev_first = 0  # 存储最长序列的第一个元素的位置(直到检测到更长的序列)
        prev_last = 0

        while pointer is not None:
            if a == 0:
                if pointer.elem == pointer.next.elem:
                    last = count + 1
            else:
                if (last - first) >= (prev_last - prev_first):
                    prev_first = first
                    prev_last = last
                    new_pointer = pointer

                first = last + 1
                last = first

            print(f"{pointer.elem} || {count}")  # 这些只是为了观察一切是否正常工作

            pointer = pointer.next
            count += 1
            if pointer is None:
                a = 1

        for i in range(prev_last):
            if pointer2.next.elem == new_pointer.elem:
                if a == 1:
                    previous_to_seq = pointer2
                    a = 2

            elif a == 2 and pointer2.next.elem != new_pointer.elem:
                first_after_sequence = pointer2.next
                a = 3

            pointer2 = pointer2.next

        if previous_to_seq.elem == new_pointer.elem:
            previous_to_seq = previous_to_seq.next

        previous_to_seq.next = first_after_sequence
        print(f"序列后的第一个元素:{first_after_sequence.elem}")
        print(f"序列前的最后一个元素:{previous_to_seq.elem}")

nodes_list = SList2()
for i in (1, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6):
    nodes_list.addLast(i)

print(nodes_list)

nodes_list.delLargestSeq()

print(f"{nodes_list} || 最终列表")
英文:

This code is supposed to search through a Node list for the largest sequence of repeated elements and then eliminate it (each Node has connection only to the next element, it is a singly linked list). The execution gets stuck in the last element of the list ( which isnt printed, I dont understand why) and the code never finishes ( it never gets to print the last "print").My code does count correctly the position of each element but it gets stuck on the last one. In previous tinkering with the code it either not eliminate the largest sequence or it deleted the whole list.

from slistH import SList
from slistH import SNode
import sys
class SList2(SList):
def delLargestSeq(self):
# implement here your solution
previous_to_seq = self._head # we start at the first position for all pointers
first_after_sequence = self._head
pointer2 = self._head
pointer = self._head
new_pointer = self._head # this pointer will store the node that is the most repeated
a = 0  # this variable controls that it has been the first time that a condition has been fulfilled
count = 0 
first = 0 # this variable controls the position of the first element o a sequence
last = 0 
prev_first = 0 # stores the biggest sequence's first element position (until a bigger one is detected)
prev_last = 0   
while pointer is not None: 
if  a == 0 : 
if pointer.elem == pointer.next.elem:
last = count + 1 
else:
if (last - first) >= (prev_last - prev_first): # if a bigger sequence is detected...
prev_first = first 
prev_last = last 
new_pointer = pointer # this will now be the most repeated element
first = last + 1 # the next element will be the first of the next sequence that we will compare
last = first
print(f"{pointer.elem} || {count} ") # these are just to observe that everything is working
pointer = pointer.next 
count += 1 
if pointer is None:
a = 1
for i in range(prev_last): # goes through the list until the biggest sequence's last element's position
if pointer2.next.elem == new_pointer.elem: 
if a == 1: # if it is the first element of the sequence...
previous_to_seq = pointer2 # it is the first element before the biggest sequence
a = 2 # this ensures that it is the first time that this has been executed
elif a == 2 and pointer2.next.elem != new_pointer.elem: 
first_after_sequence = pointer2.next # first element different from the ones in the sequence after it
a = 3 
pointer2 = pointer2.next
if previous_to_seq.elem == new_pointer.elem: # in case there is no element before the sequence
previous_to_seq = previous_to_seq.next
previous_to_seq.next = first_after_sequence
print(f"first after sequence: {first_after_sequence.elem}")
print(f"last before sequence {previous_to_seq.elem}")
nodes_list = SList2()
for i in (1, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6):
nodes_list.addLast(i)
print(nodes_list)
nodes_list.delLargestSeq()
print(f"{nodes_list} || final list")          

The execution gets stuck in the last element of the list ( which isnt printed, I dont understand why) and the code never finishes ( it never gets to print the last "print").My code does count correctly the position of each element but it gets stuck on the last one. In previous tinkering with the code it either not eliminate the largest sequence or it deleted the whole list. I also had a problem where the loop reached "None" and None has no ".next".

答案1

得分: 0

这个语句最终会在 pointer 是列表中的最后一个节点时执行:

if pointer.elem == pointer.next.elem:

在那一刻,pointer.nextNone,所以 pointer.next.elem 是一个无效的引用,会产生错误。

其他一些备注:

  • 你有太多只是陈述显而易见事实的注释。注释不应该将语句翻译成英文,而应该提供更高层次的含义。如果没有这样的含义可提供,就不要添加注释。

  • 命名很重要,可以使代码更易读(而不需要在每一行都加注释)。例如,a 是一个不好的名称,它应该表示“...这是第一次条件被满足的时候”。

  • 在第一个循环中,变量 a 仅在循环即将退出时更新,这意味着第一个循环永远不会执行 else 块,并且永远不会更新 first 的值。

  • 该算法过于依赖位置。在链表中,你不应该需要追踪位置(索引),而是应该追踪节点。索引是你通常在使用原生列表时会使用的东西,但在链表中并不需要。追踪节点引用的优势在于你不需要第二个循环来执行实际的移除。在链表中,移除只需一个赋值操作。

  • 在你的代码中,没有考虑到移除必须发生在列表的开始的情况,因为在这种情况下,_head 引用应该被更新,但在你的实现中没有这样的语句。

还有更多问题,但我认为这已经足够解释为什么该算法不起作用了。

假设

我在下面添加了一个实现,但我不得不做一些在你的问题中没有明确说明的假设:

  • 如果一个(非空)列表没有两个连续的节点具有相等的值,那么最长的重复值序列将为1。由于这实际上不能被称为“重复”,我假设在这种情况下,列表应该保持原样,不进行任何移除操作。

  • 如果列表有两个或更多的具有相同长度的重复段,而且这些段碰巧是最长的,那么只有第一个节点段将被移除。

  • 可以创建一个辅助节点,可以用 SNode(None) 创建。由于你没有提供 SNode 的实现,我只能猜测构造函数可以这样调用。

算法

为了从链表中删除一个段,最好跟踪该段之前的节点,因为那个节点将需要更新其 next 属性。第一组重复值可能出现在列表的开头,那么就没有这样的前置节点。在这种情况下,我们有一个不同的情况,那就是列表的 _head 属性应该被更新(如果它是最长的重复系列的话)。

为了允许以相同的方式处理这些不同的情况,通常的做法是创建一个在列表的头节点之前的虚拟节点。然后第一个系列的前置节点是那个虚拟节点,如果需要移除这个系列,我们可以在不采取任何特殊预防措施的情况下更新前置节点的 next 属性。最后,当相关时,我们可以将 dummy.next 赋值回 _head 属性,这样在相关情况下会正确更新头。

实现

    def delLargestSeq(self):
        # 在列表前创建一个虚拟节点,这样可以简化代码,方便在处理结束时更新头节点
        dummy = SNode(None)
        dummy.next = self._head
        
        # 为了有效地移除,我们应该跟踪*第一个*要移除的节点之前的节点
        beforeDupes = dummy
        beforeFirstToRemove = lastToRemove = None
        numNodesToRemove = 1
        
        while beforeDupes.next:
            endDupes = beforeDupes.next
            numNodes = 1
            # 尽可能扩展节点的选择,只要它们具有相同的值
            while endDupes.next and endDupes.next.elem == endDupes.elem:
                numNodes += 1
                endDupes = endDupes.next
            # 跟踪最长的序列
            if numNodes > numNodesToRemove:
                numNodesToRemove = numNodes
                beforeFirstToRemove = beforeDupes
                lastToRemove = endDupes
            beforeDupes = endDupes

        # 假设移除必须涉及至少2个节点,因为单个节点不能被称为“重复”
        if numNodesToRemove > 1: 
            # 移除最长的重复序列
            beforeFirstToRemove.next = lastToRemove.next
            # 如果序列从第一个节点开始,头引用必须被更新
            self._head = dummy.next

repl.it上运行。

可视化

让我们以一个例子来说明:

1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6

当算法识别到第一个重复值系列,并且这是迄今为止最长的系列时,状态如下:

dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑               ↑
beforeDupes     end
英文:

This statement will eventually run when pointer is the last node in the list:

if pointer.elem == pointer.next.elem:

At that moment pointer.next is None, and so pointer.next.elem is an invalid reference, and will produce an error.

Some other remarks:

  • You have too many comments that just state the obvious. A comment should not translate the statement into English, but should give a higher level of meaning. If there is no such meaning to give, then don't add a comment.

  • Naming is important and could make the code more readable (without the need to comment every line). For instance, a is a bad name for something that is supposed to indicate "...that it has been the first time that a condition has been fulfilled".

  • In the first loop, the variable a is only updated when the loop is about to exit, so that means that the first loop will never execute the else block and will never update the value of first

  • The algorithm depends too much on positions. In a linked list you should not need to keep track of positions (indices), but of nodes. Indices are what you would typically use when working with a native list, not so with linked lists. The advantage of keeping track of node references is that you will not need a second loop to perform the actual removal. In a linked list, a removal can be done with one assignment only.

  • There is no provision in your code for the case where the removal has to happen at the very start of the list, because in that case the _head reference should be updated, but there is no such statement in your implementation.

There are more issues, but I think this is enough to explain why the algorithm doesn't work.

Assumptions

I add an implementation below, but I had to make some assumptions which were not clarified in your question:

  • If a (non-empty) list does not have two consecutive nodes that have an equal value, then the longest sequence with repeating values would be 1. As this is not really something that could be called "duplicate", I assume that in this case the list should stay as it is, without any removal.

  • If the list has two or more sections with duplicates that have the same length, and these happen to be the longest, then only the first section of nodes will be removed.

  • It is OK to create a helper node, which can be created with SNode(None). As you didn't provide the SNode implementation, I can only guess that the constructor can be called like that.

Algorithm

To remove a section from a linked list, you can better track which is the node that precedes that section, because it is that node that will need its next attribute to be updated.

The first section of duplicates could occur at the very start of the list, and then there is no such predecessor node. In that case we have a different situation where the _head attribute of the list should be updated (if it happens to be the longest duplicate series).

To allow these different scenarios to be dealt with in the same way, it is common practice to create a dummy node that is prefixed before the head node of the list. Then the predecessor node for the first series is that dummy node, and if that section needs to be removed, we can update the next attribute of the predecessor without taking any special precautions. Finally we can assign dummy.next back to the _head attribute and this will update the head correctly when relevant.

Implementation

    def delLargestSeq(self):
# Create a dummy node in front of the list, as this facilitates the rest
#   of the code, and makes it easy to update the head at the end of the process
dummy = SNode(None)
dummy.next = self._head
# For efficient removal, we should keep track of the node that *precedes*
#    the first node to remove
beforeDupes = dummy
beforeFirstToRemove = lastToRemove = None
numNodesToRemove = 1
while beforeDupes.next:
endDupes = beforeDupes.next
numNodes = 1
# Extend the selection of nodes for as long as they have the same value
while endDupes.next and endDupes.next.elem == endDupes.elem:
numNodes += 1
endDupes = endDupes.next
# Keep track of the longest sequence
if numNodes > numNodesToRemove:
numNodesToRemove = numNodes
beforeFirstToRemove = beforeDupes
lastToRemove = endDupes
beforeDupes = endDupes
# Assuming that a removal must concern at least 2 nodes, 
#     as a single node cannot be called "duplicate"
if numNodesToRemove > 1: 
# Remove the longest sequence of duplicates
beforeFirstToRemove.next = lastToRemove.next
# If the sequence started at the first node, the head reference must be updated
self._head = dummy.next

See it run on repl.it

Visualisation

Let's take the example list:

1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6

When the algorithm has identified the first series of duplicate values, and this is the longest series so far, we have this state:

dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑               ↑
beforeDupes     endDupes
beforeFirstToRemove lastToRemove

Then beforeDupes resumes from endDupes onwards and a next sequence is identified:

dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑       ↑
↑          beforeDupes endDupes
beforeFirstToRemove lastToRemove

This one does not update the beforeFirstToRemove or lastToRemove references as it is not greater in size. Same for:

dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑           ↑
↑               ↑  beforeDupes   endDupes
beforeFirstToRemove lastToRemove

...and:

dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑       ↑
↑               ↑            beforeDupes endDupes
beforeFirstToRemove lastToRemove

Then the removal part kicks in:

dummy
↓      ┌─────────────────┐
None → 1 ┘ 3 → 3 → 3 → 3 → └→ 4 → 4 → 5 → 5 → 5 → 6 → 6
↑               ↑ 
beforeFirstToRemove lastToRemove

And that's basically it.

huangapple
  • 本文由 发表于 2023年3月9日 17:10:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/75682477.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定