我正在研究以下算法,以从“编程访谈曝光”一书中展平树状链表系统:
void flattenList( Node *head, Node **tail)
Node *curNode = head;
while( curNode ){
/* The current node has a child */
if( curNode->child ){
append( curNode->child, tail );
}
curNode = curNode->next;
}
}
/* Appends the child list to the end of the tail and updates
* the tail.
*/
void append( Node *child, Node **tail ){
Node *curNode;
/* Append the child child list to the end */
(*tail)->next = child;
child->prev = *tail;
/*Find the new tail, which is the end of the child child
*list.
*/
for( curNode = child; curNode->next;
curNode = curNode->next )
; /* Body intentionally empty */
/* Update the tail pointer now that curNode is the new
* tail.
*/
*tail = curNode;
}
我知道我们将**tail
而不是*tail
传递到append
函数,以确保对*tail
的更改仍然存在,但我不明白的是为什么我们不为孩子做同样的事情(这意味着我们为什么不通过**child
代替*child
)?
答案 0 :(得分:1)
C使用“按值调用”参数传递语义。这意味着,如果您调用类似f(x)
的函数,则只会将x
的值传递给函数,而不是变量x
本身。为函数内部的参数赋值时,用于调用函数的x
不会改变。
如果您希望能够更改变量本身,则必须传递此调用中的地址f(&x)
,如果您在已更改变量的函数内对*x
进行了赋值{ {1}}。
x
被声明为因为flattenList
是按值传递的(它不会改变),但是head
与其地址一起传递,因为函数必须更改它。 / p>
这类似于tail
功能。第一个参数不会改变,而第二个参数必须在函数内部改变。
答案 1 :(得分:1)
立即回答你的问题。 child
的{{1}}成员永远不会更新。它们仅用于遍历基础子列表。
老实说,参数名称(Node
)的选择不可能更糟糕(不能称之为child
或其他一些)。看看以下是否更清楚。它利用了这样一个事实:C是一个byval-call系统,并且实际上使用其中一个参数(节点指针)来查找从给定节点开始的列表的结尾而没有任何临时指针(我们使用节点指针本身用于走路,因为它是byval):
parent
我应该节点,反正这不是很好的代码。对于任何参数等,它都不会对NULL进行检查。这应该是非常强化的。我选择放弃错误检查和非空验证只是因为原始代码也将所有这些都留下了。如果你想要一个更强大的版本让我知道。
答案 2 :(得分:0)
append
中的评论非常清楚
/* Update the tail pointer now that curNode is the new
* tail.
*/
*tail = curNode;
因为您正在更新尾指针本身(而不仅仅是它引用的节点),所以您需要传递pointer to a pointer
才能使更新在函数外部可见。
对于child
指针,我们只更新它引用的节点,而不是指针本身。因此,没有理由通过添加另一个间接层来进一步使代码复杂化。
答案 3 :(得分:0)
struct node *flatten_nr(struct node *root)
{
struct node *temp1, *temp2;
if (NULL == root) return NULL;
temp2 = root;
while (root != NULL)
{
if( root->next != NULL)
{
temp1 = root;
/* go to the end of the list and append */
while (temp1->down != NULL)
{
temp1 = temp1->down;
}
temp1->down = root->next;
}
root = root->down;
}
return temp2;
}