Linked List Basics

来源:互联网 发布:武汉大学网络 编辑:程序博客网 时间:2024/05/18 03:03

Linked List Basics1

mixture:混合

terminology:术语

idiom:典型风格

hallmarks:标志,特征,特点

intensive:加强的

deliberate:故意的

Memory drawings should distinguish heap memory from local stack memory. Reminder: malloc() allocates memory in the heap which is only be deallocated by deliberate calls to free(). In contrast, local stack variables for each function are automatically allocated when the function starts and deallocated when it exits.

  1. Basic Utility Function Implementations

    //Basic Utility Function Implementations//Length()// Return the number of nodes in a list (while-loop version)int Length(struct node* head) {   int count = 0;   struct node* current = head;   while (current != NULL) {       count++;       current=current->next;   }   return(count);}//Push()// Given a reference (pointer to pointer) to the head// of a list and an int, push a new node on the front of the list.// Creates a new node with the int, links the list off the .next of the// new node, and finally changes the head to point to the new node.void Push(struct node** headRef, int newData) {   struct node* newNode =(struct node*) malloc(sizeof(struct node));    // allocate node   newNode->data = newData; // put in the data   newNode->next = (*headRef); // link the old list off the new node   (*headRef) = newNode; // move the head to point to the new node}//BuildOneTwoThree()// Build and return the list {1, 2, 3}struct node* BuildOneTwoThree() {   struct node* head = NULL; // Start with the empty list   Push(&head, 3); // Use Push() to add all the data   Push(&head, 2);   Push(&head, 1);   return(head);}

  2. Count()

    //Write a Count() function that counts the number of times a given int occurs in a list. //The code for this has the classic list traversal structure as demonstrated in Length().int Count(struct node* head, int searchFor) {   struct node* current = head;   int count = 0;   while (current != NULL) {       if (current->data == searchFor)            count++;       current = current->next;   }   return count;}int Count2(struct node* head, int searchFor) {   struct node* current;   int count = 0;   for (current = head; current != NULL; current = current->next) {       if (current->data == searchFor)            count++;   }   return count;}

  3. GetNth()

    //Write a GetNth() function that takes a linked list and an integer index and returns the data value stored in the node at that index position. GetNth() uses the C numbering convention that the first node is index 0, the second is index 1, ... and so on.int GetNth(struct node* head, int index) {   struct node* current = head;   int count = 0; // the index of the node we're currently looking at   while (current != NULL) {       if (count == index)            return(current->data);       count++;       current = current->next;   }   assert(0);    // if we get to this line, the caller was asking   // for a non-existent element so we assert fail.}

  4. DeleteList()

    //Write a function DeleteList() that takes a list, deallocates all of its memory and sets its head pointer to NULL (the empty list).void DeleteList(struct node** headRef) {   struct node* current = *headRef; // deref headRef to get the real head   struct node* next;   while (current != NULL) {       next = current->next; // note the next pointer       free(current); // delete the node       current = next; // advance to the next node   }   *headRef = NULL; // Again, deref headRef to affect the real head back   // in the caller.}

  5. Pop()

    //Write a Pop() function that is the inverse of Push(). Pop() takes a non-empty list, deletes the head node, and returns the head node's data.int Pop(struct node** headRef) {   struct node* head;   int result;   head = *headRef;   assert(head != NULL);   result = head->data; // pull out the data before the node is deleted   *headRef = head->next; // unlink the head node for the caller   // Note the * -- uses a reference-pointer   // just like Push() and DeleteList().   free(head); // free the head node   return(result); // don't forget to return the data from the link}

  6. InsertNth()

    //A more difficult problem is to write a function InsertNth() which can insert a new node at any index within a list.void InsertNth(struct node** headRef, int index, int data) {   // position 0 is a special case...   if (index == 0)        Push(headRef, data);   else {       struct node* current = *headRef;       int i;       for (i=0; i<index-1; i++) {           assert(current != NULL); // if this fails, index was too big           current = current->next;       }       assert(current != NULL); // tricky: you have to check one last time       Push(&(current->next), data); // Tricky use of Push() --       // The pointer being pushed on is not       // in the stack. But actually this works       // fine -- Push() works for any node pointer.   }}

  7. SortedInsert()

    //Write a SortedInsert() function which given a list that is sorted in increasing order, and a single node, inserts the node into the correct sorted position in the list.// Uses special case code for the head endvoid SortedInsert(struct node** headRef, struct node* newNode) {   // Special case for the head end   if (*headRef == NULL || (*headRef)->data >= newNode->data) {       newNode->next = *headRef;       *headRef = newNode;   } else {       // Locate the node before the point of insertion       struct node* current = *headRef;       while (current->next!=NULL && current->next->data<newNode->data) {           current = current->next;       }       newNode->next = current->next;       current->next = newNode;   }}// Dummy node strategy for the head endvoid SortedInsert2(struct node** headRef, struct node* newNode) {   struct node dummy;   struct node* current = &dummy;   dummy.next = *headRef;   while (current->next!=NULL && current->next->data<newNode->data) {       current = current->next;   }   newNode->next = current->next;   current->next = newNode;   *headRef = dummy.next;}// Local references strategy for the head endvoid SortedInsert3(struct node** headRef, struct node* newNode) {   struct node** currentRef = headRef;   while (*currentRef!=NULL && (*currentRef)->data<newNode->data) {       currentRef = &((*currentRef)->next);   }   newNode->next = *currentRef; // Bug: this line used to have   // an incorrect (*currRef)->next   *currentRef = newNode;}

  8. InsertSort()

    //Write an InsertSort() function which given a list, rearranges its nodes so they are sorted in increasing order. It should use SortedInsert().// Given a list, change it to be in sorted order (using SortedInsert()).void InsertSort(struct node** headRef) {   struct node* result = NULL; // build the answer here   struct node* current = *headRef; // iterate over the original list   struct node* next;   while (current!=NULL) {       next = current->next;        // tricky - note the next pointer before we change it       SortedInsert(&result, current);       current = next;   }   *headRef = result;}

  9. Append()

    //Write an Append() function that takes two lists, 'a' and 'b', appends 'b' onto the end of 'a', and then sets 'b' to NULL (since it is now trailing off the end of 'a').void Append(struct node** aRef, struct node** bRef) {   struct node* current;   if (*aRef == NULL) {        // Special case if a is empty       *aRef = *bRef;   } else {        // Otherwise, find the end of a, and append b there       current = *aRef;       while (current->next != NULL) {            // find the last node           current = current->next;       }       current->next = *bRef; // hang the b list off the last node   }   *bRef=NULL; // NULL the original b, since it has been appended above}

  10. FrontBackSplit()

    //Given a list, split it into two sublists — one for the front half, and one for the back half. If the number of elements is odd, the extra element should go in the front list.// Uses the "count the nodes" strategyvoid FrontBackSplit(struct node* source,                   struct node** frontRef, struct node** backRef) {  int len = Length(source);  int i;  struct node* current = source;  if (len < 2) {      *frontRef = source;      *backRef = NULL;  } else {      int hopCount = (len-1)/2; //(figured these with a few drawings)      for (i = 0; i<hopCount; i++) {          current = current->next;      }      // Now cut at current      *frontRef = source;      *backRef = current->next;      current->next = NULL;  }}// Uses the fast/slow pointer strategyvoid FrontBackSplit2(struct node* source,                  struct node** frontRef, struct node** backRef) {  struct node* fast;  struct node* slow;  if (source==NULL || source->next==NULL) {       // length < 2 cases      *frontRef = source;      *backRef = NULL;  } else {      slow = source;      fast = source->next;      // Advance 'fast' two nodes, and advance 'slow' one node      while (fast != NULL) {          fast = fast->next;          if (fast != NULL) {              slow = slow->next;              fast = fast->next;          }      }      // 'slow' is before the midpoint in the list, so split it in two at that point.      *frontRef = source;      *backRef = slow->next;      slow->next = NULL;  }}

  11. RemoveDuplicates()

    //Write a RemoveDuplicates() function which takes a list sorted in increasing order and deletes any duplicate nodes from the list. Ideally, the list should only be traversed once.// Remove duplicates from a sorted listvoid RemoveDuplicates(struct node* head) {    struct node* current = head;    if (current == NULL)         return; // do nothing if the list is empty    // Compare current node with next node    while(current->next!=NULL) {        if (current->data == current->next->data) {            struct node* nextNext = current->next->next;            free(current->next);            current->next = nextNext;        } else {            current = current->next; // only advance if no deletion        }    }}

  12. MoveNode()

    //This is a variant on Push(). Instead of creating a new node and pushing it onto the given list, MoveNode() takes two lists, removes the front node from the second list and pushes it onto the front of the first.void MoveNode(struct node** destRef, struct node** sourceRef) {    struct node* newNode = *sourceRef; // the front source node    assert(newNode != NULL);    *sourceRef = newNode->next; // Advance the source pointer    newNode->next = *destRef; // Link the old dest off the new node    *destRef = newNode; // Move dest to point to the new node}

  13. AlternatingSplit()

    //Write a function AlternatingSplit() that takes one list and divides up its nodes to make two smaller lists.void AlternatingSplit(struct node* source,                    struct node** aRef, struct node** bRef) {    struct node* a = NULL; // Split the nodes to these 'a' and 'b' lists    struct node* b = NULL;    struct node* current = source;    while (current != NULL) {        MoveNode(&a, &current); // Move a node to 'a'        if (current != NULL) {            MoveNode(&b, &current); // Move a node to 'b'        }    }    *aRef = a;    *bRef = b;}void AlternatingSplit2(struct node* source,                        struct node** aRef, struct node** bRef) {    struct node aDummy;    struct node* aTail = &aDummy; // points to the last node in 'a'    struct node bDummy;    struct node* bTail = &bDummy; // points to the last node in 'b'    struct node* current = source;    aDummy.next = NULL;    bDummy.next = NULL;    while (current != NULL) {        MoveNode(&(aTail->next), &current); // add at 'a' tail        aTail = aTail->next; // advance the 'a' tail        if (current != NULL) {            MoveNode(&(bTail->next), &current);            bTail = bTail->next;        }    }    *aRef = aDummy.next;    *bRef = bDummy.next;}

  14. ShuffleMerge()

    //Given two lists, merge their nodes together to make one list, taking nodes alternately between the two lists.//SuffleMerge() — Dummy Node Not Using MoveNode()struct node* ShuffleMerge(struct node* a, struct node* b) {    struct node dummy;    struct node* tail = &dummy;    dummy.next = NULL;    while (1) {        if (a==NULL) { // empty list cases            tail->next = b;            break;        } else if (b==NULL) {                tail->next = a;                break;            } else {                 // common case: move two nodes to tail                tail->next = a;                tail = a;                a = a->next;                tail->next = b;                tail = b;                b = b->next;            }    }    return(dummy.next);}//SuffleMerge() — Dummy Node Using MoveNode()//Basically the same as above, but use MoveNode().struct node* ShuffleMerge(struct node* a, struct node* b) {    struct node dummy;    struct node* tail = &dummy;    dummy.next = NULL;    while (1) {        if (a==NULL) {            tail->next = b;            break;        } else if (b==NULL) {                tail->next = a;                break;            } else {                MoveNode(&(tail->next), &a);                tail = tail->next;                MoveNode(&(tail->next), &b);                tail = tail->next;            }    }    return(dummy.next);}//SuffleMerge() — Local References//Uses a local reference to get rid of the dummy nodes entirely.struct node* ShuffleMerge(struct node* a, struct node* b) {    struct node* result = NULL;    struct node** lastPtrRef = &result;    while (1) {        if (a==NULL) {            *lastPtrRef = b;            break;        } else if (b==NULL) {            *lastPtrRef = a;            break;        } else {            MoveNode(lastPtrRef, &a);            lastPtrRef = &((*lastPtrRef)->next);            MoveNode(lastPtrRef, &b);            lastPtrRef = &((*lastPtrRef)->next);        }    }    return(result);}//SuffleMerge() — Recursive//The recursive solution is the most compact of all, but is probably not appropriate for production code since it uses stack space proportionate to the lengths of the lists.struct node* ShuffleMerge(struct node* a, struct node* b) {    struct node* result;    struct node* recur;    if (a==NULL)         return(b); // see if either list is empty    else         if (b==NULL)             return(a);        else {            // it turns out to be convenient to do the recursive call first --            // otherwise a->next and b->next need temporary storage.            recur = ShuffleMerge(a->next, b->next);            result = a; // one node from a            a->next = b; // one from b            b->next = recur; // then the rest            return(result);        }}

  15. SortedMerge()

    //Write a SortedMerge() function that takes two lists, each of which is sorted in increasing order, and merges the two together into one list which is in increasing order.struct node* SortedMerge(struct node* a, struct node* b) {    struct node dummy; // a dummy first node to hang the result on    struct node* tail = &dummy; // Points to the last result node --    // so tail->next is the place to add    // new nodes to the result.    dummy.next = NULL;    while (1) {        if (a == NULL) { // if either list runs out, use the other list            tail->next = b;            break;        } else if (b == NULL) {            tail->next = a;            break;        }        if (a->data <= b->data) {            MoveNode(&(tail->next), &a);        } else {            MoveNode(&(tail->next), &b);        }        tail = tail->next;    }    return(dummy.next);}//SortedMerge() Using Local Referencesstruct node* SortedMerge2(struct node* a, struct node* b) {    struct node* result = NULL;    struct node** lastPtrRef = &result; // point to the last result pointer    while (1) {        if (a==NULL) {            *lastPtrRef = b;            break;        } else if (b==NULL) {            *lastPtrRef = a;            break;        }        if (a->data <= b->data) {            MoveNode(lastPtrRef, &a);        } else {            MoveNode(lastPtrRef, &b);        }        lastPtrRef = &((*lastPtrRef)->next); // tricky: advance to point to        // the next ".next" field    }    return(result);}//SortedMerge() Using Recursionstruct node* SortedMerge3(struct node* a, struct node* b) {    struct node* result = NULL;    // Base cases    if (a==NULL)         return(b);    else if (b==NULL)             return(a);    // Pick either a or b, and recur    if (a->data <= b->data) {        result = a;        result->next = SortedMerge3(a->next, b);    } else {        result = b;        result->next = SortedMerge3(a, b->next);    }    return(result);}

  16. MergeSort()

    //(This problem requires recursion) Given FrontBackSplit() and SortedMerge(), it's pretty easy to write a classic recursive MergeSort(): split the list into two smaller lists, recursively sort those lists, and finally merge the two sorted lists together into a single sorted list.void MergeSort(struct node** headRef) {    struct node* head = *headRef;    struct node* a;    struct node* b;    // Base case -- length 0 or 1    if ((head == NULL) || (head->next == NULL)) {        return;    }    FrontBackSplit(head, &a, &b); // Split head into 'a' and 'b' sublists    // We could just as well use AlternatingSplit()    MergeSort(&a); // Recursively sort the sublists    MergeSort(&b);    *headRef = SortedMerge(a, b); // answer = merge the two sorted lists together}

  17. SortedIntersect()

    //Given two lists sorted in increasing order, create and return a new list representing the intersection of the two lists.// This solution uses the temporary dummy to build up the result liststruct node* SortedIntersect(struct node* a, struct node* b) {    struct node dummy;    struct node* tail = &dummy;    dummy.next = NULL;    // Once one or the other list runs out -- we're done    while (a!=NULL && b!=NULL) {        if (a->data == b->data) {            Push((&tail->next), a->data);            tail = tail->next;            a = a->next;            b = b->next;        } else if (a->data < b->data) { // advance the smaller list            a = a->next;        } else {            b = b->next;        }    }    return(dummy.next);}// This solution uses the local referencestruct node* SortedIntersect2(struct node* a, struct node* b) {    struct node* result = NULL;    struct node** lastPtrRef = &result;    // Advance comparing the first nodes in both lists.    // When one or the other list runs out, we're done.    while (a!=NULL && b!=NULL) {        if (a->data == b->data) { // found a node for the intersection            Push(lastPtrRef, a->data);            lastPtrRef = &((*lastPtrRef)->next);            a=a->next;            b=b->next;        } else if (a->data < b->data) { // advance the smaller list            a=a->next;        } else {            b=b->next;        }    }    return(result);}

  18. Reverse()

    //Write an iterative Reverse() function that reverses a list by rearranging all the .next pointers and the head pointer./*Iterative list reverse.Iterate through the list left-right.Move/insert each node to the front of the result list --like a Push of the node.*/static void Reverse(struct node** headRef) {    struct node* result = NULL;    struct node* current = *headRef;    struct node* next;    while (current != NULL) {        next = current->next; // tricky: note the next node        current->next = result; // move the node onto the result        result = current;        current = next;    }    *headRef = result;}//Here's the variation on the above that uses MoveNode() to do the work...static void Reverse2(struct node** headRef) {    struct node* result = NULL;    struct node* current = *headRef;    while (current != NULL) {        MoveNode(&result, &current);    }    *headRef = result;}//Finally, here's the back-middle-front strategy...// Reverses the given linked list by changing its .next pointers and// its head pointer. Takes a pointer (reference) to the head pointer.void Reverse(struct node** headRef) {    if (*headRef != NULL) { // special case: skip the empty list        /*        Plan for this loop: move three pointers: front, middle, back        down the list in order. Middle is the main pointer running        down the list. Front leads it and Back trails it.        For each step, reverse the middle pointer and then advance all        three to get the next node.        */        struct node* middle = *headRef; // the main pointer        struct node* front = middle->next; // the two other pointers (NULL ok)        struct node* back = NULL;        while (1) {            middle->next = back; // fix the middle node            if (front == NULL)                 break; // test if done            back = middle; // advance the three pointers            middle = front;            front = front->next;        }        *headRef = middle; // fix the head pointer to point to the new front    }}

  19. RecursiveReverse()

    //RecursiveReverse()void RecursiveReverse(struct node** headRef) {    struct node* first;    struct node* rest;    if (*headRef == NULL)         return; // empty list base case    first = *headRef; // suppose first = {1, 2, 3}    rest = first->next; // rest = {2, 3}    if (rest == NULL)         return; // empty rest base case    RecursiveReverse(&rest); // Recursively reverse the smaller {2, 3} case    // after: rest = {3, 2}    first->next->next = first; // put the first elem on the end of the list    first->next = NULL; // (tricky step -- make a drawing)    *headRef = rest; // fix the head pointer}

  1. http://cslibrary.stanford.edu/
    [^2]: http://cslibrary.stanford.edu/103/ ↩