这是 Space 复杂性的奇怪问题。有人可以提供任何见解吗?
This is strange question of Space Complexity. Can someone provide any insights?
我在解决这个问题时点击了这个方法 -
给定一个单链表和一个整数 x。您的任务是完成 deleteAllOccurances() 函数,该函数删除链表中出现的所有键 x。该函数有两个参数:链表的头部和一个整数 x。该函数应该return修改链表的头部。
我不确定我的代码的 space 复杂度是多少。
我想因为我只使用了 1 个额外的节点 space 并且同时创建新节点和删除旧节点,所以它应该是 O(1)。
Node* deleteAllOccurances(Node *head,int x)
{
Node *new_head = new Node(-1);
Node *tail = new_head;
Node *temp = head;
Node *q;
while(temp != NULL) {
if(temp->data != x) {
tail->next = new Node(temp->data);
tail = tail->next;
}
q = temp;
delete q;
temp = temp->next;
}
tail->next = NULL;
return new_head->next;
}
是的,因为您在任何时候分配了多少 space 并不取决于参数(例如列表的长度或列表中有多少 x
的值) space 函数的复杂度是 O(1)
space 复杂度的实际意义在于查看您的算法需要多少内存。您永远不需要超过 1 个内存节点(加上局部变量),O(1)
反映了这一点。
嗯,有点。
这取决于您是否将总分配视为净变化(在这种情况下您是对的)。
但是 如果您正在考虑为新分配访问堆的次数,那么它会使用更多 space 和大量计算。 (给定的 C++ 编译器和运行时 不是 有义务保证立即重用 space 在堆中释放,只是它 可用 用于重用.)
作为一名几十年的 C++ 程序员,您所做的事情有点令人恐惧,因为您正在做 很多 的新分配。这会导致堆分配结构的颠簸。
此外,您执行此操作的方式是将不匹配的内容推送到列表的末尾,因此您正在将内容打乱。
提示 - 您不需要创建 任何 个新节点。
衡量复杂性部分取决于您认为的变量。就列表中的节点数而言,您的算法在 space 用法中是 O(1)
。但是,这可能不是这种情况下的最佳视角。
这种情况下的另一个变量是节点的大小。通常这个方面被复杂性分析所忽略,但我认为它在这种情况下是有价值的。虽然您的算法的 space 要求不取决于节点数,但它确实取决于节点的大小。节点中的数据越多,您需要的 space 就越多。令s
为单个节点的大小;公平地说,您的算法的大小要求是 O(s)
.
此任务的更常见算法的大小要求是 O(1)
,即使考虑到节点数和每个节点的大小也是如此。 (它不需要创建任何节点,不需要复制数据。)我不会推荐你的算法。
为了避免全盘否定,我会将您的方法视为对传统方法的两个独立更改。一个变化是引入了虚拟节点 new_head
。此更改很有用(事实上正在使用),即使您的实现会泄漏内存。它只比不使用虚拟头稍微低一些效率,并且它简化了从列表前面删除节点的逻辑。只要您的节点大小不过大,这就很好。
另一个变化是切换到复制节点而不是移动它们。这是一个令人畏惧的变化,因为它无偿地增加了程序员、编译器和执行的工作量。渐近分析(big-O)可能不会接受这个加法,但它没有任何有益的收获。您已经破坏了链表的一个关键优势,并且在 return.
中一无所获
让我们看看删除第二个更改。您需要添加一行,特别是将 new_head->next
初始化为 head
,但这通过消除在末尾将 tail->next
设置为 nullptr
的需要来平衡。另一个添加是 else
子句,因此当前每次迭代 运行 的行不一定是每次迭代 运行 。除此之外,还有代码删除和一些名称更改:删除 temp
指针(改为使用 tail->next
)并删除循环中新节点的创建。总而言之,与您的代码相比,这些更改严格减少了完成的工作(和内存需求)。
为了解决内存泄漏问题,我使用了本地虚拟节点而不是动态分配它。这删除了最后一次使用 new
,这反过来又删除了问题评论中提出的大部分反对意见。
Node* deleteAllOccurances(Node *head, int x)
{
Node new_head{-1}; //<-- Avoid dynamic allocation
new_head.next = head; //<-- added line
Node *tail = &new_head;
while(tail->next != nullptr) {
if(tail->next->data != x) {
tail = tail->next;
}
else { //<-- make the rest of the loop conditional
Node *q = tail->next;
tail->next = tail->next->next;
delete q;
}
}
return new_head.next;
}
此版本删除了 "cringe factor",因为有利于创建一个节点,并且 new
未被使用。这个版本足够干净,无需每个人询问就可以进行复杂性分析 "why???".
我在解决这个问题时点击了这个方法 -
给定一个单链表和一个整数 x。您的任务是完成 deleteAllOccurances() 函数,该函数删除链表中出现的所有键 x。该函数有两个参数:链表的头部和一个整数 x。该函数应该return修改链表的头部。
我不确定我的代码的 space 复杂度是多少。
我想因为我只使用了 1 个额外的节点 space 并且同时创建新节点和删除旧节点,所以它应该是 O(1)。
Node* deleteAllOccurances(Node *head,int x)
{
Node *new_head = new Node(-1);
Node *tail = new_head;
Node *temp = head;
Node *q;
while(temp != NULL) {
if(temp->data != x) {
tail->next = new Node(temp->data);
tail = tail->next;
}
q = temp;
delete q;
temp = temp->next;
}
tail->next = NULL;
return new_head->next;
}
是的,因为您在任何时候分配了多少 space 并不取决于参数(例如列表的长度或列表中有多少 x
的值) space 函数的复杂度是 O(1)
space 复杂度的实际意义在于查看您的算法需要多少内存。您永远不需要超过 1 个内存节点(加上局部变量),O(1)
反映了这一点。
嗯,有点。
这取决于您是否将总分配视为净变化(在这种情况下您是对的)。
但是 如果您正在考虑为新分配访问堆的次数,那么它会使用更多 space 和大量计算。 (给定的 C++ 编译器和运行时 不是 有义务保证立即重用 space 在堆中释放,只是它 可用 用于重用.)
作为一名几十年的 C++ 程序员,您所做的事情有点令人恐惧,因为您正在做 很多 的新分配。这会导致堆分配结构的颠簸。
此外,您执行此操作的方式是将不匹配的内容推送到列表的末尾,因此您正在将内容打乱。
提示 - 您不需要创建 任何 个新节点。
衡量复杂性部分取决于您认为的变量。就列表中的节点数而言,您的算法在 space 用法中是 O(1)
。但是,这可能不是这种情况下的最佳视角。
这种情况下的另一个变量是节点的大小。通常这个方面被复杂性分析所忽略,但我认为它在这种情况下是有价值的。虽然您的算法的 space 要求不取决于节点数,但它确实取决于节点的大小。节点中的数据越多,您需要的 space 就越多。令s
为单个节点的大小;公平地说,您的算法的大小要求是 O(s)
.
此任务的更常见算法的大小要求是 O(1)
,即使考虑到节点数和每个节点的大小也是如此。 (它不需要创建任何节点,不需要复制数据。)我不会推荐你的算法。
为了避免全盘否定,我会将您的方法视为对传统方法的两个独立更改。一个变化是引入了虚拟节点 new_head
。此更改很有用(事实上正在使用),即使您的实现会泄漏内存。它只比不使用虚拟头稍微低一些效率,并且它简化了从列表前面删除节点的逻辑。只要您的节点大小不过大,这就很好。
另一个变化是切换到复制节点而不是移动它们。这是一个令人畏惧的变化,因为它无偿地增加了程序员、编译器和执行的工作量。渐近分析(big-O)可能不会接受这个加法,但它没有任何有益的收获。您已经破坏了链表的一个关键优势,并且在 return.
中一无所获让我们看看删除第二个更改。您需要添加一行,特别是将 new_head->next
初始化为 head
,但这通过消除在末尾将 tail->next
设置为 nullptr
的需要来平衡。另一个添加是 else
子句,因此当前每次迭代 运行 的行不一定是每次迭代 运行 。除此之外,还有代码删除和一些名称更改:删除 temp
指针(改为使用 tail->next
)并删除循环中新节点的创建。总而言之,与您的代码相比,这些更改严格减少了完成的工作(和内存需求)。
为了解决内存泄漏问题,我使用了本地虚拟节点而不是动态分配它。这删除了最后一次使用 new
,这反过来又删除了问题评论中提出的大部分反对意见。
Node* deleteAllOccurances(Node *head, int x)
{
Node new_head{-1}; //<-- Avoid dynamic allocation
new_head.next = head; //<-- added line
Node *tail = &new_head;
while(tail->next != nullptr) {
if(tail->next->data != x) {
tail = tail->next;
}
else { //<-- make the rest of the loop conditional
Node *q = tail->next;
tail->next = tail->next->next;
delete q;
}
}
return new_head.next;
}
此版本删除了 "cringe factor",因为有利于创建一个节点,并且 new
未被使用。这个版本足够干净,无需每个人询问就可以进行复杂性分析 "why???".