0

So I'm stuck trying to solve this exercise from a book (C Programming: A Modern Approach - K.N. King Chapter 17 exercise 6):

Modify the delete_from_list function so that it uses only one pointer variable instead of two (cur and prev).

struct node {
    int value;
    struct node *next;
};

struct node *delete_from_list(struct node *list, int n)
{
    struct node *cur, *prev;

    for (cur = list, prev = NULL;
         cur != NULL && cur->value != n;
         prev = cur, cur = cur->next)
        ;
    if (cur == NULL)
        return list;
    if (prev == NULL)
        list = list->next;
    else
        prev->next = cur->next;
    free(cur);
    return list;
}

(The function returns the head of the list)

One way to do it would be to change the first function parameter to a pointer to pointer but that is asked in a following exercise.

I really can't think of a way to solve this using only one local variable without a memory leak or undefined behavior. Something like this:

struct node *delete_from_list(struct node *list, int n)
{
    struct node *p = list;

    if (!p)
        return NULL;

    if (p->value == n) {
        list = list->next;
        free(p);
        return list;
    }

    while (p->next && p->next->value != n)
        p = p->next;

    if (p->next)
//      free(p->next); // undefined behavior
        p->next = p->next->next; // memory leak

    return list;
}

Another way is recursion but I could never think of this by myself. I doubt this is the intention of the exercise since the exercises in this book are not complicated (this is the first one that I can't figure out) and there were only a few simple recursion exercises in a previous chapter. Also I think technically it's not really one local variable because each call of the function gets new variable copies.

struct node *delete_from_list(struct node *list, int n)
{
    struct node *t;

    if (list == NULL)
        return NULL;
    if (list->value == n) {
        t = list->next;
        free(list);
        return t;
    }
    list->next = delete_from_list(list->next, n);
    return list;
}

So is there any other way to do this?

  • it's perhaps the last approach that they thought of. it's just fine. – The Paramagnetic Croissant Jul 22 '14 at 22:13
  • The recursive version uses only one local variable. If you don't want "implicit" local variables from the recursion, then strictly speaking you shouldn't call `free` either since you don't know how many local variables it uses ;) – Fred Foo Jul 22 '14 at 22:23

3 Answers3

1

While it is possible to rewrite the cycle with only one variable (as you know already), to look for a way to free the memory without using at least one more variable doesn't seem to make much sense.

BTW, adding an extra level of indirection in such cases allows one to write more compact code with less branching. More specifically, deletion of the very first element of the list ceases to be a "special case" (which it is in your code)

struct node *delete_from_list(struct node *list, int n)
{
    struct node **pcur;

    for (pcur = &list; *pcur != NULL && (*pcur)->value != n; pcur = &(*pcur)->next)
        ;

    if (*pcur != NULL)
    {
      struct node *cur = *pcur;
      *pcur = (*pcur)->next;
      free(cur);
    }

    return list;
}

In any case, the requirement not to use extra local variables is not sufficiently clear, as @larsmans noted in the comments. What if free uses its own local variables? Is it OK or not? If it is, then it is probably OK to write your own version of free

struct node *my_free(struct node *node)
{
  struct node *next = node->next;  
  free(node);
  return next;
}

and then use it in delete_from_list

if (*pcur != NULL)
  *pcur = my_free(*pcur)

thus "eliminating" the extra local variable.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • @larsmans: The first paragraph of my answer is there to explain that I intend to rewrite the search cycle with only one variable, but I will not even attempt to eliminate the extra variable from the memory freeing process. – AnT stands with Russia Jul 22 '14 at 22:26
1

Okay, here's another way that truly does not use an assistant variable:

struct node * listdelete( struct node * list, int n ) {
    if ( list == NULL )
        return list;

    struct node * caret = list;
    if ( list->value == n ) {
        list = list->next;
        free( caret );
    }
    else {
        while ( caret->next != NULL && caret->next->value != n ) {
            caret = caret->next;
        }
        while ( caret->next->next != NULL ) {
            caret->next->value = caret->next->next->value;
            caret = caret->next;
        }
        free( caret->next );
        caret->next = NULL;
    }

    return list;
}

What it does is to simply shift the values of the nodes to the preceding node, and finally to delete the one at the end. Silly, I have to say, but it does what has been challenged. I didn't need any other variable during the process, not an integer, not a pointer, nor a pointer to a pointer.

Here's a functional example: http://ideone.com/gEy7Q0

Utkan Gezer
  • 3,009
  • 2
  • 16
  • 29
  • 1
    nice one ;) treat it like an array. I think it needs a check after the first while: if (caret->next == NULL)... –  Jul 23 '14 at 02:21
0

Here is an idea that only uses one pointer local variable (not counting list) and which doesn't move values from one node to another, but aims to first make the targeted node the tail node of the list. And then the deletion is trivial.

To make the target node the tail we can proceed as follows:

  • First deal with the boundary cases (empty list or node to delete is the first node).

  • Find the target node. If not found, return the original list pointer.

  • If the target node happens to be also the tail, then we can delete that node and return the list pointer.

  • Find the tail node.

  • "Abuse" the next member of the tail node (which was NULL) to traverse the list again, stopping at the target node. So now the original tail node is no longer the tail, but has the target node following after it. The target node has now two references to it, from its original predecessor and the original tail.

  • Traverse the list again, and this time link the original predecessor of the target with its successor. Now we have a linked list where the target node is referenced once, by the original tail node.

  • Continue the traversal until we meet the target node again, and delete it as if it were the tail (it actually has a non-NULL next member, but we ignore that).

That's it. There is one issue though: it will not work if the value to delete occurs multiple times in the list. To work around this, we could mark the node to delete with a special value which we will assume is not used elsewhere in the list. For example: INT_MAX.

Here is how it would look:

struct node *delete_from_list(struct node *list, int n) {
    struct node *cur = list;

    // Boundary cases
    if (list == NULL) return NULL;
    if (list->value == n) {
        list = list->next;
        free(cur);
        return list;
    }

    // Find the node with value n:
    while (cur->next != NULL && cur->next->value != n) cur = cur->next;

    if (cur->next == NULL) return list; // Nothing to delete

    // If the found node is not the tail node, we will first make it the tail
    if (cur->next->next != NULL) {
        // Mark the node with INT_MAX as value, assuming that doesn't occur elsewhere
        cur->next->value = INT_MAX;
    
        // find the tail of the list
        while (cur->next != NULL) cur = cur->next;
        
        // Use tail's next member to walk to the marked node
        cur->next = list;
        while (cur->next->value != INT_MAX) {
            cur->next = cur->next->next; // cur doesn't move; only its next member
        }
        
        // Find predecessor of the node to delete
        cur = list;
        while (cur->next->value != INT_MAX) cur = cur->next;
    
        // Unlink that node here (it still is linked to by original tail node)
        cur = cur->next = cur->next->next;
    
        // Find the node again, which we can now consider the tail
        while (cur->next->value != INT_MAX) cur = cur->next;
    }
    
    // As cur->next is the tail, we don't need to know its next pointer, and can free it
    free(cur->next);

    // Detach that tail node:
    cur->next = NULL;

    return list;
}

I doubt this was the intention of the exercise though.

trincot
  • 317,000
  • 35
  • 244
  • 286