Linked List Problems
|
|
|
- Christina Greer
- 10 years ago
- Views:
Transcription
1 Linked List Problems By Nick Parlante Copyright , Nick Parlante Abstract This document presents 18 linked list problems covering a wide range of difficulty. Most obviously, these problems are useful to learn and practice linked list skills. More importantly, these problems are an excellent area to develop your ability with complex pointer algorithms. Even though modern languages and tools have made linked lists pretty unimportant for day-to-day programming, the skills for complex pointer algorithms are always in style, and linked lists are the classic area to develop those skills. The problems use the C language syntax, so they require a basic understanding of C and its pointer syntax. The emphasis is on the important concepts of pointer manipulation and linked list algorithms rather than the features of the C language. The intermediate and advanced problems require the use of pointers to pointers so called "reference pointers". See the references below for help with those background topics. For a few problems multiple solutions are presented, such as iteration vs. recursion, dummy node vs. local reference. The specific problems are, in rough order of difficulty: Count, GetNth, DeleteList, Pop, InsertNth, SortedInsert, InsertSort, Append, FrontBackSplit, RemoveDuplicates, MoveNode, AlternatingSplit, ShuffleMerge, SortedMerge, SortedIntersect, Reverse, and RecursiveReverse. Contents Section 1 Review of basic linked list code techniques 3 Section 2 18 list problems in increasing order of difficulty 9 Section 3 Solutions to all the problems 19 This is document #105, Linked List Problems, in the CS Education Library at Stanford. This and other free educational materials are available at This document is free to be used, reproduced, or retransmitted so long as this notice is clearly reproduced at its beginning. Related CS Education Library Documents Here are some helpful review materials for the linked list problems... Linked List Basics ( Explains all the basic issues and techniques for building linked lists. Pointers and Memory ( Explains all about how pointers and memory work in C and other languages. Starts with the very basics, and extends through advanced topics such as reference parameters and heap management. Essential C ( Explains all the basic features of the C programming language.
2 2 Why Linked Lists Are Great To Study Linked lists hold a special place in the hearts of many programmers. Linked lists are great to study because... Nice Domain The linked list structure itself is simple. Many linked list operations such as "reverse a list" or "delete a list" are easy to describe and understand since they build on the simple purpose and structure of the linked list itself. Complex Algorithm Even though linked lists are simple, the algorithms that operate on them can be as complex and beautiful as you want (See problem #18). It's easy to find linked list algorithms that are meaningful, complex, and pointer intensive. Linked list algorithms are not complex in a good way or a bad way, but in a realistic way for pointer code. Pointer Intensive Linked list problems are really about pointers. The linked list structure itself is obviously pointer intensive. Furthermore, linked list algorithms often break and re-weave the pointers in a linked list as they go. Linked lists give you a deep understanding of pointers. Visualization Visualization is an important skill in programming and design. Ideally, a programmer can visualize the state of memory to help think through the solution. Even the most abstract languages such as Java and Perl have layered, reference based data structures that benefit from visualization. Linked lists have a natural visual structure for practicing this sort of thinking. It's easy to draw the state of a linked list and use that drawing to think through the code. Not to appeal to your mercenary side, but for all of the above reasons, linked list problems are often used as interview and exam questions. They are short to state, and have complex, pointer intensive solutions. No one really cares if you can build linked lists. But they do want to see if you have programming agility for complex algorithms and pointer manipulation, and linked lists are a great area of such problems. What About a Simplifying Variations? The problems in this document use the "plain" linked list, even though the code to manage the plain linked list is not the simplest. There are simplifying variations on the plain linked list that avoid some complex code using dummy nodes, using a functional style to avoid reference pointers. For many programs the simplifying approach is a fine idea, but that is not what this document is about. The goal of this document is to seek out complex pointer problems and use them for practice. For that reason, the lists are used in their plain form, and the code will have all its complexity. How To Use This Document Try not to use these problems passively. You should take the time to try to solve each problem. Even if you do not succeed, you will think through the right issues in the attempt, and looking at the given solution will make more sense. Use drawings to think about the problems and work through the solutions. Linked lists are well-suited for memory drawings, so these problems are an excellent opportunity to develop your visualization skill. Edition This is the first major release of this document Jan 17, The author may be reached at [email protected]. The CS Education Library may be reached at [email protected].
3 3 Dedication This document is distributed for the benefit and education of all. That someone seeking education should have the opportunity to find it. May you learn from it in the spirit in which it is given to make efficiency and beauty in your designs, peace and fairness in your actions. Section 1 Linked List Review This section is a quick review of the concepts used in these linked list problems. For more detailed coverage, see Link List Basics ( where all of this material is explained in much more detail. Linked List Ground Rules All of the linked list code in this document uses the "classic" singly linked list structure: A single head pointer points to the first node in the list. Each node contains a single.next pointer to the next node. The.next pointer of the last node is NULL. The empty list is represented by a NULL head pointer. All of the nodes are allocated in the heap. For a few of the problems, the solutions present the temporary "dummy node" variation (see below), but most of the code deals with linked lists in their plain form. In the text, brackets { are used to describe lists the list containing the numbers 1, 2, and 3 is written as {1, 2, 3. The node type used is... struct node { int data; struct node* next; ; For clarity, no typedefs are used. All pointers to nodes are declared simply as struct node*. Pointers to pointers to nodes are declared as struct node**. In the text, such pointers to pointers are often called "reference pointers". Basic Utility Functions In a few places, the text assumes the existence of the following basic utility functions... int Length(struct node* head); Returns the number of nodes in the list. struct node* BuildOneTwoThree(); Allocates and returns the list {1, 2, 3. Used by some of the example code to build lists to work on. void Push(struct node** headref, int newdata); Given an int and a reference to the head pointer (i.e. a struct node** pointer to the head pointer), add a new node at the head of the list with the standard 3-step-link-in: create the new node, set its.next to point to the current head, and finally change the head to point to the new node. (If you are not sure of how this function works, the first few problems may be helpful warm-ups.)
4 4 Use of the Basic Utility Functions This sample code demonstrates the basic utility functions being used. Their implementations are also given in the appendix at the end of the document. void BasicsCaller() { struct node* head; int len; head = BuildOneTwoThree(); // Start with {1, 2, 3 Push(&head, 13); // Push 13 on the front, yielding {13, 1, 2, 3 // (The '&' is because head is passed // as a reference pointer.) Push(&(head->next), 42); // Push 42 into the second position // yielding {13, 42, 1, 2, 3 // Demonstrates a use of '&' on // the.next field of a node. // (See technique #2 below.) len = Length(head); // Computes that the length is 5. If these basic functions do not make sense to you, you can (a) go see Linked List Basics ( which explains the basics of linked lists in detail, or (b) do the first few problems, but avoid the intermediate and advanced ones. Linked List Code Techniques The following list presents the most common techniques you may want to use in solving the linked list problems. The first few are basic. The last few are only necessary for the more advanced problems. 1) Iterate Down a List A very frequent technique in linked list code is to iterate a pointer over all the nodes in a list. Traditionally, this is written as a while loop. The head pointer is copied into a local variable current which then iterates down the list. Test for the end of the list with current!=null. Advance the pointer with current=current->next. // 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); Alternately, some people prefer to write the loop as a for which makes the initialization, test, and pointer advance more centralized, and so harder to omit... for (current = head; current!= NULL; current = current->next) {
5 5 2) Changing a Pointer With A Reference Pointer Many list functions need to change the caller's head pointer. To do this in the C language, pass a pointer to the head pointer. Such a pointer to a pointer is sometimes called a "reference pointer". The main steps for this technique are... Design the function to take a pointer to the head pointer. This is the standard technique in C pass a pointer to the "value of interest" that needs to be changed. To change a struct node*, pass a struct node**. Use '&' in the caller to compute and pass a pointer to the value of interest. Use '*' on the parameter in the callee function to access and change the value of interest. The following simple function sets a head pointer to NULL by using a reference parameter... // Change the passed in head pointer to be NULL // Uses a reference pointer to access the caller's memory void ChangeToNull(struct node** headref) { // Takes a pointer to // the value of interest *headref = NULL; // use '*' to access the value of interest void ChangeCaller() { struct node* head1; struct node* head2; ChangeToNull(&head1); // use '&' to compute and pass a pointer to ChangeToNull(&head2); // the value of interest // head1 and head2 are NULL at this point Here is a drawing showing how the headref pointer in ChangeToNull() points back to the variable in the caller... Stack ChangeCaller() head1 ChangToNull() headref Many of the functions in this document use reference pointer parameters. See the use of Push() above and its implementation in the appendix for another example of reference
6 6 pointers. See problem #8 and its solution for a complete example with drawings. For more detailed explanations, see the resources listed on page 1. 3) Build At Head With Push() The easiest way to build up a list is by adding nodes at its "head end" with Push(). The code is short and it runs fast lists naturally support operations at their head end. The disadvantage is that the elements will appear in the list in the reverse order that they are added. If you don't care about order, then the head end is the best. struct node* AddAtHead() { struct node* head = NULL; int i; for (i=1; i<6; i++) { Push(&head, i); // head == {5, 4, 3, 2, 1; return(head); 4) Build With Tail Pointer What about adding nodes at the "tail end" of the list? Adding a node at the tail of a list most often involves locating the last node in the list, and then changing its.next field from NULL to point to the new node, such as the tail variable in the following example of adding a "3" node to the end of the list {1, 2... Stack Heap head 1 2 tail newnode 3 This is just a special case of the general rule: to insert or delete a node inside a list, you need a pointer to the node just before that position, so you can change its.next field. Many list problems include the sub-problem of advancing a pointer to the node before the point of insertion or deletion. The one exception is if the operation falls on the first node in the list in that case the head pointer itself must be changed. The following examples show the various ways code can handle the single head case and all the interior cases... 5) Build Special Case + Tail Pointer Consider the problem of building up the list {1, 2, 3, 4, 5 by appending the nodes to the tail end. The difficulty is that the very first node must be added at the head pointer, but all the other nodes are inserted after the last node using a tail pointer. The simplest way to deal with both cases is to just have two separate cases in the code. Special case code first adds the head node {1. Then there is a separate loop that uses a tail pointer to add all the
7 7 other nodes. The tail pointer is kept pointing at the last node, and each new node is added at tail->next. The only "problem" with this solution is that writing separate special case code for the first node is a little unsatisfying. Nonetheless, this approach is a solid one for production code it is simple and runs fast. struct node* BuildWithSpecialCase() { struct node* head = NULL; struct node* tail; int i; // Deal with the head node here, and set the tail pointer Push(&head, 1); tail = head; // Do all the other nodes using 'tail' for (i=2; i<6; i++) { Push(&(tail->next), i); // add node at tail->next tail = tail->next; // advance tail to point to last node return(head); // head == {1, 2, 3, 4, 5; 6) Build Dummy Node Another solution is to use a temporary dummy node at the head of the list during the computation. The trick is that with the dummy, every node appear to be added after the.next field of a node. That way the code for the first node is the same as for the other nodes. The tail pointer plays the same role as in the previous example. The difference is that it now also handles the first node. struct node* BuildWithDummyNode() { struct node dummy; // Dummy node is temporarily the first node struct node* tail = &dummy; // Start the tail at the dummy. // Build the list on dummy.next (aka tail->next) int i; for (i=1; i<6; i++) { Push(&(tail->next), i); tail = tail->next; // The real result list is now in dummy.next // dummy.next == {1, 2, 3, 4, 5; return(dummy.next); Some linked list implementations keep the dummy node as a permanent part of the list. For this "permanent dummy" strategy, the empty list is not represented by a NULL pointer. Instead, every list has a dummy node at its head. Algorithms skip over the dummy node for all operations. That way the dummy node is always present to provide the above sort of convenience in the code. Some of the solutions presented in this document will use the temporary dummy strategy. The code for the permanent dummy strategy is extremely similar, but is not shown.
8 8 7) Build Local References Finally, here is a tricky way to unifying all the node cases without using a dummy node. The trick is to use a local "reference pointer" which always points to the last pointer in the list instead of to the last node. All additions to the list are made by following the reference pointer. The reference pointer starts off pointing to the head pointer. Later, it points to the.next field inside the last node in the list. (A detailed explanation follows.) struct node* BuildWithLocalRef() { struct node* head = NULL; struct node** lastptrref= &head; // Start out pointing to the head pointer int i; for (i=1; i<6; i++) { Push(lastPtrRef, i); // Add node at the last pointer in the list lastptrref= &((*lastptrref)->next); // Advance to point to the // new last pointer // head == {1, 2, 3, 4, 5; return(head); This technique is short, but the inside of the loop is scary. This technique is rarely used. (Actually, I'm the only person I've known to promote it. I think it has a sort of compact charm.) Here's how it works... 1) At the top of the loop, lastptrref points to the last pointer in the list. Initially it points to the head pointer itself. Later it points to the.next field inside the last node in the list. 2) Push(lastPtrRef, i); adds a new node at the last pointer. The new node becomes the last node in the list. 3) lastptrref= &((*lastptrref)->next); Advance the lastptrref to now point to the.next field inside the new last node that.next field is now the last pointer in the list. Here is a drawing showing the state of memory for the above code just before the third node is added. The previous values of lastptrref are shown in gray... Stack LocalRef() head Heap 1 2 lastptrref This technique is never required to solve a linked list problem, but it will be one of the alternative solutions presented for some of the advanced problems.
9 9 Section 2 Linked List Problems Here are 18 linked list problems arranged by order of difficulty. The first few are quite basic and the last few are quite advanced. Each problem starts with a basic definition of what needs to be accomplished. Many of the problems also include hints or drawings to get you started. The solutions to all the problems are in the next section. It's easy to just passively sweep your eyes over the solution verifying its existence without lettings its details touch your brain. To get the most benefit from these problems, you need to make an effort to think them through. Whether or not you solve the problem, you will be thinking through the right issues, and the given solution will make more sense. Great programmers can visualize data structures to see ahead to the solution, and linked lists are well suited to that sort of visual thinking. Use these problems to develop your visualization. skill. Make memory drawings to trace through the execution of code. Use drawings of the pre- and post-conditions of a problem to start thinking about a solution. "The will to win means nothing without the will to prepare." - Juma Ikangaa, marathoner (also attributed to Bobby Knight) 1 Count() Write a Count() function that counts the number of times an int occurs in a list. The code for this has the classic list traversal structure as demonstrated in Length(). void CountTest() { List mylist = BuildOneTwoThree(); // build {1, 2, 3 int count = Count(myList, 2); // returns 1 since there's 1 '2' in the list /* Given a list and an int, return the number of times that int occurs in the list. */ int Count(struct node* head, int searchfor) { // Your code 2 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. So for the list {42, 13, 666 GetNth() with index 1 should return 13. The index should be in the range [0..length- 1]. If it is not, GetNth() should assert() fail (or you could implement some other error case strategy).
10 10 void GetNthTest() { List mylist = BuildOneTwoThree(); // build {1, 2, 3 int lastnode = GetNth(myList, Length(myList)-1); // returns the value 3 Essentially, GetNth() tries to make the list appear like an array the client can ask for elements by index number. However, GetNth() is much slower than [ ] for an array. The advantage of the linked list is its much more flexible memory management we can Push() at any time to add more elements and the memory is allocated as needed. // Given a list and an index, return the data // in the nth node of the list. The nodes are numbered from 0. // Assert fails if the index is invalid (outside 0..lengh-1). int GetNth(struct node* head, int index) { // Your code 3 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 DeleteListTest() { List mylist = BuildOneTwoThree(); // build {1, 2, 3 DeleteList(&myList); // deletes the three nodes and sets mylist to NULL Post DeleteList() Memory Drawing Here's the drawing showing the state of memory after DeleteList() executes in the above sample. Overwritten pointers are shown in gray and deallocated heap memory has an 'X' through it. Essentially DeleteList() just needs to call free() once for each node and set the head pointer to NULL. Stack Heap DeleteListTest() mylist mylist is overwritten with the value NULL The three heap blocks are deallocated by calls to free(). Their memory will appear to be intact for a while, but the memory should not be accessed.
11 11 DeleteList() The DeleteList() implementation will need to use a reference parameter just like Push() so that it can change the caller's memory (mylist in the above sample). The implementation also needs to be careful not to access the.next field in each node after the node has been deallocated. void DeleteList(struct node** headref) { // Your code 4 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. If all you ever used were Push() and Pop(), then our linked list would really look like a stack. However, we provide more general functions like GetNth() which what make our linked list more than just a stack. Pop() should assert() fail if there is not a node to pop. Here's some sample code which calls Pop()... void PopTest() { struct node* head = BuildOneTwoThree(); // build {1, 2, 3 int a = Pop(&head); // deletes "1" node and returns 1 int b = Pop(&head); // deletes "2" node and returns 2 int c = Pop(&head); // deletes "3" node and returns 3 int len = Length(head); // the list is now empty, so len == 0 Pop() Unlink Pop() is a bit tricky. Pop() needs to unlink the front node from the list and deallocate it with a call to free(). Pop() needs to use a reference parameter like Push() so that it can change the caller's head pointer. A good first step to writing Pop() properly is making the memory drawing for what Pop() should do. Below is a drawing showing a Pop() of the first node of a list. The process is basically the reverse of the 3-Step-Link-In used by Push() (would that be "Ni Knil Pets-3"?). The overwritten pointer value is shown in gray, and the deallocated heap memory has a big 'X' drawn on it... Stack Heap PopTest() head The head pointer advances to refer to the node after the unlinked one The unlinked node is deallocated by a call to free(). Ironically, the unlinked node itself is not changed immediately. It is no longer appears in the list just because the head pointer no longer points to it.
12 12 Pop() /* The opposite of Push(). Takes a non-empty list and removes the front node, and returns the data which was in that node. */ int Pop(struct node** headref) { // your code... 5 InsertNth() A more difficult problem is to write a function InsertNth() which can insert a new node at any index within a list. Push() is similar, but can only insert a node at the head end of the list (index 0). The caller may specify any index in the range [0..length], and the new node should be inserted so as to be at that index. void InsertNthTest() { struct node* head = NULL; // start with the empty list InsertNth(&head, 0, 13); // build {13) InsertNth(&head, 1, 42); // build {13, 42 InsertNth(&head, 1, 5); // build {13, 5, 42 DeleteList(&head); // clean up after ourselves InsertNth() is complex you will want to make some drawings to think about your solution and afterwards, to check its correctness. /* A more general version of Push(). Given a list, an index 'n' in the range 0..length, and a data element, add a new node to the list so that it has the given index. */ void InsertNth(struct node** headref, int index, int data) { // your code... 6 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. While Push() allocates a new node to add to the list, SortedInsert() takes an existing node, and just rearranges pointers to insert it into the list. There are many possible solutions to this problem. void SortedInsert(struct node** headref, struct node* newnode) { // Your code... 7 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) { // Your code
13 13 8 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'). Here is a drawing of a sample call to Append(a, b) with the start state in gray and the end state in black. At the end of the call, the 'a' list is {1, 2, 3, 4, and 'b' list is empty. Stack Heap a 1 2 b 3 4 It turns out that both of the head pointers passed to Append(a, b) need to be reference parameters since they both may need to be changed. The second 'b' parameter is always set to NULL. When is 'a' changed? That case occurs when the 'a' list starts out empty. In that case, the 'a' head must be changed from NULL to point to the 'b' list. Before the call 'b' is {3, 4. After the call, 'a' is {3, 4. Stack Heap a b 3 4
14 14 // Append 'b' onto the end of 'a', and then set 'b' to NULL. void Append(struct node** aref, struct node** bref) { // Your code... 9 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. So FrontBackSplit() on the list {2, 3, 5, 7, 11 should yield the two lists {2, 3, 5 and {7, 11. Getting this right for all the cases is harder than it looks. You should check your solution against a few cases (length = 2, length = 3, length=4) to make sure that the list gets split correctly near the short-list boundary conditions. If it works right for length=4, it probably works right for length=1000. You will probably need special case code to deal with the (length <2) cases. Hint. Probably the simplest strategy is to compute the length of the list, then use a for loop to hop over the right number of nodes to find the last node in the front half, and then cut the list at that point. A trickier way to do it is to have two pointers which traverse the list. One pointer advances two nodes at a time, while the other goes one node at a time. When the leading pointer reaches the end, the trailing pointer will be about half way. For either strategy, care is required to split the list at the right point. /* Split the nodes of the given list into front and back halves, and return the two lists using the reference parameters. If the length is odd, the extra node should go in the front list. */ void FrontBackSplit(struct node* source, struct node** frontref, struct node** backref) { // Your code 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 list. */ void RemoveDuplicates(struct node* head) { // Your code 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. This turns out to be a handy utility function to have for several later problems. Both Push() and MoveNode() are designed around the feature that list operations work most naturally at the head of the list. Here's a simple example of what MoveNode() should do...
15 15 void MoveNodeTest() { struct node* a = BuildOneTwoThree(); // the list {1, 2, 3 struct node* b = BuildOneTwoThree(); MoveNode(&a, &b); // a == {1, 1, 2, 3 // b == {2, 3 /* Take the node from the front of the source, and move it to the front of the dest. It is an error to call this with the source list empty. */ void MoveNode(struct node** destref, struct node** sourceref) { // Your code 12 AlternatingSplit() Write a function AlternatingSplit() which takes one list and divides up its nodes to make two smaller lists. The sublists should be made from alternating elements in the original list. So if the original list is {a, b, a, b, a, then one sublist should be {a, a, a and the other should be {b, b. You may want to use MoveNode() as a helper. The elements in the new lists may be in any order (for some implementations, it turns out to be convenient if they are in the reverse order from the original list.) /* Give the source list, split its nodes into two shorter lists. aref should be set to point to the list of odd position elements, and bref should be set to point to the list of even position elements. The elements in the new lists may be in any order. */ void Split(struct node* source, struct node** aref, struct node** bref) { // Your code 13 ShuffleMerge() Given two lists, merge their nodes together to make one list, taking nodes alternately between the two lists. So ShuffleMerge() with {1, 2, 3 and {7, 13, 1 should yield {1, 7, 2, 13, 3, 1. If either list runs out, the all the nodes should be taken from the other list. The solution depends on being able to move nodes to the end of a list as discussed in the Section 1 review. You may want to use MoveNode() as a helper. Overall, many techniques are possible: dummy node, local reference, or recursion. Using this function and FrontBackSplit(), you could simulate the shuffling of cards. /* Merge the nodes of the two lists into a single list taking a nodes alternately from each list, and return the new list. */ struct node* ShuffleMerge(struct node* a, struct node* b) { // Your code 14 SortedMerge() Write a SortedMerge() function which takes two lists, each of which is sorted in increasing order, and merges the two together into one list which is increasing order.
16 16 SortedMerge() should return the new list. The new list should be made by splicing together the nodes of the first two lists (use MoveNode()). Ideally, Merge() should only make one pass through each list. Merge() is tricky to get right it may be solved iteratively or recursively. There are many cases to deal with: either 'a' or 'b' may be empty, during processing either 'a' or 'b' may run out first, and finally there's the problem of starting the result list empty, and building it up while going through 'a' and 'b'. /* Takes two lists sorted in increasing order, and splices their nodes together to make one big sorted list which is returned. */ struct node* SortedMerge(struct node* a, struct node* b) { // your code 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. Ironically, this problem is easier than either FrontBackSplit() or SortedMerge(). void MergeSort(struct node* headref) { // Your code SortedIntersect() Given two lists sorted in increasing order, create and return a new list representing the intersection of the two lists. The new list should be made with its own memory the original lists should not be changed. In other words, this should be Push() list building, not MoveNode(). Ideally, each list should only be traversed once. This problem along with Union() and Difference() form a family of clever algorithms that exploit the constraint that the lists are sorted to find common nodes efficiently. /* Compute a new sorted list that represents the intersection of the two given sorted lists. */ struct node* SortedIntersect(struct node* a, struct node* b) { // Your code 17 Reverse() Write an iterative Reverse() function which reverses a list by rearranging all the.next pointers and the head pointer. Ideally, Reverse() should only need to make one pass of the list. This is a difficult problem. It requires a good understanding of pointer operations as well as the ability to think through a complex code design. Any programmer can benefit from working through the code even if they need look at the solution first. The problem is a good example of the sort of code and memory issues which complex algorithms sometimes require. Many great programmers have struggled with Reverse() it's a classic. (A memory drawing for Reverse() an some hints are below.)
17 17 void ReverseTest() { struct node* head; head = BuildOneTwoThree(); Reverse(&head); // head now points to the list {3, 2, 1 DeleteList(&head); // clean up after ourselves Stack ReverseTest() head Heap List reverse before and after. Before (in gray) the list is {1, 2, 3. After (in black), the pointers have been rearranged so the list is {3, 2, Reverse Hint As a hint, here's one strategy: Instead of running a single "current" pointer down the list, run three pointers (front, middle, back) down the list in order: front is on one node, middle is just behind it, and back in turn is one behind middle. Once the code to run the three pointers down the list is clear and tested in a drawing, add code to reverse the.next pointer of the middle node during the iteration. Add code to take care of the empty list and to adjust the head pointer itself. /* Reverse 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) { // your code...
18 18 18 RecursiveReverse() (This problem is difficult and is only possible if you are familiar with recursion.) The solution is short and amazing. As before, the code should only make a single pass over the list. Solving it, or even appreciating how it works requires real understanding of pointer code and recursion. (Although the code for RecursiveReverse() is quite nice, there is another complex linked list recursion algorithm which is the most beautfiul of all the O(n) conversion of an ordered binary tree to a sorted doubly linked circular list. That algorithm may get its own CS Education Library document someday.) /* Recursively reverses the given linked list by changing its.next pointers and its head pointer. Takes a pointer (reference) to the head pointer. */ void RecursiveReverse(struct node** headref) { // your code...
19 19 Section 3 Solutions 1 Count() Solution A straightforward iteration just like 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; Alternately, the iteration may be coded with a for loop instead of a while... 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; 2 GetNth() Solution Combine standard list iteration with the additional problem of counting over to find the right node. Off-by-one errors are common in this sort of code. Check it carefully against a simple case. If it's right for n=0, n=1, and n=2, it will probably be right for n=1000. 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.
20 20 3 DeleteList() Solution Delete the whole list and set the head pointer to NULL. Slight complication in the inside of the loop, since we need extract the.next pointer before we delete the node, since after the delete it will be technically unavailable. 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; free(current); current = next; // note the next pointer // delete the node // advance to the next node *headref = NULL; // Again, deref headref to affect the real head back // in the caller. 4 Pop() Solution Extract the data from the head node, delete the node, advance the head pointer to point at the next node in line. Uses a reference parameter since it changes the head pointer. 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); return(result); // free the head node // don't forget to return the data from the link 5 InsertNth() Solution This code handles inserting at the very front as a special case. Otherwise, it works by running a current pointer to the node before where the new node should go. Uses a for loop to march the pointer forward. The exact bounds of the loop (the use of < vs <=, n vs. n-1) are always tricky the best approach is to get he general structure of the iteration correct first, and then make a careful drawing of a couple test cases to adjust the n vs. n-1 cases to be correct. (The so called "OBOB" Off By One Boundary cases.) The OBOB cases are always tricky and not that interesting. Write the correct basic structure and then use a test case to get the OBOB cases correct. Once the insertion point has been determined, this solution uses Push() to do the link in. Alternately, the 3-Step Link In code could be pasted here directly.
21 21 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); current = current->next; // if this fails, index was too big 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. 6 SortedInsert() Solution The basic strategy is to iterate down the list looking for the place to insert the new node. That could be the end of the list, or a point just before a node which is larger than the new node. The three solutions presented handle the "head end" case in different ways... // Uses special case code for the head end void 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 end void 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;
22 22 // Local references strategy for the head end void SortedInsert3(struct node** headref, struct node* newnode) { struct node** currentref = headref; while (*currentref!=null && (*currentref)->data<newnode->data) { currentref = &((*currentref)->next); newnode->next = (*currentref)->next; *currentref = newnode; 7 InsertSort() Solution Start with an empty result list. Iterate through the source list and SortedInsert() each of its nodes into the result list. Be careful to note the.next field in each node before moving it into the result list. // Given a list, change it to be in sorted order (using InsertSorted()). 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; 8 Append() Solution The case where the 'a' list is empty is a special case handled first in that case the 'a' head pointer needs to be changed directly. Otherwise we iterate down the 'a' list until we find its last node with the test (current->next!= NULL), and then tack on the 'b' list there. Finally, the original 'b' head is set to NULL. This code demonstrates extensive use of pointer reference parameters, and the common problem of needing to locate the last node in a list. (There is also a drawing of how Append() uses memory below.) 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
23 23 *bref=null; // NULL the original b, since it has been appended above Append() Test and Drawing The following AppendTest() code calls Append() to join two lists. What does memory look like just before the call to Append() exits? void AppendTest() { struct node* a; struct node* b; // set a to {1, 2 // set b to {3, 4 Append(&a, &b); As an example of how reference parameters work, note how reference parameters in Append() point back to the head pointers in AppendTest()... Stack AppendTest() a Heap 1 2 b Append(&a, &b) aref 3 4 bref current 9 FrontBachSplit() Solution Two solutions are presented... // Uses the "count the nodes" strategy void 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;
24 24 *backref = NULL; else { int hopcount = ((len+1)/2)-1; //(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 strategy void FrontBackSplit2(struct node* source, struct node** frontref, struct node** backref) { struct node* fast; struct node* slow; if (source==null source->next==null) { *frontref = source; *backref = NULL; else { slow = source; fast = source->next; // length < 2 cases // 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; 10 RemoveDuplicates() Solution Since the list is sorted, we can proceed down the list and compare adjacent nodes. When adjacent nodes are the same, remove the second one. There's a tricky case where the node after the next node needs to be noted before the deletion. // Remove duplicates from a sorted list void RemoveDuplicates(struct node* head) { struct node* current = head; if (current == NULL) return; // do nothing if the list is empty
25 25 // 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 11 MoveNode() Solution The MoveNode() code is most similar to the code for Push(). It's short just changing a couple pointers but it's complex. Make a drawing. void MoveNode(struct node** destref, struct node** sourceref) { struct node* newnode = *sourceref; // the front source node assert(newnode!= NULL); *sourceref = newnode->next; newnode->next = *destref; *destref = newnode; // Advance the source pointer // Link the old dest off the new node // Move dest to point to the new node 12 AlternatingSplit() Solution The simplest approach iterates over the source list and use MoveNode() to pull nodes off the source and alternately put them on 'a' and b'. The only strange part is that the nodes will be in the reverse order that they occurred in the source list. AlternatingSplit() 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, ¤t); // Move a node to 'a' if (current!= NULL) { MoveNode(&b, ¤t); // Move a node to 'b' *aref = a; *bref = b; AlternatingSplit() Using Dummy Nodes Here is an alternative approach which builds the sub-lists in the same order as the source list. The trick here is to use a temporary dummy header nodes for the 'a' and 'b' lists as they are being built. Each sublist has a "tail" pointer which points to its current last node that way new nodes can be appended to the end of each list easily. The dummy nodes
26 26 give the tail pointers something to point to initially. The dummy nodes are efficient in this case because they are temporary and allocated in the stack. Alternately, the "local references" technique could be used to get rid of the dummy nodes (see Section 1 for more details). 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), ¤t); // add at 'a' tail atail = atail->next; // advance the 'a' tail if (current!= NULL) { MoveNode(&(bTail->next), ¤t); btail = btail->next; *aref = adummy.next; *bref = bdummy.next; 13 SuffleMerge() Solution There are four separate solutions included. See Section 1 for information on the various dummy node and reference techniques. 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) { tail->next = b; break; else if (b==null) { tail->next = a; break; else { tail->next = a; tail = a; a = a->next; // empty list cases // common case: move two nodes to tail tail->next = b; tail = b; b = b->next;
27 27 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);
28 28 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 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; a->next = b; b->next = recur; return(result); // one node from a // one from b // then the rest 14 SortedMerge() Solution SortedMerge() Using Dummy Nodes The strategy used here is to use a single "dummy" node as the start of the result list. The pointer tail always points to the last node in the result list, so appending new nodes is easy. The dummy node gives tail something to point to initially when the result list is empty. This dummy node is efficient, since it is only temporary, and it is allocated in the stack. The loop proceeds, removing one node from either 'a' or 'b', and adding it to tail. When we are done, the result is in dummy.next. 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);
29 29 tail = tail->next; return(dummy.next); SortedMerge() Using Local References This solution is structurally very similar to the above, but it avoids using a dummy node. Instead, it maintains a struct node** pointer, lastptrref, that always points to the last pointer of the result list. This solves the same case that the dummy node did dealing with the result list when it is empty. If you are trying to build up a list at its tail, either they dummy node or the struct node** "reference" strategies can be used (see Section 1 for details). struct 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 Recursion Merge() is one of those problems where the recursive solution has fewer weird cases than the iterative solution. You probably wouldn't want to use the recursive version for production code however, because it will use stack space which is proportional the length of the lists. struct 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
30 30 if (a->data <= b->data) { result = a; result->next = SortedMerge3(a->next, b); else { result = b; result->next = SortedMerge3(a, b->next); return(result); 15 MergeSort() Solution The MergeSort strategy is: split into sublists, sort the sublists recursively, merge the two sorted lists together to form the answer. 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 (Extra for experts) Using recursive stack space proportional to the length of a list is not recommended. However, the recursion in this case is ok it uses stack space which is proportional to the log of the length of the list. For a 1000 node list, the recursion will only go about 10 deep. For a 2000 node list, it will go 11 deep. If you think about it, you can see that doubling the size of the list only increases the depth by SortedIntersect() The strategy is to advance up both lists and build the result list as we go. When the current point in both lists are the same, add a node to the result. Otherwise, advance whichever list is smaller. By exploiting the fact that both lists are sorted, we only traverse each list once. To build up the result list, both the dummy node and local reference strategy solutions are shown... // This solution uses the temporary dummy to build up the result list struct 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
31 31 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 reference struct 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) { Push(lastPtrRef, a->data); lastptrref = &((*lastptrref)->next); a=a->next; b=b->next; // found a node for the intersection else if (a->data < b->data) { // advance the smaller list a=a->next; else { b=b->next; return(result); 17 Reverse() Solution This uses the back-middle-front strategy described in the problem statement. We only make one pass of the list. // 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
32 32 three to get the next node. */ struct node* middle = *headref; struct node* front = middle->next; struct node* back = NULL; // the main pointer // the two other pointers (NULL ok) while (1) { middle->next = back; if (front == NULL) break; // fix the middle node // test if done back = middle; middle = front; front = front->next; // advance the three pointers *headref = middle; // fix the head pointer to point to the new front 18 RecursiveReverse() Solution Probably the hardest part is accepting the concept that the RecursiveReverse(&rest) does in fact reverse the rest. Then then there's a trick to getting the one front node all the way to the end of the list. Make a drwaing to see how the trick works. 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; RecursiveReverse(&rest); // empty rest base case // 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
33 33 Appendix Basic Utility Function Implementations Here is the source code for the basic utility functions. Length() // Return the number of nodes in a list 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 = 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);
Linked List Basics. Essential C (http://cslibrary.stanford.edu/101/) features of the C programming language.
Linked List Basics By Nick Parlante Copyright 1998-2001, Nick Parlante Abstract This document introduces the basic structures and techniques for building linked lists with a mixture of explanations, drawings,
CS106A, Stanford Handout #38. Strings and Chars
CS106A, Stanford Handout #38 Fall, 2004-05 Nick Parlante Strings and Chars The char type (pronounced "car") represents a single character. A char literal value can be written in the code using single quotes
CHAPTER 4 ESSENTIAL DATA STRUCTRURES
CHAPTER 4 ESSENTIAL DATA STRUCTURES 72 CHAPTER 4 ESSENTIAL DATA STRUCTRURES In every algorithm, there is a need to store data. Ranging from storing a single value in a single variable, to more complex
Lecture 10: Dynamic Memory Allocation 1: Into the jaws of malloc()
CS61: Systems Programming and Machine Organization Harvard University, Fall 2009 Lecture 10: Dynamic Memory Allocation 1: Into the jaws of malloc() Prof. Matt Welsh October 6, 2009 Topics for today Dynamic
Questions 1 through 25 are worth 2 points each. Choose one best answer for each.
Questions 1 through 25 are worth 2 points each. Choose one best answer for each. 1. For the singly linked list implementation of the queue, where are the enqueues and dequeues performed? c a. Enqueue in
Data Structure with C
Subject: Data Structure with C Topic : Tree Tree A tree is a set of nodes that either:is empty or has a designated node, called the root, from which hierarchically descend zero or more subtrees, which
Data Structures and Data Manipulation
Data Structures and Data Manipulation What the Specification Says: Explain how static data structures may be used to implement dynamic data structures; Describe algorithms for the insertion, retrieval
DATA STRUCTURES USING C
DATA STRUCTURES USING C QUESTION BANK UNIT I 1. Define data. 2. Define Entity. 3. Define information. 4. Define Array. 5. Define data structure. 6. Give any two applications of data structures. 7. Give
1) The postfix expression for the infix expression A+B*(C+D)/F+D*E is ABCD+*F/DE*++
Answer the following 1) The postfix expression for the infix expression A+B*(C+D)/F+D*E is ABCD+*F/DE*++ 2) Which data structure is needed to convert infix notations to postfix notations? Stack 3) The
Data Structures Using C++ 2E. Chapter 5 Linked Lists
Data Structures Using C++ 2E Chapter 5 Linked Lists Test #1 Next Thursday During Class Cover through (near?) end of Chapter 5 Objectives Learn about linked lists Become aware of the basic properties of
Chapter 8: Bags and Sets
Chapter 8: Bags and Sets In the stack and the queue abstractions, the order that elements are placed into the container is important, because the order elements are removed is related to the order in which
Common Data Structures
Data Structures 1 Common Data Structures Arrays (single and multiple dimensional) Linked Lists Stacks Queues Trees Graphs You should already be familiar with arrays, so they will not be discussed. Trees
Algorithms and Data Structures
Algorithms and Data Structures Part 2: Data Structures PD Dr. rer. nat. habil. Ralf-Peter Mundani Computation in Engineering (CiE) Summer Term 2016 Overview general linked lists stacks queues trees 2 2
The Tower of Hanoi. Recursion Solution. Recursive Function. Time Complexity. Recursive Thinking. Why Recursion? n! = n* (n-1)!
The Tower of Hanoi Recursion Solution recursion recursion recursion Recursive Thinking: ignore everything but the bottom disk. 1 2 Recursive Function Time Complexity Hanoi (n, src, dest, temp): If (n >
Lecture Notes on Binary Search Trees
Lecture Notes on Binary Search Trees 15-122: Principles of Imperative Computation Frank Pfenning André Platzer Lecture 17 October 23, 2014 1 Introduction In this lecture, we will continue considering associative
CS 2112 Spring 2014. 0 Instructions. Assignment 3 Data Structures and Web Filtering. 0.1 Grading. 0.2 Partners. 0.3 Restrictions
CS 2112 Spring 2014 Assignment 3 Data Structures and Web Filtering Due: March 4, 2014 11:59 PM Implementing spam blacklists and web filters requires matching candidate domain names and URLs very rapidly
CSCE-608 Database Systems COURSE PROJECT #2
CSCE-608 Database Systems Fall 2015 Instructor: Dr. Jianer Chen Teaching Assistant: Yi Cui Office: HRBB 315C Office: HRBB 501C Phone: 845-4259 Phone: 587-9043 Email: [email protected] Email: [email protected]
Linked Lists, Stacks, Queues, Deques. It s time for a chainge!
Linked Lists, Stacks, Queues, Deques It s time for a chainge! Learning Goals After this unit, you should be able to... Differentiate an abstraction from an implementation. Define and give examples of problems
Coding Rules. Encoding the type of a function into the name (so-called Hungarian notation) is forbidden - it only confuses the programmer.
Coding Rules Section A: Linux kernel style based coding for C programs Coding style for C is based on Linux Kernel coding style. The following excerpts in this section are mostly taken as is from articles
LINKED DATA STRUCTURES
LINKED DATA STRUCTURES 1 Linked Lists A linked list is a structure in which objects refer to the same kind of object, and where: the objects, called nodes, are linked in a linear sequence. we keep a reference
recursion, O(n), linked lists 6/14
recursion, O(n), linked lists 6/14 recursion reducing the amount of data to process and processing a smaller amount of data example: process one item in a list, recursively process the rest of the list
10CS35: Data Structures Using C
CS35: Data Structures Using C QUESTION BANK REVIEW OF STRUCTURES AND POINTERS, INTRODUCTION TO SPECIAL FEATURES OF C OBJECTIVE: Learn : Usage of structures, unions - a conventional tool for handling a
Output: 12 18 30 72 90 87. struct treenode{ int data; struct treenode *left, *right; } struct treenode *tree_ptr;
50 20 70 10 30 69 90 14 35 68 85 98 16 22 60 34 (c) Execute the algorithm shown below using the tree shown above. Show the exact output produced by the algorithm. Assume that the initial call is: prob3(root)
MAX = 5 Current = 0 'This will declare an array with 5 elements. Inserting a Value onto the Stack (Push) -----------------------------------------
=============================================================================================================================== DATA STRUCTURE PSEUDO-CODE EXAMPLES (c) Mubashir N. Mir - www.mubashirnabi.com
A binary search tree or BST is a binary tree that is either empty or in which the data element of each node has a key, and:
Binary Search Trees 1 The general binary tree shown in the previous chapter is not terribly useful in practice. The chief use of binary trees is for providing rapid access to data (indexing, if you will)
1. The memory address of the first element of an array is called A. floor address B. foundation addressc. first address D.
1. The memory address of the first element of an array is called A. floor address B. foundation addressc. first address D. base address 2. The memory address of fifth element of an array can be calculated
5. A full binary tree with n leaves contains [A] n nodes. [B] log n 2 nodes. [C] 2n 1 nodes. [D] n 2 nodes.
1. The advantage of.. is that they solve the problem if sequential storage representation. But disadvantage in that is they are sequential lists. [A] Lists [B] Linked Lists [A] Trees [A] Queues 2. The
7.1 Our Current Model
Chapter 7 The Stack In this chapter we examine what is arguably the most important abstract data type in computer science, the stack. We will see that the stack ADT and its implementation are very simple.
Node-Based Structures Linked Lists: Implementation
Linked Lists: Implementation CS 311 Data Structures and Algorithms Lecture Slides Monday, March 30, 2009 Glenn G. Chappell Department of Computer Science University of Alaska Fairbanks [email protected]
Linked Lists Linked Lists, Queues, and Stacks
Linked Lists Linked Lists, Queues, and Stacks CSE 10: Introduction to C Programming Fall 200 Dynamic data structure Size is not fixed at compile time Each element of a linked list: holds a value points
Lecture 12 Doubly Linked Lists (with Recursion)
Lecture 12 Doubly Linked Lists (with Recursion) In this lecture Introduction to Doubly linked lists What is recursion? Designing a node of a DLL Recursion and Linked Lists o Finding a node in a LL (recursively)
Memory Allocation. Static Allocation. Dynamic Allocation. Memory Management. Dynamic Allocation. Dynamic Storage Allocation
Dynamic Storage Allocation CS 44 Operating Systems Fall 5 Presented By Vibha Prasad Memory Allocation Static Allocation (fixed in size) Sometimes we create data structures that are fixed and don t need
Software Engineering Techniques
Software Engineering Techniques Low level design issues for programming-in-the-large. Software Quality Design by contract Pre- and post conditions Class invariants Ten do Ten do nots Another type of summary
Sorting revisited. Build the binary search tree: O(n^2) Traverse the binary tree: O(n) Total: O(n^2) + O(n) = O(n^2)
Sorting revisited How did we use a binary search tree to sort an array of elements? Tree Sort Algorithm Given: An array of elements to sort 1. Build a binary search tree out of the elements 2. Traverse
Algorithms. Margaret M. Fleck. 18 October 2010
Algorithms Margaret M. Fleck 18 October 2010 These notes cover how to analyze the running time of algorithms (sections 3.1, 3.3, 4.4, and 7.1 of Rosen). 1 Introduction The main reason for studying big-o
Outline. 1 Denitions. 2 Principles. 4 Implementation and Evaluation. 5 Debugging. 6 References
Outline Computer Science 331 Introduction to Testing of Programs Mike Jacobson Department of Computer Science University of Calgary Lecture #3-4 1 Denitions 2 3 4 Implementation and Evaluation 5 Debugging
Recursion vs. Iteration Eliminating Recursion
Recursion vs. Iteration Eliminating Recursion continued CS 311 Data Structures and Algorithms Lecture Slides Monday, February 16, 2009 Glenn G. Chappell Department of Computer Science University of Alaska
Binary Heap Algorithms
CS Data Structures and Algorithms Lecture Slides Wednesday, April 5, 2009 Glenn G. Chappell Department of Computer Science University of Alaska Fairbanks [email protected] 2005 2009 Glenn G. Chappell
Linked List as an ADT (cont d.)
Linked List as an ADT (cont d.) Default constructor Initializes list to an empty state Destroy the list Deallocates memory occupied by each node Initialize the list Reinitializes list to an empty state
Moving from CS 61A Scheme to CS 61B Java
Moving from CS 61A Scheme to CS 61B Java Introduction Java is an object-oriented language. This document describes some of the differences between object-oriented programming in Scheme (which we hope you
Pseudo code Tutorial and Exercises Teacher s Version
Pseudo code Tutorial and Exercises Teacher s Version Pseudo-code is an informal way to express the design of a computer program or an algorithm in 1.45. The aim is to get the idea quickly and also easy
Data Structure [Question Bank]
Unit I (Analysis of Algorithms) 1. What are algorithms and how they are useful? 2. Describe the factor on best algorithms depends on? 3. Differentiate: Correct & Incorrect Algorithms? 4. Write short note:
Data Structures Using C++ 2E. Chapter 5 Linked Lists
Data Structures Using C++ 2E Chapter 5 Linked Lists Doubly Linked Lists Traversed in either direction Typical operations Initialize the list Destroy the list Determine if list empty Search list for a given
[Refer Slide Time: 05:10]
Principles of Programming Languages Prof: S. Arun Kumar Department of Computer Science and Engineering Indian Institute of Technology Delhi Lecture no 7 Lecture Title: Syntactic Classes Welcome to lecture
THIS CHAPTER studies several important methods for sorting lists, both contiguous
Sorting 8 THIS CHAPTER studies several important methods for sorting lists, both contiguous lists and linked lists. At the same time, we shall develop further tools that help with the analysis of algorithms
Home Page. Data Structures. Title Page. Page 1 of 24. Go Back. Full Screen. Close. Quit
Data Structures Page 1 of 24 A.1. Arrays (Vectors) n-element vector start address + ielementsize 0 +1 +2 +3 +4... +n-1 start address continuous memory block static, if size is known at compile time dynamic,
Binary Trees and Huffman Encoding Binary Search Trees
Binary Trees and Huffman Encoding Binary Search Trees Computer Science E119 Harvard Extension School Fall 2012 David G. Sullivan, Ph.D. Motivation: Maintaining a Sorted Collection of Data A data dictionary
Introduction to Data Structures
Introduction to Data Structures Albert Gural October 28, 2011 1 Introduction When trying to convert from an algorithm to the actual code, one important aspect to consider is how to store and manipulate
Ordered Lists and Binary Trees
Data Structures and Algorithms Ordered Lists and Binary Trees Chris Brooks Department of Computer Science University of San Francisco Department of Computer Science University of San Francisco p.1/62 6-0:
Stacks. Linear data structures
Stacks Linear data structures Collection of components that can be arranged as a straight line Data structure grows or shrinks as we add or remove objects ADTs provide an abstract layer for various operations
Glossary of Object Oriented Terms
Appendix E Glossary of Object Oriented Terms abstract class: A class primarily intended to define an instance, but can not be instantiated without additional methods. abstract data type: An abstraction
D06 PROGRAMMING with JAVA
Cicles Formatius de Grau Superior Desenvolupament d Aplicacions Informàtiques D06 PROGRAMMING with JAVA Ch20 Data Structures I PowerPoint presentation, created by Angel A. Juan - ajuanp(@)gmail.com, for
Loop Invariants and Binary Search
Loop Invariants and Binary Search Chapter 4.3.3 and 9.3.1-1 - Outline Ø Iterative Algorithms, Assertions and Proofs of Correctness Ø Binary Search: A Case Study - 2 - Outline Ø Iterative Algorithms, Assertions
Quiz 4 Solutions EECS 211: FUNDAMENTALS OF COMPUTER PROGRAMMING II. 1 Q u i z 4 S o l u t i o n s
Quiz 4 Solutions Q1: What value does function mystery return when called with a value of 4? int mystery ( int number ) { if ( number
AP Computer Science AB Syllabus 1
AP Computer Science AB Syllabus 1 Course Resources Java Software Solutions for AP Computer Science, J. Lewis, W. Loftus, and C. Cocking, First Edition, 2004, Prentice Hall. Video: Sorting Out Sorting,
Lecture Notes on Binary Search Trees
Lecture Notes on Binary Search Trees 15-122: Principles of Imperative Computation Frank Pfenning Lecture 17 March 17, 2010 1 Introduction In the previous two lectures we have seen how to exploit the structure
Analysis of a Search Algorithm
CSE 326 Lecture 4: Lists and Stacks 1. Agfgd 2. Dgsdsfd 3. Hdffdsf 4. Sdfgsfdg 5. Tefsdgass We will review: Analysis: Searching a sorted array (from last time) List ADT: Insert, Delete, Find, First, Kth,
BSc (Hons) Business Information Systems, BSc (Hons) Computer Science with Network Security. & BSc. (Hons.) Software Engineering
BSc (Hons) Business Information Systems, BSc (Hons) Computer Science with Network Security & BSc. (Hons.) Software Engineering Cohort: BIS/05/FT BCNS/05/FT BSE/05/FT Examinations for 2005-2006 / Semester
TREE BASIC TERMINOLOGIES
TREE Trees are very flexible, versatile and powerful non-liner data structure that can be used to represent data items possessing hierarchical relationship between the grand father and his children and
CS104: Data Structures and Object-Oriented Design (Fall 2013) October 24, 2013: Priority Queues Scribes: CS 104 Teaching Team
CS104: Data Structures and Object-Oriented Design (Fall 2013) October 24, 2013: Priority Queues Scribes: CS 104 Teaching Team Lecture Summary In this lecture, we learned about the ADT Priority Queue. A
Krishna Institute of Engineering & Technology, Ghaziabad Department of Computer Application MCA-213 : DATA STRUCTURES USING C
Tutorial#1 Q 1:- Explain the terms data, elementary item, entity, primary key, domain, attribute and information? Also give examples in support of your answer? Q 2:- What is a Data Type? Differentiate
Binary Search Trees. A Generic Tree. Binary Trees. Nodes in a binary search tree ( B-S-T) are of the form. P parent. Key. Satellite data L R
Binary Search Trees A Generic Tree Nodes in a binary search tree ( B-S-T) are of the form P parent Key A Satellite data L R B C D E F G H I J The B-S-T has a root node which is the only node whose parent
Why Use Binary Trees?
Binary Search Trees Why Use Binary Trees? Searches are an important application. What other searches have we considered? brute force search (with array or linked list) O(N) binarysearch with a pre-sorted
SQL Query Evaluation. Winter 2006-2007 Lecture 23
SQL Query Evaluation Winter 2006-2007 Lecture 23 SQL Query Processing Databases go through three steps: Parse SQL into an execution plan Optimize the execution plan Evaluate the optimized plan Execution
Introduction to Algorithms March 10, 2004 Massachusetts Institute of Technology Professors Erik Demaine and Shafi Goldwasser Quiz 1.
Introduction to Algorithms March 10, 2004 Massachusetts Institute of Technology 6.046J/18.410J Professors Erik Demaine and Shafi Goldwasser Quiz 1 Quiz 1 Do not open this quiz booklet until you are directed
Lecture 11 Doubly Linked Lists & Array of Linked Lists. Doubly Linked Lists
Lecture 11 Doubly Linked Lists & Array of Linked Lists In this lecture Doubly linked lists Array of Linked Lists Creating an Array of Linked Lists Representing a Sparse Matrix Defining a Node for a Sparse
Parallelization: Binary Tree Traversal
By Aaron Weeden and Patrick Royal Shodor Education Foundation, Inc. August 2012 Introduction: According to Moore s law, the number of transistors on a computer chip doubles roughly every two years. First
The following themes form the major topics of this chapter: The terms and concepts related to trees (Section 5.2).
CHAPTER 5 The Tree Data Model There are many situations in which information has a hierarchical or nested structure like that found in family trees or organization charts. The abstraction that models hierarchical
Software Engineering Concepts: Testing. Pointers & Dynamic Allocation. CS 311 Data Structures and Algorithms Lecture Slides Monday, September 14, 2009
Software Engineering Concepts: Testing Simple Class Example continued Pointers & Dynamic Allocation CS 311 Data Structures and Algorithms Lecture Slides Monday, September 14, 2009 Glenn G. Chappell Department
IP Subnetting: Practical Subnet Design and Address Determination Example
IP Subnetting: Practical Subnet Design and Address Determination Example When educators ask students what they consider to be the most confusing aspect in learning about networking, many say that it is
root node level: internal node edge leaf node CS@VT Data Structures & Algorithms 2000-2009 McQuain
inary Trees 1 A binary tree is either empty, or it consists of a node called the root together with two binary trees called the left subtree and the right subtree of the root, which are disjoint from each
Module 2 Stacks and Queues: Abstract Data Types
Module 2 Stacks and Queues: Abstract Data Types A stack is one of the most important and useful non-primitive linear data structure in computer science. It is an ordered collection of items into which
Introduction to data structures
Notes 2: Introduction to data structures 2.1 Recursion 2.1.1 Recursive functions Recursion is a central concept in computation in which the solution of a problem depends on the solution of smaller copies
Curriculum Map. Discipline: Computer Science Course: C++
Curriculum Map Discipline: Computer Science Course: C++ August/September: How can computer programs make problem solving easier and more efficient? In what order does a computer execute the lines of code
Data Structures. Level 6 C30151. www.fetac.ie. Module Descriptor
The Further Education and Training Awards Council (FETAC) was set up as a statutory body on 11 June 2001 by the Minister for Education and Science. Under the Qualifications (Education & Training) Act,
Why you shouldn't use set (and what you should use instead) Matt Austern
Why you shouldn't use set (and what you should use instead) Matt Austern Everything in the standard C++ library is there for a reason, but it isn't always obvious what that reason is. The standard isn't
Lecture 2: Data Structures Steven Skiena. http://www.cs.sunysb.edu/ skiena
Lecture 2: Data Structures Steven Skiena Department of Computer Science State University of New York Stony Brook, NY 11794 4400 http://www.cs.sunysb.edu/ skiena String/Character I/O There are several approaches
An Introduction To Simple Scheduling (Primarily targeted at Arduino Platform)
An Introduction To Simple Scheduling (Primarily targeted at Arduino Platform) I'm late I'm late For a very important date. No time to say "Hello, Goodbye". I'm late, I'm late, I'm late. (White Rabbit in
Project 4 DB A Simple database program
Project 4 DB A Simple database program Due Date April (Friday) Before Starting the Project Read this entire project description before starting Learning Objectives After completing this project you should
External Sorting. Chapter 13. Database Management Systems 3ed, R. Ramakrishnan and J. Gehrke 1
External Sorting Chapter 13 Database Management Systems 3ed, R. Ramakrishnan and J. Gehrke 1 Why Sort? A classic problem in computer science! Data requested in sorted order e.g., find students in increasing
Persistent Binary Search Trees
Persistent Binary Search Trees Datastructures, UvA. May 30, 2008 0440949, Andreas van Cranenburgh Abstract A persistent binary tree allows access to all previous versions of the tree. This paper presents
GCE Computing. COMP3 Problem Solving, Programming, Operating Systems, Databases and Networking Report on the Examination.
GCE Computing COMP3 Problem Solving, Programming, Operating Systems, Databases and Networking Report on the Examination 2510 Summer 2014 Version: 1.0 Further copies of this Report are available from aqa.org.uk
Sequences in the C++ STL
CS 311 Data Structures and Algorithms Lecture Slides Wednesday, November 4, 2009 Glenn G. Chappell Department of Computer Science University of Alaska Fairbanks [email protected] 2005 2009 Glenn
Prepare your result file for input into SPSS
Prepare your result file for input into SPSS Isabelle Darcy When you use DMDX for your experiment, you get an.azk file, which is a simple text file that collects all the reaction times and accuracy of
AppendixA1A1. Java Language Coding Guidelines. A1.1 Introduction
AppendixA1A1 Java Language Coding Guidelines A1.1 Introduction This coding style guide is a simplified version of one that has been used with good success both in industrial practice and for college courses.
Linked Lists: Implementation Sequences in the C++ STL
Linked Lists: Implementation Sequences in the C++ STL continued CS 311 Data Structures and Algorithms Lecture Slides Wednesday, April 1, 2009 Glenn G. Chappell Department of Computer Science University
IMPROVING PERFORMANCE OF RANDOMIZED SIGNATURE SORT USING HASHING AND BITWISE OPERATORS
Volume 2, No. 3, March 2011 Journal of Global Research in Computer Science RESEARCH PAPER Available Online at www.jgrcs.info IMPROVING PERFORMANCE OF RANDOMIZED SIGNATURE SORT USING HASHING AND BITWISE
Data Structures and Algorithms
Data Structures and Algorithms CS245-2016S-06 Binary Search Trees David Galles Department of Computer Science University of San Francisco 06-0: Ordered List ADT Operations: Insert an element in the list
(Refer Slide Time: 02:17)
Internet Technology Prof. Indranil Sengupta Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur Lecture No #06 IP Subnetting and Addressing (Not audible: (00:46)) Now,
Access Queries (Office 2003)
Access Queries (Office 2003) Technical Support Services Office of Information Technology, West Virginia University OIT Help Desk 293-4444 x 1 oit.wvu.edu/support/training/classmat/db/ Instructor: Kathy
PES Institute of Technology-BSC QUESTION BANK
PES Institute of Technology-BSC Faculty: Mrs. R.Bharathi CS35: Data Structures Using C QUESTION BANK UNIT I -BASIC CONCEPTS 1. What is an ADT? Briefly explain the categories that classify the functions
CmpSci 187: Programming with Data Structures Spring 2015
CmpSci 187: Programming with Data Structures Spring 2015 Lecture #12 John Ridgway March 10, 2015 1 Implementations of Queues 1.1 Linked Queues A Linked Queue Implementing a queue with a linked list is
Interpreters and virtual machines. Interpreters. Interpreters. Why interpreters? Tree-based interpreters. Text-based interpreters
Interpreters and virtual machines Michel Schinz 2007 03 23 Interpreters Interpreters Why interpreters? An interpreter is a program that executes another program, represented as some kind of data-structure.
The Union-Find Problem Kruskal s algorithm for finding an MST presented us with a problem in data-structure design. As we looked at each edge,
The Union-Find Problem Kruskal s algorithm for finding an MST presented us with a problem in data-structure design. As we looked at each edge, cheapest first, we had to determine whether its two endpoints
CS193j, Stanford Handout #10 OOP 3
CS193j, Stanford Handout #10 Summer, 2003 Manu Kumar OOP 3 Abstract Superclass Factor Common Code Up Several related classes with overlapping code Factor common code up into a common superclass Examples
Class Overview. CSE 326: Data Structures. Goals. Goals. Data Structures. Goals. Introduction
Class Overview CSE 326: Data Structures Introduction Introduction to many of the basic data structures used in computer software Understand the data structures Analyze the algorithms that use them Know
CSC148 Lecture 8. Algorithm Analysis Binary Search Sorting
CSC148 Lecture 8 Algorithm Analysis Binary Search Sorting Algorithm Analysis Recall definition of Big Oh: We say a function f(n) is O(g(n)) if there exists positive constants c,b such that f(n)
