Warning: Declaration of action_plugin_subjectindex_indexer::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php on line 15
Warning: Declaration of action_plugin_mathjax_enable::register(Doku_Event_Handler &$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/mathjax/action/enable.php on line 62
Warning: Declaration of action_plugin_googleanalytics::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/googleanalytics/action.php on line 40
Warning: Declaration of action_plugin_folded::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/folded/action.php on line 40
Warning: Declaration of action_plugin_hidden::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/hidden/action.php on line 28
Warning: Declaration of action_plugin_include::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/include/action.php on line 354
Warning: Declaration of action_plugin_tag::register(&$contr) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/tag/action.php on line 175
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 532
Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 818
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
======Exercise 33: Linked List Algorithms======
I'm going to cover two algorithms you can do on a linked list that
involve sorting. I'm going to warn you first that if you need to sort
the data, then don't use a linked list. These are horrible for sorting
things, and there's much better data structures you can use if that's a
requirement. I'm covering these two algorithms because they are
slightly difficult to pull off with a linked list and get you thinking
about manipulating them efficiently.
In the interest of writing this book, I'm going to put the algorithms
in two different files list_algos.h and list_algos.c then write a test
in list_algos_test.c. For now just follow my structure, as it does keep
things clean, but if you ever work on other libraries remember this
isn't a common structure.
In this exercise I'm going to also give you an extra challenge and I
want you to try not to cheat. I'm going to give you the unit test
first, and I want you to type it in. Then I want you to try and
implement the two algorithms based on their descriptions in Wikipedia
before seeing if your code is like my code.
======Bubble And Merge Sort======
You know what's awesome about the Internet? I can just link you to the
Bubble Sort page Merge Sort page and tell you to read that. Man, that
saves me a boat load of typing. Now I can tell you how to actually
implement each of these using the pseudo-code they have there. Here's
how you can tackle an algorithm like this:
* Read the description and look at any visualizations it has.
* Either draw the algorithm on paper using boxes and lines, or
actually take a deck of numbered cards (like Poker Cards) and try
to do the algorithm manually. This gives you a concrete
demonstration of how the algorithm works.
* Create the skeleton functions in your list_algos.c file and make a
working list_algos.h file, then setup your test harness.
* Write your first failing test and get everything to compile.
* Go back to the Wikipedia page and copy-paste the pseudo-code (not
the C code!) into the guts of the first function you're making.
* Translate this pseudo-code into good C code like I've taught you,
using your unit test to make sure it's working.
* Fill out some more tests for edge cases like, empty lists, already
sorted lists, etc.
* Repeat for the next algorithm and test.
I just gave you the secret to figuring out most of the algorithms out
there, that is until you get to some of the more insane ones. In this
case you're just doing the Bubble and Merge Sorts from Wikipedia, but
those will be good starters.
======The Unit Test======
Here is the unit test you should try to get passing:
#include "minunit.h"
#include
#include
#include
char *values[] = {"XXXX", "1234", "abcd", "xjvef", "NDSS"};
#define NUM_VALUES 5
======List *create_words()======
{
int i = 0;
List *words = List_create();
for(i = 0; i < NUM_VALUES; i++) {
List_push(words, values[i]);
}
return words;
}
int is_sorted(List *words)
{
LIST_FOREACH(words, first, next, cur) {
if(cur->next && strcmp(cur->value, cur->next->value) > 0) {
debug("%s %s", (char *)cur->value, (char *)cur->next->value);
return 0;
}
}
return 1;
}
char *test_bubble_sort()
{
List *words = create_words();
// should work on a list that needs sorting
int rc = List_bubble_sort(words, (List_compare)strcmp);
mu_assert(rc == 0, "Bubble sort failed.");
mu_assert(is_sorted(words), "Words are not sorted after bubble sort.");
// should work on an already sorted list
rc = List_bubble_sort(words, (List_compare)strcmp);
mu_assert(rc == 0, "Bubble sort of already sorted failed.");
mu_assert(is_sorted(words), "Words should be sort if already bubble sorted."
);
List_destroy(words);
// should work on an empty list
words = List_create(words);
rc = List_bubble_sort(words, (List_compare)strcmp);
mu_assert(rc == 0, "Bubble sort failed on empty list.");
mu_assert(is_sorted(words), "Words should be sorted if empty.");
List_destroy(words);
return NULL;
}
char *test_merge_sort()
{
List *words = create_words();
// should work on a list that needs sorting
List *res = List_merge_sort(words, (List_compare)strcmp);
mu_assert(is_sorted(res), "Words are not sorted after merge sort.");
List *res2 = List_merge_sort(res, (List_compare)strcmp);
mu_assert(is_sorted(res), "Should still be sorted after merge sort.");
List_destroy(res2);
List_destroy(res);
List_destroy(words);
return NULL;
}
char *all_tests()
{
mu_suite_start();
mu_run_test(test_bubble_sort);
mu_run_test(test_merge_sort);
return NULL;
}
======RUN_TESTS(all_tests);======
I suggest that you start with the bubble sort and get that working,
then move on to the merge sort. What I would do is lay out the function
prototypes and skeletons that get all three files compiling, but not
passing the test. Then just fill in the implementation until it starts
working.
======The Implementation======
Are you cheating? In future exercises I will do exercises where I just
give you a unit test and tell you to implement it, so it'll be good
practice for you to not look at this code until you get your own
working. Here's the code for the list_algos.c and list_algos.h:
#ifndef lcthw_List_algos_h
#define lcthw_List_algos_h
#include
typedef int (*List_compare)(const void *a, const void *b);
int List_bubble_sort(List *list, List_compare cmp);
======List *List_merge_sort(List *list, List_compare cmp);======
#endif
#include
#include
inline void ListNode_swap(ListNode *a, ListNode *b)
{
void *temp = a->value;
a->value = b->value;
b->value = temp;
}
int List_bubble_sort(List *list, List_compare cmp)
{
int sorted = 1;
if(List_count(list) <= 1) {
return 0; // already sorted
}
do {
sorted = 1;
LIST_FOREACH(list, first, next, cur) {
if(cur->next) {
if(cmp(cur->value, cur->next->value) > 0) {
ListNode_swap(cur, cur->next);
sorted = 0;
}
}
}
} while(!sorted);
return 0;
}
inline List *List_merge(List *left, List *right, List_compare cmp)
{
List *result = List_create();
void *val = NULL;
while(List_count(left) > 0 || List_count(right) > 0) {
if(List_count(left) > 0 && List_count(right) > 0) {
if(cmp(List_first(left), List_first(right)) <= 0) {
val = List_shift(left);
} else {
val = List_shift(right);
}
List_push(result, val);
} else if(List_count(left) > 0) {
val = List_shift(left);
List_push(result, val);
} else if(List_count(right) > 0) {
val = List_shift(right);
List_push(result, val);
}
}
return result;
}
======List *List_merge_sort(List *list, List_compare cmp)======
{
if(List_count(list) <= 1) {
return list;
}
List *left = List_create();
List *right = List_create();
int middle = List_count(list) / 2;
LIST_FOREACH(list, first, next, cur) {
if(middle > 0) {
List_push(left, cur->value);
} else {
List_push(right, cur->value);
}
middle--;
}
List *sort_left = List_merge_sort(left, cmp);
List *sort_right = List_merge_sort(right, cmp);
if(sort_left != left) List_destroy(left);
if(sort_right != right) List_destroy(right);
return List_merge(sort_left, sort_right, cmp);
}
The bubble sort isn't too bad to figure out, although it is really
slow. The merge sort is much more complicated, and honestly I could
probably spend a bit more time optimizing this code if I wanted to
sacrifice clarity.
There is another way to implement merge sort using a "bottom up"
method, but it's a little harder to understand so I didn't do it. As
I've already said, sorting algorithms on linked lists are entirely
pointless. You could spend all day trying to make this faster and it
will still be slower than almost any other sortable data structure. The
nature of linked lists is such that you simply don't use them if you
need to sort things.
======What You Should See======
If everything works then you should get something like this:
$ make clean all
rm -rf build src/lcthw/list.o src/lcthw/list_algos.o tests/list_algos_tests test
s/list_tests
rm -f tests/tests.log
find . -name "*.gc*" -exec rm {} \;
rm -rf `find . -name "*.dSYM" -print`
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o src/lcthw/list.o
src/lcthw/list.c
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -fPIC -c -o src/lcthw/list_a
lgos.o src/lcthw/list_algos.c
ar rcs build/liblcthw.a src/lcthw/list.o src/lcthw/list_algos.o
ranlib build/liblcthw.a
cc -shared -o build/liblcthw.so src/lcthw/list.o src/lcthw/list_algos.o
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG build/liblcthw.a tests/list
_algos_tests.c -o tests/list_algos_tests
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG build/liblcthw.a tests/list
_tests.c -o tests/list_tests
sh ./tests/runtests.sh
======Running unit tests:======
----
======RUNNING: ./tests/list_algos_tests======
======ALL TESTS PASSED======
======Tests run: 2======
tests/list_algos_tests PASS
----
======RUNNING: ./tests/list_tests======
======ALL TESTS PASSED======
======Tests run: 6======
tests/list_tests PASS
$
After this exercise I'm not going to show you this output unless it's
necessary to show you how it works. From now on you should know that I
ran the tests and they all passed and everything compiled.
======How To Improve It======
Going back to the description of the algorithms, there's several ways
to improve these implementations, and there's a few obvious ones:
* The merge sort does a crazy amount of copying and creating lists,
find ways to reduce this.
* The bubble sort Wikipedia description mentions a few optimizations,
implement them.
* Can you use the List_split and List_join (if you implemented them)
to improve merge sort?
* Go through all the defensive programming checks and improve the
robustness of this implementation, protecting against bad NULL
pointers, and create an optional debug level invariant that does
what is_sorted does after a sort.
======Extra Credit======
* Create a unit test that compares the performance of the two
algorithms. You'll want to look at man 3 time for a basic timer
function, and you'll want to run enough iterations to at least have
a few seconds of samples.
* Play with the amount of data in the lists that need to be sorted
and see if that changes your timing.
* Find a way to simulate filling different sized random lists and
measuring how long they take, then graph it and see how it compares
to the description of the algorithm.
* Try to explain why sorting linked lists is a really bad idea.
* Implement a List_insert_sorted that will take a given value, and
using the List_compare, insert the element at the right position so
that the list is always sorted. How does using this method compare
to sorting a list after you've built it?
* Try implementing the "bottom up" merge sort on the wikipedia page.
The code there is already C so it should be easy to recreate, but
try to understand how it's working compared to the slower one I
have here.
Copyright (C) 2010 Zed. A. Shaw
Credits