Einfach verkettete Liste

Aus TriagonyWiki

Wechseln zu: Navigation, Suche

Eine einfach verkettete Liste, ist die einfachste Form einer Liste.

Inhaltsverzeichnis

Definition

Eine einfach verkettete Liste besteht aus einem Kopf und genau einem Rest. Der Kopf besteht wiederum aus einem Kopf und einem Rest. Mittels einer Grafik und der Liste wurde das ja schon beschrieben. Welche Funktionen eine Liste bieten soll und wie sie sich intern organisiert kann man selber bestimmen. Jedoch sollten einige Funktionsweisen gegeben sein. Beispielsweise sollte man Elemente hinzufügen und löschen können. Auch sollten diese wieder ausgegeben werden können.

Eine einfach verkettete Liste

Beispiel

Ich habe mir die Aufgabe gesetzt eine Liste in C zu implementieren. In anderen Sprachen funktioniert dies ähnlich. In vielen Sprachen sind Listen auch schon vorhanden. Jedoch möchte ich mal unter die Haube schauen und mir selber eine Liste in C implementieren.

Knoten

Zuerst benötigen wir einen Koten einer Liste. Dieser Knoten besteht aus einem Element und einem Zeiger auf ein nachfolgendes Element. Von einem Array ist es bekannt, dass man durch einen Index auf einzelne Elemente des Arrays zugreifen kann. Dieser Zugriff ist direkt. Bei einer Liste ist dies ohne weiteres nicht möglich. Es muss immer durch die gesamte Liste gegangen werden, um einzelne Elemente auszugeben. Dies ist der Grund, warum wir den Index nicht mit Speichern müssen. Es muss immer wieder bei 0 angefangen werden und damit kann man den Abstand zum ersten Element abzählen.

  1. /**
  2.  * Einzelner Knoten der Liste
  3.  */
  4. typedef struct KListNode {
  5. 	void *element;
  6. 	struct KListNode *next;
  7. } KListNode;

Dies steht in der Header Datei des C - Programmes. Dabei wird ein einzelner Knoten einer Liste repräsentiert. Es besitzt einen Zeiger auf ein Element und einen Zeiger auf den nächsten Knoten. Es wird ein void - Zeiger auf ein nächstes Element verwendet, damit in dem Knoten alle Datentypen gespeichert werden können. Dies ist nicht für alle Anforderungen nötig. Darum muss das Element kein Zeiger sein, sondern kann ebenfalls ein Typ sein. Der Speicherverbrauch eines leeren Knoten ist nun 8 Byte.

Kopf

Damit auch Zustandsinformationen gespeichert werden können, benötigen wir einen Kopf. In diesem wird die Größe des zu speichernden Elementes vermerkt, sowie die Anzahl der enthaltenden Elemente und und und. Ebenfalls können über Funktionszeiger Benutzer Funktionen gespeichert werden, welche nachher zum vergleichen von Elementen oder zum löschen wichtig sein können.

  1. /**
  2.  * Der Listenkopf
  3.  */
  4. typedef struct KList {
  5. 	size_t type;
  6. 	int count;
  7. 	int capacity;
  8. 	int resizeable;
  9. 	void (*destroyElementUserFunction)( void *p );
  10. 	int (*compareUserFunction)( const void *s1, const void *s2, size_t n );
  11. 	KListNode *first;
  12. } KList;

Damit dieser als Listenkopf agieren kann, braucht dieser ein Verweis auf das erste Element der Liste. Die "destroyElementUserFunction" ist zum löschen von Elementen gedacht. Wenn in dieser Liste eine Struktur mit einem Zeiger auf eine andere Struktur gespeichert wird, dann würde es zu Speicherlags kommen. Denn die Liste weiß nicht, was für Elemente gespeichert werden. Wenn sie mit "free" den Speicher des Elementes frei räumen möchte, wird ebenfalls der Zeiger gelöcht, welcher in der gespeicherten Struktur angegeben wurde. Damit ist noch Speicher reserviert, jedoch die Referenz entfernt worden. durch den Funktionszeiger "destroyElementUserFunction" ist der Anwender selber für das frei räumen des Elementes verantwortlich.

Liste initialisieren

Um die Liste nutzen zu können, muss diese erst initialisiert werden. Dazu muss der Kopf gesetzt werden.

  1. /**
  2.  * listInit initialisiert eine Liste mit den Standardwerten
  3.  * Es wird ein Zeiger auf die Liste zurück gegeben
  4.  */
  5. KList *klist_initList( size_t const type, int const resizeable,
  6. 		void(*destroyElementUserFunction)( void *p ),
  7. 		int(*compareUserFunction)( const void *s1, const void *s2, size_t n ) ) {
  8.  
  9. 	//Alloziert Speicher für eine neue Liste
  10. 	KList *newList = ( KList * ) malloc( sizeof(KList) );
  11.  
  12. 	if( NULL != newList ) {
  13. 		//Standardwerte werden gesetzt
  14. 		newList->capacity = 0;
  15. 		newList->count = 0;
  16. 		newList->type = type;
  17. 		newList->resizeable = resizeable;
  18. 		newList->first = NULL;
  19.  
  20. 		if( NULL == destroyElementUserFunction )
  21. 			newList->destroyElementUserFunction = free;
  22. 		else
  23. 			newList->destroyElementUserFunction = destroyElementUserFunction;
  24.  
  25. 		if( NULL == compareUserFunction )
  26. 			newList->compareUserFunction = memcmp;
  27. 		else
  28. 			newList->compareUserFunction = compareUserFunction;
  29. 	}
  30.  
  31. 	return newList;
  32.  
  33. }

Wie schon gesagt. Es werden einfach nur Standardwerte gesetzt und natürlich auch die Eingaben des Anwenders beachtet. Es wird Speicher für den Listenkopf Alloziert und die Funktionszeiger werden gesetzt. Sollten diese NULL sein, wird auf die free Funktion zum freiräumen und auf die memcpy Funktion zum vergleichen zurück gegriffen. Danach wird ein Zeiger auf den Listenkopf zurück gegeben.

Knoten initialisieren

Die Initialisierung eines Knotens ist einfacher. Jedoch ist die Vorgehensweise die gleiche.

  1. /**
  2.  * Ein ListenKnoten wird initialisiert. Hierin werden später die Werte gespeichert.
  3.  * Ebenfalls wird ein Zeiger zurück gegeben
  4.  */
  5. static KListNode *klist_initNode( void ) {
  6.  
  7. 	//Für Knoten wird Speicher alloziiert
  8. 	KListNode *newNode = ( KListNode * ) malloc( sizeof(KListNode) );
  9.  
  10. 	if( NULL != newNode ) {
  11. 		//Standartwerte werden gesetzt
  12. 		newNode->element = NULL;
  13. 		newNode->next = NULL;
  14. 	}
  15.  
  16. 	return newNode;
  17.  
  18. }

Es wird wieder Speicher alloziert und die Elemente werden mit NULL initialisiert. Ein Zeiger auf den Speicherbereich wird zurück gegeben. Diese Funktion ist als static deklariert. Dies bedeutet, dass die Funktion von außerhalb nicht sichtbar ist. Für den Anwender sollte diese Funktion auch nicht weiter von Interesse sein, weil Knoten für ihn mit der add und insert Funktion erstellt werden.

Elemente eintragen

Um Elemente in einer Liste eintragen zu können muss erst einmal vorausgesetzt werden, dass wir wissen, wie wir an eine bestimmte Position in der Liste gelangen. Da die Liste keine lineare Speicherstruktur ist, jedoch die Adressen der Nachfolgeelemente gespeichert sind, müssen wir über einige Elemente gehen, um zu der gewünschten Position zu gelangen. Dazu gibt es zwei Strategien. Die erste ist die rekursive Lösung. Dies bedeutet, dass eine Funktion sich selber aufruft. Vorteil dabei ist, dass es übersichtlich ist und bleibt. Der Nachteil dabei ist jedoch, dass alle Variablen auf den Stack geschrieben werden müssen. Das heißt, dass je häufiger sich eine Funktion selber aufruft, der Speicherverbrauch weiter wächst. Die andere Möglichkeit ist der iterative Aufruf. Hierbei wird durch eine Schleife über die Elemente iteriert. Dadurch wird nicht so viel auf den Stack geschrieben. Nachteil ist jedoch, dass es unübersichtlicher ist.
Wichtig ist noch, wie das Element in die Liste eingefügt wird. Diese Liste besteht fast ausschließlich aus Zeigern. Es muss erst Speicher Alloziert werden und das Element muss dort hinein geschrieben werden.

  1. /**
  2.  * Diese Funktion alloziert Speicher und kopiert das Element auf welches der void-Zeiger zeigt
  3.  * mit der angegebenen Größe in den Speicher
  4.  */
  5. static inline void *klist_cpyElement( void const * const element,
  6. 		size_t const size ) {
  7. 	//Inhalt auf den der void-Zeiger zeigt wird kopiert.
  8. 	void *ptr = malloc( size );
  9. 	if( NULL == ptr ) {
  10. 		return NULL;
  11. 	}
  12. 	return memcpy( ptr, element, size );
  13. }

Diese Funktion ist als inline deklariert. Dies bedeutet, dass es keinen Funktionsaufruf gibt, sondern der Compiler an jeder Stelle wo diese Funktion aufgerufen wird den Code einfügt. Damit entfällt das schreiben und lesen von dem Stack.
Nun habe ich zwei Strategien zum eintragen implementiert.

add

Die erste und einfachste Möglichkeit ist add. Die Vorgehensweise ist dabei recht einfach. Wir befinden uns am Anfang im Kopf der Liste. Wir schauen, ob es einen ersten Knoten gibt. Wenn nicht dann muss einer angelegt werden. Dabei muss geprüft werden, ob ein Knoten angelegt werden konnte. Wenn nicht, wird add abgebrochen. Wenn ein Knoten angelegt werden konnte, muss die Kapazität der Liste um eins addiert werden. Nach der Überprüfung, ob es schon ein Knoten gibt, gehen wir in den ersten Knoten rein. Ob wir ihn nun angelegt haben, oder nicht. Nun wird geschaut, ob sich in dem Knoten, in welchem wir uns befinden, ein Element vorhanden ist. Wenn ja, dann schauen wir ob es einen Folgeknoten gibt. Wenn nicht legen wir diesen an. So geht es dann weiter, bis ein Knoten noch kein Element besitzt. Jedoch muss darauf geachtet werden, dass immer wieder geprüft wird, ob ein Knoten angelegt wurde oder nicht. Wir befinden uns jetzt in einem Knoten, welcher noch kein Element besitzt. Nun muss geprüft werden, ob das Element, welches wir eintragen wollen NULL ist oder nicht. Wenn es NULL ist, befinden wir uns am Ende und geben zurück, dass add erfolgreich war. Wenn es ungleich NULL ist, kopieren wir das Element in den Speicher. Bei Erfolg wird die Anzahl der Elemente in der Liste erhöht. Wenn dies nicht der Fall ist, brechen wir hier ab.
Nun können Elemente mit der add Funktion in die Liste eingetragen werden.

  1. /**
  2.  * Der Wert auf den der Zeiger element zeigt wird an die Liste angehangen
  3.  */
  4. char klist_add( KList * const list, void const * const element ) {
  5.  
  6. 	//Wenn kein erstes Element bereit steht
  7. 	if( NULL == list->first ) {
  8. 		//Wird ein erster Knoten angelegt
  9. 		list->first = klist_initNode();
  10. 		//Wenn kein neuer Knoten angelegt werden konnte
  11. 		if( NULL == list->first ) {
  12. 			return 0;
  13. 		}
  14.  
  15. 		//Wird die Kapazität vergrößert
  16. 		list->capacity++;
  17. 	}
  18.  
  19. 	//Erster Knoten ist der Ausgangspunkt
  20. 	KListNode *node = list->first;
  21. 	//Solange wie Elemente in den Knoten vorhanden sind
  22. 	while( NULL != node->element ) {
  23. 		//Wenn es kein Folgeknoten gibt
  24. 		if( NULL == node->next ) {
  25. 			//wird einer angelegt
  26. 			node->next = klist_initNode();
  27. 			//Wenn ein neuer Knoten angelegt werden konnte
  28. 			if( NULL == node->next ) {
  29. 				return 0;
  30. 			}
  31. 			//Wird die Kapazität vergrößert
  32. 			list->capacity++;
  33. 		}
  34. 		//Es wird weiter durch die Liste iteriert
  35. 		node = node->next;
  36. 	}
  37. 	//Wenn ein leeres Element gefunden wurde, wird es gesetzt
  38. 	if( NULL != element ) {
  39. 		node->element = klist_cpyElement( element, list->type );
  40. 		//Wenn das Element eingetragen werden konnte, dann wird count inkremnetiert
  41. 		if( NULL == node->element ) {
  42. 			return 0;
  43. 		}
  44. 		list->count++;
  45. 	}
  46. 	else {
  47. 		node->element = NULL;
  48. 	}
  49. 	return 1;
  50.  
  51. }

insert

Die insert Funktion funktioniert ähnlich wie die add Funktion. Nur dass hierbei ein Index angegeben wird, an welche Stelle das Element geschrieben werden soll. Sollte die Kapazität der Liste noch nicht so groß sein, wie der Index, wird die Liste bis zu dem Index erweitert. Tritt der andere Fall ein, werden alle Folgenden Elemente um eine Position nach hinten verschoben.
Nun zu der Funktionsweise. Weil wir einen Listekopf benutzen, kann von Anfang an nicht mit einer Schleife durch die Liste iteriert werden. Darum muss der erste Knoten immer gesondert behandelt werden. Zu aller erst wird geprüft, ob der Index 0 ist. Wenn ja, dann befinden wir uns nun, am Listenkopf, schon am Ende. Wir speichern uns den Zeiger auf das erste Element zwischen. danach erstellen wir ein neues erstes Element. Hier muss wieder geprüft werden, ob das erstellen erfolgreich war. Dann fügen wir das Element in dem Knoten ein. Danach müssen wir natürlich noch das alte erste Element anfügen. Wenn dies alles geschafft wurde, sind wir schon fertig und können Erfolg berichten. Sollte der Index nicht 0 sein, dann müssen wir prüfen, ob schon ein erstes Element vorhanden ist. Wenn nicht, legen wir eins an. Danach iterieren wir wieder durch die Liste. Nun müssen wir darauf achten, dass wir bei jedem Schritt tiefer in die Liste den Index immer wieder um eins verringern. Sollte der Index dann 1 sein, müssen wir aufhören tiefer in die Liste einzudringen. Dies ist nötig, weil wir sonst das neue Element nicht an der gewünschten Position anfügen können. Denn wir arbeiten nur mit einer Kopie des Zeigers. Wenn wir den tmpNode Zeiger ändern, ändern wir nicht den next Zeiger des Elementes. Dann können wir den Zeiger auf das alte Element zwischenspeichern und nun das neue Element erstellen. Danach wieder das alte Element an das neue Element anfügen und wir können ebenfalls Erfolg verzeichnen, wenn alles geklappt hat.
Sollten viele Elemente hinzugefügt werden, sollte diese Funktion verwendet werden. Da man die Elemente an dem Index 0 schnell einfügen kann. Dazu muss dann nicht durch die ganze Liste iteriert werden, wie es bei der add Funktion der Fall ist. Denn je mehr Elemente durch add hinzugefügt werden, umso länger ist die Liste und umso länger muss add auch durch die Liste iterieren.

  1. /**
  2.  * Fügt ein Element am Index an. Alle folgenden Elemente
  3.  * werden um ein Index nach hinten verschoben
  4.  */
  5. char klist_insert( KList * const list, int index, void const * const element ) {
  6.  
  7. 	KListNode *tmpNode;
  8. 	//wenn der Index 0 ist
  9. 	if( 0 == index ) {
  10. 		tmpNode = list->first;
  11. 		list->first = klist_initNode();
  12. 		//Wenn kein knoten erzeugt werden konnte
  13. 		if( NULL == list->first ) {
  14. 			list->first = tmpNode;
  15. 			return 0;
  16. 		}
  17.  
  18. 		list->capacity++;
  19.  
  20. 		//Wenn das Element NULL ist, darf klist_cpyElement nicht ausgeführt werden
  21. 		if( NULL != element ) {
  22. 			list->first->element = klist_cpyElement( element, list->type );
  23. 			if( NULL == list->first->element ) {
  24. 				free( list->first );
  25. 				list->first = tmpNode;
  26. 				list->capacity--;
  27. 				return 0;
  28. 			}
  29.  
  30. 			//Wenn alles geklappt hat, ist es wunderbar! ;)
  31. 			list->count++;
  32. 		}
  33. 		else {
  34. 			list->fist->element = NULL;
  35. 		}
  36. 		list->first->next = tmpNode;
  37. 		return 1;
  38. 	}
  39.  
  40. 	//Wenn noch keine Elemente in der Liste sind
  41. 	if( NULL == list->first ) {
  42. 		list->first = klist_initNode();
  43. 		if( NULL == list->first ) {
  44. 			return 0;
  45. 		}
  46. 		list->capacity++;
  47. 	}
  48.  
  49. 	//Es wird durch die Liste gegangen, bis vor dem einzufügenden Element
  50. 	KListNode *node = list->first;
  51. 	while( 1 < index ) {
  52. 		if( NULL == node->next ) {
  53. 			node->next = klist_initNode();
  54. 			if( NULL == node->next ) {
  55. 				return 0;
  56. 			}
  57. 			list->capacity++;
  58. 		}
  59. 		node = node->next;
  60. 		index--;
  61. 	}
  62.  
  63. 	//Element wird eingefügt
  64. 	tmpNode = node->next;
  65. 	node->next = klist_initNode();
  66. 	if( NULL == node->next ) {
  67. 		free( node->next );
  68. 		node->next = tmpNode;
  69. 		return 0;
  70. 	}
  71.  
  72. 	list->capacity++;
  73.  
  74. 	//Wenn das Element NULL ist, darf klist_cpyElement nicht ausgeführt werden
  75. 	if( NULL != element ) {
  76. 		node->next->element = klist_cpyElement( element, list->type );
  77. 		//Wenn kein Speicher alloziiert werden konnte, muss alles rückgängig gemacht werden.
  78. 		if( NULL == node->next->element ) {
  79. 			free( node->next );
  80. 			node->next = tmpNode;
  81. 			list->capacity--;
  82. 			return 0;
  83. 		}
  84.  
  85. 		list->count++;
  86. 	}
  87. 	else {
  88. 		node->next->element = NULL;
  89. 	}
  90. 	node->next->next = tmpNode;
  91. 	return 1;
  92.  
  93. }

Elemente ausgeben

Dies ist ebenfalls eine essentieller Teil einer Liste. Die Elemente, welche wir geschrieben haben, möchten wir ja auch wieder abrufen und benutzen können. Hierzu biete ich die get Funktion an. Ebenfalls biete ich eine search Funktion an, wobei dort direkt nach einem Element gesucht werden soll ein Zeiger zurück gegeben wird.

get

Durch die get Funktion kann mit Hilfe des Index ein Element ausgegeben werden. Dabei wird keine Kopie des Elementes zurück gegeben, sondern der Anwender arbeitet direkt auf das Element. Möchte der Anwender mit einer Kopie Arbeiten, muss er dies selbst erledigen. Jedoch bietet dies den Vorteil, dass der Anwender das Element nicht neu in die Liste speichern muss, wenn er das Element bearbeitet. Um das Element auszugeben wird zuerst geprüft, ob es den Index gibt. Wenn die Liste nicht so groß ist, wie der Index angibt, dann müssen wir uns auch nicht die Arbeit machen bis zu dem Index zu gelangen. Ansonsten müssen wir bis zu dem angegeben Index durch die Liste und können dann den Zeiger auf das Element zurück geben.

  1. /**
  2.  * Gibt das Element mit dem Index zurück. Es wird direkt
  3.  * auf das Element in der Liste gearbeitet. Möchte mit einer
  4.  * Kopie gearbeitet werden, muss das Element kopiert werden.
  5.  */
  6. void *klist_get( KList * const list, int index ) {
  7.  
  8. 	if( index >= list->capacity ) {
  9. 		return NULL;
  10. 	}
  11.  
  12. 	KListNode *node = list->first;
  13.  
  14. 	while( 0 < index ) {
  15. 		index--;
  16. 		node = node->next;
  17. 		if( NULL == node ) {
  18. 			return NULL;
  19. 		}
  20. 	}
  21.  
  22. 	return node->element;
  23.  
  24. }

search

Wieder muss durch die Liste iteriert werden. Dabei untersuchen wir nur direkt die Elemente mittels dem Funktionszeiger searchUserFunktion. Wurde diese bei dem Funktionsaufruf nicht gesetzt wird auf die memcmp Funktion aus der stdlib.h zurück gegriffen. Sollten wir das Element gefunden haben geben wir dies aus und sind fertig. Wenn nicht muss weiter durch die Liste iteriert werden. Sind wir am Ende der Liste angelangt und das Element wurde nicht gefunden wird NULL ausgegeben.

  1. /**
  2.  * Sucht das erste Vorkommen eines Elementes, welches mittels der
  3.  * searchUserFunction und search macht und gibt einen Zeiger auf
  4.  * dieses Element zurück.
  5.  */
  6. void *klist_search( KList *list, void *search, int(*searchUserFunction)(
  7. 		const void *s1, const void *s2, size_t n ) ) {
  8.  
  9. 	//Wenn keine angegeben wurde, wird die std fkt verwendet
  10. 	if( NULL == searchUserFunction ) {
  11. 		searchUserFunction = memcmp;
  12. 	}
  13.  
  14. 	//Wir untersuchen nur die Elemente.
  15. 	KListNode *listNode = list->first;
  16. 	while( NULL != listNode ) {
  17. 		if( 0 == searchUserFunction( listNode->element, search, list->type ) ) {
  18. 			//Wenn es macht, gibs aus
  19. 			return listNode->element;
  20. 		}
  21. 		listNode = listNode->next;
  22. 	}
  23.  
  24. 	//Wenn nicht, gib es NULL zurück
  25. 	return NULL;
  26. }

Löschen

Das Löschen von Elementen oder der Liste ist ein essentieller Teil einer Liste. Das Löschen sollte ebenfalls von der Liste selbst übernommen werden. Damit wird dem Anwender arbeit abgenommen und er muss sich nicht direkt um das freiräumen des Speichers kümmern. Nicht direkt heißt, dass er sich um das freiräumen eines Elementes kümmern muss. Dies geschieht jedoch schon beim initialisieren der Liste. Beim initialisieren der Liste gibt der Anwender die destroyUserFunction an. Diese erledigt das Löschen eines einzelnen Elementes. Dies ist sehr wichtig. Denn es kann sich alles mögliche in der Liste befinden. Wir, als Liste, wissen nicht was wir speichern. Sollte ein Element mit einem Zeiger gespeichert werden, würden wir den Zeiger einfach löschen. Jedoch hätten wir vorher nicht den Speicher des Zeigers frei geräumt. Also wäre dieser Speicher immer noch alloziert aber der Verweis weg. So entstehen Speicherlöcher. Da wir nicht wissen was für Elemente in der Liste gespeichert werden, muss der Anwender diesen Platz selber frei räumen. Jedoch braucht er dies nur ein mal zu tun. Denn wir werden die Funktion für alle Elemente der Liste einzeln aufrufen. Damit haben wir dem Anwender wiederum eine Menge Arbeit abgenommen.

Löschen eines einzelnen Knotens

Zu aller erst sollten wir erst einmal überprüfen, ob die Liste die Kapazität bis zu dem angegebenen Index hat. Wenn ja, dann lohnt es sich durch die Liste durch zu steppen. Wenn dies erledigt ist, dann müssen wir schauen, wo wir uns gerade befinden. Das heißt ob wir in die Liste "reinsteppen" müssen oder nicht. Wir müssen hier wieder vor den gewünschten Knoten. Nicht direkt auf ihn, weil wir die Folgeknoten wieder setzen müssen. Dies geht nur, wenn wir uns vor dem gewünschten knoten befinden. Sollten wir schon am Ende sein, also der Index ist 0, dann können wir mit dem freigeben beginnen. Wir müsse zuerst den Zeiger auf den Knoten sichern. Denn dort befindet sich unser Folgeknoten und das Element, welches wir löschen wollen. Nun können wir das Element mit der destroyUserFunction frei geben, wenn sich ein Element in diesem Knoten befindet. Wenn nicht, brauchen wir es natürlich nicht frei geben, da kein Speicher reserviert wurde. Nun kopieren wir den Zeiger des Folgeknotens. Dieser repräsentiert dann den ersten Knoten in der Liste. Danach können wir den zu löschenden Knoten freigeben. Die Kapazität muss un noch um Eins verringert werden und dann sind wir schon fertig. Sollte der Index nicht 0 sein, müssen wir natürlich bis vor das Element gehen. Jedoch sind dann die gleichen Schritte erforderlich.

  1. /**
  2.  * Diese Funktion entfernt einen Listenknoten an dem angegebenen Index.
  3.  * Sollte die komplette Liste händisch gelöscht werden, wird empfohlen,
  4.  * Immer das erste Element zu löschen und abzuprüfen ob count größer als
  5.  * 1 ist.
  6.  */
  7. void klist_remove( KList * const list, int index ) {
  8.  
  9. 	//Wenn kein Knoten in der Liste vorhanden ist, muss auch nix gelöscht werden
  10. 	if( index >= list->capacity ) {
  11. 		return;
  12. 	}
  13.  
  14. 	//Der erste Knoten ist der Ausgangspunkt
  15. 	KListNode *node = list->first;
  16. 	//Wenn der Index 0 ist, ist dies ein Sonderfall
  17. 	if( 0 == index ) {
  18. 		//Wenn ein Element vorhanden ist,
  19. 		if( NULL != node->element ) {
  20. 			//Dann wird es gelöscht
  21. 			list->destroyElementUserFunction( node->element );
  22. 			list->count--;
  23. 		}
  24. 		//Der nachfolgende Knoten ist dann der erste
  25. 		list->first = node->next;
  26. 		//Knoten wird gelöscht
  27. 		free( node );
  28. 		list->capacity--;
  29. 		return;
  30. 	}
  31.  
  32. 	//So lange wie nicht der Vorgänger erreicht ist
  33. 	while( 1 < index ) {
  34. 		//Iteriere weiter
  35. 		index--;
  36. 		//Folgeelement wird neuer Ausgangspunkt
  37. 		node = node->next;
  38. 		//Wenn dieser NULL ist, wurde das Ende der Liste erreicht
  39. 		if( NULL == node ) {
  40. 			//Ende im Gelände
  41. 			return;
  42. 		}
  43. 	}
  44.  
  45. 	//Es wird sich der zu löschende Knoten genommen
  46. 	KListNode *oldNode = node->next;
  47. 	//Wenn ein Element vorhanden ist
  48. 	if( NULL != oldNode->element ) {
  49. 		//Wird es frei gegeben
  50. 		list->destroyElementUserFunction( oldNode->element );
  51. 		list->count--;
  52. 	}
  53.  
  54. 	//Der neue Nächste ist der Folgende des alten
  55. 	node->next = oldNode->next;
  56. 	//Alter Knoten wird frei gegeben
  57. 	free( oldNode );
  58. 	list->capacity--;
  59.  
  60. }

Löschen der Liste

Das löschen der gesamten Liste gestaltet sich nach dem implementiere des entfernen von Elementen denkbar einfach. So lange wie es einen ersten Knoten gibt, löschen wir nur das erste Element der Liste. Der Anwender muss jedoch den Zeiger auf die Liste selbst manuell freigeben. Denn wir arbeiten wiederum nur mit einer Kopie des Zeigers.

  1. /**
  2.  * Vernichtet die Liste und räumt damit den Speicher wieder frei.
  3.  * Es werden jedoch nur die Knoten in der Liste gelöscht.
  4.  * Die Listen-Struktur muss per Hand gelöscht werden.
  5.  */
  6. void klist_removeList( KList * const list ) {
  7.  
  8. 	while( NULL != list->first ) {
  9. 		klist_remove( list, 0 );
  10. 	}
  11.  
  12. }

Resize

Das die Größe einer Liste neu gemessen wird ist sehr wichtig. Denn es kann immer passieren, dass der Anwender ein Element anlegt welches NULL ist oder Elemente aus der Liste löscht, ohne dass er den Knoten an sich löscht. Dazu biete ich die Resize Funktion an. Die Vorgehensweise hier ist etwas anders als die Vorgehensweise beim Eintragen oder ähnlichem. Natürlich bildet das erste Element wieder eine Ausnahme. Da wir nicht wissen, wie viele Elemente, welche NULL sind nach dem ersten Knoten kommen, wird dies diesmal in einer while Schleife behandelt. Wir befinden uns einfach so lange in diese Schleife, wie die Elemente des ersten Knotens NULL sind. Sollte ein Element NULL sein, speichern wir uns temporär den zu löschenden Knoten, setzen den Folgeknoten als erstes Element und löschen den Knoten, welchen wir dazu auserkoren haben. Dies geschieht so lange, bis der erste Knoten ein Element besitzt. Danach geschieht prinzipiell das gleiche mit den folgenden Knoten. Wir müssen und nur abspeichern, wo wir uns gerade befinden. Dann iterieren wir so lange durch die Liste, bis wir am Ende angekommen sind. Zwischendurch prüfen wir, ob die nächsten Knoten immer ein Element besitzen. Wenn nicht, dann löschen wir den Knoten. Dies immer so lange, bis wieder ein Knoten kommt, indem sich ein Element befindet. Danach dann wieder ein Knoten weiter in der Liste. Bis zum Schluss.

  1. /**
  2.  * Wenn eine Liste resizeable ist, dann werden alle
  3.  * Elemente, welche NULL sind entfernt.
  4.  */
  5. char klist_resize( KList * const list ) {
  6.  
  7. 	//Wenn die Liste resized werden darf
  8. 	if( !list->resizeable ) {
  9. 		return 0;
  10. 	}
  11.  
  12. 	if( NULL == list->first ) {
  13. 		return 1;
  14. 	}
  15.  
  16. 	KListNode *tmpNode;
  17.  
  18. 	//Erstes element wird geprüft
  19. 	while( NULL == list->first->element ) {
  20. 		//Element ist NULL und wird gelöscht
  21. 		tmpNode = list->first;
  22. 		list->first = tmpNode->next;
  23. 		list->capacity--;
  24.  
  25. 		free( tmpNode );
  26. 	}
  27.  
  28. 	//Der aktuelle Knoten, an welchem wir uns gerade befinden
  29. 	KListNode *iterateNode = list->first;
  30. 	while( NULL != iterateNode ) {
  31.  
  32. 		//Wenn es einen nächsten Knoten gibt und das Element NULL ist
  33. 		while( NULL != iterateNode->next && NULL == iterateNode->next->element ) {
  34. 			//Dann wird dieser Knoten gelöscht
  35. 			tmpNode = iterateNode->next;
  36. 			iterateNode->next = tmpNode->next;
  37. 			list->capacity--;
  38.  
  39. 			free( tmpNode );
  40. 		}
  41.  
  42. 		//Wenn nicht, gehen wir weiter durch die Liste
  43. 		iterateNode = iterateNode->next;
  44.  
  45. 	}
  46.  
  47. 	return 1;
  48.  
  49. }

Eine Liste an eine Liste anhängen

Bei dieser Funktion soll die zweite Liste an die erste Liste an gehangen werden. Dazu sollte jedoch erst einmal geprüft werden, ob beide Listen rudimentär gleich sind. Dazu wird die Größe der Elemente verglichen und resizeable. Danach muss nur zum letzten Element der Liste gegangen werden und dann kann der erste Knoten aus der zweiten Liste an das Ende der ersten Liste gehangen werden. Dazu sollten dann noch die Kapazität und die Anzahl der Elemente übernommen werden. In der zweiten Liste werden diese dann 0 gesetzt und das erste Element NULL. Der Anwender muss sich nun darum kümmern, den Listenkopf der zweiten Liste frei zu geben.

  1. /**
  2.  * Die Liste second wird an first angehangen.
  3.  * Second muss jedoch manuell wieder freigegeben werden,
  4.  * wenn diese danach nicht mehr benutzt wird.
  5.  */
  6. char klist_append( KList * const first, KList * const second ) {
  7.  
  8. 	if( first->type != second->type ) {
  9. 		return 0;
  10. 	}
  11.  
  12. 	if( first->resizeable != second->resizeable ) {
  13. 		return 0;
  14. 	}
  15.  
  16. 	if( NULL == first->first ) {
  17. 		first->first = second->first;
  18.  
  19. 		first->capacity += second->capacity;
  20. 		first->count += second->count;
  21.  
  22. 		second->first = NULL;
  23. 		second->capacity = 0;
  24. 		second->count = 0;
  25. 		return 1;
  26. 	}
  27.  
  28. 	KListNode *node = first->first;
  29. 	while( NULL != node->next ) {
  30. 		node = node->next;
  31. 	}
  32.  
  33. 	node->next = second->first;
  34.  
  35. 	first->capacity += second->capacity;
  36. 	first->count += second->count;
  37.  
  38. 	second->first = NULL;
  39. 	second->capacity = 0;
  40. 	second->count = 0;
  41.  
  42. 	return 1;
  43.  
  44. }

Liste klonen

Die Liste, welche wir erstellt haben, können wir natürlich auch klonen. Der Umstand, dass wir Zustandsinformationen speichern ermöglicht uns dies. Wir kennen die Größe der Elemente und an welcher Speicheradresse diese beginnen. Wir kennen ebenfalls die Anzahl der Knoten und die Anzahl der Elemente. Das klonen an sich ist dann relativ einfach. Zum einen sollte gesagt werden, wenn ein Fahler auftritt, dann sollte der gesamte Klon gelöscht und wieder freigegeben werden. Als nächstes sollten wir darauf achten, dass wir uns bei dem Klon, wie auch bei dem Original immer an der gleichen Position befinden. Zu aller erst kopieren wir natürlich den Listenkopf. Danach erfolgt das kopieren der Elemente.

  1. /**
  2.  * Klont die Liste mit allen Elementen und Eigenschaften.
  3.  * Es wird ein void - Zeiger auf den Listenkopf zurück gegeben
  4.  * Wenn die Liste nicht geklont werden kann, bsp durch zu wenig
  5.  * Speicher, wird ein NULL zurück gegeben.
  6.  */
  7. void *klist_clone( KList *list ) {
  8.  
  9. 	//Neuer Listenkopf wird erstellt.
  10. 	KList *result = klist_initList( list->type, list->resizeable,
  11. 			list->destroyElementUserFunction, list->compareUserFunction );
  12. 	if( NULL == result ) {
  13. 		return NULL;
  14. 	}
  15.  
  16. 	//capacity und count werden ebenfalls übernommen
  17. 	result->capacity = list->capacity;
  18. 	result->count = list->count;
  19.  
  20. 	//Erstes Element wird kopiert.
  21. 	if( NULL != list->first ) {
  22. 		result->first = klist_initNode();
  23.  
  24. 		if( NULL == result->first ) {
  25. 			free( result );
  26. 			return NULL;
  27. 		}
  28.  
  29. 		//Element wird kopiert
  30. 		if( NULL != list->first->element ) {
  31. 			result->first->element = klist_cpyElement( list->first->element,
  32. 					result->type );
  33. 			if( NULL == result->first->element ) {
  34.  
  35. 				free( result->first );
  36. 				free( result );
  37.  
  38. 				return NULL;
  39. 			}
  40. 		}
  41. 	}
  42.  
  43. 	//Tmp Elemente
  44. 	KListNode *tmpListNode = list->first;
  45. 	KListNode *tmpResultNode = result->first;
  46.  
  47. 	//Alle anderen Elemente können nun auch kopiert werden.
  48. 	while( NULL != tmpListNode->next ) {
  49.  
  50. 		tmpResultNode->next = klist_initNode();
  51. 		if( NULL == tmpListNode->next ) {
  52. 			klist_removeList( result );
  53. 			free( result );
  54.  
  55. 			return NULL;
  56. 		}
  57.  
  58. 		tmpListNode = tmpListNode->next;
  59. 		tmpResultNode = tmpResultNode->next;
  60.  
  61. 		if( NULL != tmpListNode->element ) {
  62. 			tmpResultNode->element = klist_cpyElement( tmpListNode->element,
  63. 					result->type );
  64. 			if( NULL == tmpResultNode->element ) {
  65. 				klist_removeList( result );
  66. 				free( result );
  67.  
  68. 				return NULL;
  69. 			}
  70. 		}
  71.  
  72. 	}
  73.  
  74. 	return result;
  75.  
  76. }

Liste in Array konvertieren

Auch dies ist uns möglich, durch das speichern von Status Informationen. Wir haben die Größe der Elemente und die Anzahl der Elemente in der Liste. Außerdem noch eine Hilfsvariable, zum speichern der Position. Dies können wir alles in einer for-Schleife realisieren. Wir schauen in dem ersten Knoten, ob sich dort ein Element befindet. Wenn nicht, dann müssen wir weiter gehen, bis sich ein Element findet. Danach kopieren wir es einfach und Addieren die Hilfsvariable um eins. Das so lange, wie die Hilfsvariable kleiner als die Anzahl der Elemente in der Liste ist. Dann sind wir fertig. Und können den Zeiger zurück geben. Nun zu der Funktion.

  1. /**
  2.  * Es wird die komplette Liste in einen linearen Speicherbereich
  3.  * geladen. Es wird ein Zeiger auf das erste Element zurück
  4.  * gegeben. Es kann dann wie in einem Array durchitteriert
  5.  * werden. Der Speicherbereich ist list->count * list->type
  6.  * groß. Der Speicherplatz muss manuell mit free wieder freigegeben
  7.  * werden. Da der Speicherbereich mit malloc erstellt wurde.
  8.  */
  9. void *klist_toArray( KList *list ) {
  10.  
  11. 	//Speicherplatz wird reserviert.
  12. 	void *result = malloc( list->count * list->type );
  13.  
  14. 	//Hilfsvariable
  15. 	int pos = 0;
  16. 	KListNode *node = list->first;
  17.  
  18. 	//da wir mit einem void Zeiger arbeiten, müssen wir immer list->type Speicherzellen weiter
  19. 	for( ; pos < ( list->count * list->type ); pos += ( int ) list->type ) {
  20.  
  21. 		//Falls Elemente NULL sind, überspringen wir diese einfach
  22. 		while( NULL == node->element && NULL != node->next ) {
  23. 			node = node->next;
  24. 		}
  25.  
  26. 		//Element wird kopiert
  27. 		memcpy( ( result + pos ), node->element, list->type );
  28.  
  29. 		//Nochmals ein test, ob es noch weitere Knoten gibt
  30. 		if( NULL == node->next )
  31. 			break;
  32.  
  33. 		//Weiter im Programm
  34. 		node = node->next;
  35.  
  36. 	}
  37.  
  38. 	return result;
  39. }

Liste teilen

Beim Liste Teilen bedienen wir uns wieder eines Indexes. Diesen bekommen wir von dem Anwender. Denn diese muss angeben, ab welchem Index er die Liste teilen möchte. Hier wird wiederum eine Hilfsvariable benötigt. Denn wir müssen wissen, wie viele Elemente sich nach der Teilung in den einzelnen Listen befinden. Ebenfalls müssen wir prüfen, ob beide Listen ähnliche Eigenschaften aufweisen. Dies bedeutet, ob sie beide gleichen Typs sind, ob beide resizeable sind und natürlich ob diese beiden die selben UserFunktions nutzen. Wenn dies alles überprüft wurde, gehen wir bis zu dem Index und zählen immer die Elemente mit. Danach kopieren wir den Zeiger in die neue Liste und löschen den Zeiger aus dem aktuellen Knoten. Danach nur nch ein wenig rechnen und schon sind wir wieder fertig.

  1. /**
  2.  * Teilt die Liste nach dem angegebenen Index. First ist die
  3.  * Komplette Liste, diese wird gekürzt, ausschließlich Index.
  4.  * Der Index wird das erste Element der zweiten Liste.
  5.  */
  6. void klist_splitAt( KList * const first, KList *second, int index ) {
  7.  
  8. 	//Wenn der index größer ist als die capacity
  9. 	if( index >= first->capacity ) {
  10. 		//dann muss nichts gemacht werden
  11. 		return;
  12. 	}
  13.  
  14. 	//Dann wird die Liste initialisiert, sollte es nicht schon geschehen sein
  15. 	if( NULL == second ) {
  16. 		return;
  17. 	}
  18. 	else {
  19. 		//Wenn die Listen keine gemeinsame Basis haben.
  20. 		if( first->destroyElementUserFunction
  21. 				!= second->destroyElementUserFunction || first->resizeable
  22. 				!= second->resizeable || first->type != second->type
  23. 				|| first->compareUserFunction != second->compareUserFunction ) {
  24. 			return;
  25. 		}
  26. 	}
  27.  
  28. 	second->capacity = first->capacity - index;
  29. 	int countElements = 0;
  30.  
  31. 	//Wenn ab dem 0ten Element geteilt werden soll
  32. 	if( 0 == index ) {
  33.  
  34. 		//Danach werden die relevanten Daten & Zeiger kopiert...
  35. 		second->capacity = first->capacity;
  36. 		second->count = first->count;
  37. 		second->first = first->first;
  38.  
  39. 		//Und aus der alten gelöscht.
  40. 		first->capacity = 0;
  41. 		first->count = 0;
  42. 		first->first = NULL;
  43.  
  44. 	}
  45.  
  46. 	//Das erste Element hat er...
  47. 	index--;
  48. 	KListNode *node = first->first;
  49. 	while( 0 < index ) {
  50. 		//Wenn in dem aktuellen Element Inhalt ist
  51. 		if( NULL != node->element ) {
  52. 			//Dann wird eins hoch gezählt
  53. 			countElements++;
  54. 		}
  55. 		//Das nächste Element schnabbn
  56. 		node = node->next;
  57. 		index--;
  58. 	}
  59.  
  60. 	//Hier könnte auch noch ein Element sein
  61. 	if( NULL != node->element ) {
  62. 		countElements++;
  63. 	}
  64.  
  65. 	//Elemente kopieren...
  66. 	second->count = first->count - countElements;
  67. 	second->first = node->next;
  68.  
  69. 	//Erste Liste wird gekürzt.
  70. 	node->next = NULL;
  71. 	first->count = countElements;
  72. 	first->capacity = first->capacity - second->capacity;
  73.  
  74. }

sortieren der Liste

Konzept

Es gibt verschiedene Möglichkeiten Datenstrukturen zu sortieren. Dazu kann Wikipedia[1] einen Überblick geben. Alle Verfahren haben ihre Vor- und Nachteile. Ich habe mich für Mergesort[2] entschieden. Er zeichnet sich durch seine Stabilität aus. Dies bedeutet, das es keine besondern Fälle gibt, wo er schneller oder langsamer ist. Die Komplexität ist immer gleich. Außerdem kann Mergesort bei verketteten Listen als inline Verfahren implementiert werden. Dies bedeutet das Die Elemente nicht kopiert werden brauchen und somit auch kein oder kaum extra Speicher benötigt wird. In diesem Beispiel ist dies auch fast der Fall. Das einzige wozu wir zusätzlichen Speicher benötigen ist der Listenkopf. Denn diesen müssen wir für jede Rekrusionsstufe neu erstellen.
Nun zu dem Prinzip wie Mergesort Funktioniert. Mergesort funktioniert nach dem Prinzip teile und herrsche. Die Datenstruktur, also unsere Liste wird in zwei Teile geteilt. Danach werden die Teile an sich sortiert. Das bedeutet ganz einfach, wir rufen Mergesort mit unsere Liste auf. Die Liste wird geteilt in die linke und rechte Liste. Die linke Liste wird nun wieder sortiert und ist damit unser neue Ausgangspunkt. Es wird überprüft, ob sich mehr als ein Knoten in der Liste befindet. Wenn ja, dann wird die Liste wieder geteilt und weiter sortiert. Wenn nicht, also wenn nur noch ein Knoten in der Liste ist, gilt diese als sortiert und wird zurück gegeben.
Nun folgt das interessante. Nämlich das mergen. Gemerged werden immer genau zwei Listen zu einer. Dazu wird sich eine neue Liste geschaffen. In dieser wird das Ergebnis zurück gegeben. Die beiden ersten Elemente der linken und rechten Liste werden verglichen. Das größere Element wird in die neue Liste geschrieben und aus der alten Liste raus gelöscht. Nun werden wieder beide ersten Elemente vergleichen. Dabei ist zu beachten, das vor jedem Vergleich die Anzahl der Knoten in der Liste geprüft werden. Wenn die linke Liste keine Knoten mehr hat, dann wird die gesamte rechte Liste an das Ergebnis gehangen und der Mergeprozess ist beendet. Wenn die rechte Liste keine Knoten mehr hat, wird demzufolge die linke Liste an gehangen.
In dem Merge - Schritt habe ich mir keine Ergebnisliste erstellt sondern habe alles in der linken Liste sortieren lassen. Damit meine ich, wenn in der linken Liste das größere Element war, bin ich in der linken Liste ein Knoten weiter gegangen. Wenn in der rechten Liste das größere Element war, habe ich dies eingefügt in die linke Liste und bin dann ein weiter gegangen. Dies habe ich so lange gemacht, bis ich die ganze Liste sortiert hatte.

mergesort

Dies ist nun der Mergesort - Algorithmus. Er besteht aus zwei Funktionen. Wobei die sort Funktion sich selbst nochmals aufruft. Was passiert nun hier? Zu aller erst prüfen wir, ob die Kapazität der Liste eins ist. Wenn ja, dann gilt die Liste als sortiert und wir geben diese zurück. Wenn nicht, erstellen wir uns einen neuen Listenkopf. Natürlich mit den gleichen Grundeigenschaften wie unsere originale Liste. Danach teilen wir die Liste in der Hälfte. Sollte etwas schief gehen, geben wir einen Fehler zurück. Nun lassen wir beide Teillisten für sich sortieren. Wenn es einen Fehler beim sortieren der Teillisten gibt, muss aufgeräumt werden. Dies eird dann auch getan. Wenn alles gut gelaufen ist werden die Listen zusammen gebracht mittels merge. Wenn dort wieder alles geklappt hat, müssen wir nur die rechte Liste wieder frei machen und sind fertig mit dem sortieren. Das mergen kann man sich als zusammenführen der Listen vorstellen. Dies ist der schwierigere Schritt an mergesort und wird unten weiter beschrieben.

  1. /**
  2.  * Sortiert eine Liste anhand des mergesort -
  3.  * Sortieralgorythmus. Der comparor befindet
  4.  * sich in der Liste
  5.  */
  6. char klist_sort( KList * const list ) {
  7.  
  8. 	if( 1 == list->capacity ) {
  9. 		return 1;
  10. 	}
  11.  
  12. 	KList *right = klist_initList( list->type, list->resizeable,
  13. 			list->destroyElementUserFunction, list->compareUserFunction );
  14.  
  15. 	klist_splitAt( list, right, ( list->capacity / 2 ) );
  16.  
  17. 	//Wenn die Liste nicht erstellt werden konnte
  18. 	if( NULL == right ) {
  19. 		return 0;
  20. 	}
  21.  
  22. 	//Hier vlt threads starten
  23. 	if( !klist_sort( list ) || !klist_sort( right ) ) {
  24.  
  25. 		//Tmp Listenkopf muss wieder freigegeben werden.
  26. 		//um keine Elemente zu verlieren, wird die rechte Liste an die Linke angehangen.
  27. 		klist_append( list, right );
  28. 		free( right );
  29.  
  30. 		return 0;
  31. 	}
  32.  
  33. 	//Listen werden gemerged
  34. 	if( !klist_merge( list, right ) ) {
  35.  
  36. 		//Tmp Listenkopf muss wieder freigegeben werden.
  37. 		//um keine Elemente zu verlieren, wird die rechte Liste an die Linke angehangen.
  38. 		klist_append( list, right );
  39. 		free( right );
  40.  
  41. 		return 0;
  42.  
  43. 	}
  44.  
  45. 	//Tmp Listenkopf muss wieder freigegeben werden.
  46. 	klist_removeList( right );
  47. 	free( right );
  48.  
  49. 	return 1;
  50.  
  51. }

merge

Das mergen der beiden Listen ist das komplizierteste an Mergesort. Jedoch, wenn man einmal das Prinzip dahinter verstanden hat, ist es ganz simpel. Es werden immer die ersten beiden Elemente der Liste verglichen. Dann wird ein Element, kommt darauf an wie verglichen wird, in die Ergebnis Liste geschrieben und aus der Ursprungs Liste gelöscht. Wenn einer der beiden Listen leer ist, wird die andere komplett an die Ergebnis Liste an gehangen.
Ich habe es ähnlich implementiert. Ich habe mir die Ergebnis Liste gespart. Die komplett sortierte Liste wird am ende in der linken Liste vorhanden sein. Die rechte Liste wird Leer sein.
Zu aller erst prüfe ich jedoch die Ähnlichkeit der beiden Listen. Sind diese nicht so ähnlich, wie ich sie haben möchte, wird abgebrochen und ein Fehler zurück gegeben. Danach prüfe ich ob eine der beiden Listen leer ist. Wenn ja, bin ich fertig und speichere die vorhandene Liste in der linken Liste. Wenn bis dahin alles so verlaufen ist, wie ich es wollte, fange ich an die Listen zu mergen. Dazu benötige ich einen temporären Knoten. In diesem wird das aktuelle Element vor dem "ersten" Element der linken Liste gespeichert. Nun vergleiche ich das erste Element der rechten Liste mit dem nächsten Element, welches sich in dem temporären Knoten befindet. Ich muss es mit dem nächsten Element des temporären Knotens vergleichen, weil ich sonst das Element aus der rechten Liste nicht vor das aktuelle Element legen kann. Wenn das rechte Element nach der UserCompareFunction größer ist als das nächste Element aus dem ersten knoten, schreibe ich es vor das nächste Element. Danach gehe ich ein Knoten weiter. Dies mache ich so lange, bis ich mit meinem temporären Knoten das Ende der Liste erreicht habe, oder bis kein Knoten mehr in der rechten Liste ist. Sollten noch Elemente in der rechten Liste sein, dann hänge ich diese an die linke Liste ran. Nach alledem bin ich fertig mit dem mergen und kann erfolg zurück geben.

  1. /**
  2.  * Fügt zwei Listen zusammen. Die Reihenfole wird durch den
  3.  * comparor der Liste left festgelegt.
  4.  */
  5. static char klist_merge( KList * const left, KList * const right ) {
  6.  
  7. 	//Wenn die Listen nicht die gleiche Struktur haben, wird abgebrochen...
  8. 	if( left->destroyElementUserFunction != right->destroyElementUserFunction
  9. 			|| left->resizeable != right->resizeable || left->type
  10. 			!= right->type || left->compareUserFunction
  11. 			!= right->compareUserFunction ) {
  12. 		return 0;
  13. 	}
  14.  
  15. 	//Sollte eine der Listen Leer sein
  16. 	if( NULL == right->first ) {
  17. 		return 1;
  18. 	}
  19.  
  20. 	if( NULL == left->first ) {
  21. 		left->first = right->first;
  22. 		right->first = NULL;
  23. 		return 1;
  24. 	}
  25.  
  26. 	KListNode *tmpNode;
  27.  
  28. 	//Nur wenn beide Elemente nicht NULL sind kann verglichen werden
  29. 	if( NULL != left->first->element && NULL != right->first->element ) {
  30.  
  31. 		if( left->compareUserFunction( left->first->element,
  32. 				right->first->element, left->type ) >= 0 ) {
  33.  
  34. 			tmpNode = left->first;
  35. 			left->first = right->first;
  36. 			right->first = right->first->next;
  37. 			left->first->next = tmpNode;
  38.  
  39. 			left->capacity++;
  40. 			left->count++;
  41.  
  42. 			right->capacity--;
  43. 			right->count--;
  44.  
  45. 		}
  46.  
  47. 	}
  48. 	//Sollte das linke Element NULL sein, wird das rechte vor ihm eingefügt
  49. 	else if( NULL == left->first->element ) {
  50.  
  51. 		tmpNode = left->first;
  52. 		left->first = right->first;
  53. 		right->first = right->first->next;
  54. 		left->first->next = tmpNode;
  55.  
  56. 		left->capacity++;
  57. 		left->count++;
  58.  
  59. 		right->capacity--;
  60. 		right->count--;
  61.  
  62. 	}
  63.  
  64. 	//Nun kann erst durch die Liste iteriert werden.
  65. 	KListNode *iterateNode = left->first;
  66. 	while( NULL != iterateNode->next && NULL != right->first ) {
  67.  
  68. 		if( NULL != iterateNode->next->element && NULL != right->first->element ) {
  69.  
  70. 			if( left->compareUserFunction( iterateNode->next->element,
  71. 					right->first->element, left->type ) >= 0 ) {
  72. 				tmpNode = iterateNode->next;
  73. 				iterateNode->next = right->first;
  74. 				right->first = right->first->next;
  75. 				iterateNode->next->next = tmpNode;
  76.  
  77. 				left->capacity++;
  78. 				left->count++;
  79.  
  80. 				right->capacity--;
  81. 				right->count--;
  82. 			}
  83.  
  84. 		}
  85. 		//Wenn das element NULL sein sollte, wird es hinten dran gahangen
  86. 		else if( NULL == iterateNode->next->element ) {
  87. 			tmpNode = iterateNode->next;
  88. 			iterateNode->next = right->first;
  89. 			right->first = right->first->next;
  90. 			iterateNode->next->next = tmpNode;
  91.  
  92. 			left->capacity++;
  93. 			left->count++;
  94.  
  95. 			right->capacity--;
  96. 			right->count--;
  97. 		}
  98.  
  99. 		//Weiter gehts
  100. 		iterateNode = iterateNode->next;
  101. 	}
  102.  
  103. 	//Wenn die rechte liste nicht leer ist und das ende der linken erreicht ist, wird die rechte ran gehangen.
  104. 	if( NULL != right->first ) {
  105. 		iterateNode->next = right->first;
  106. 		right->first = NULL;
  107.  
  108. 		left->capacity += right->capacity;
  109. 		left->count += right->count;
  110.  
  111. 		right->capacity = 0;
  112. 		right->count = 0;
  113. 	}
  114.  
  115. 	//Wenn alles glatt lief, ist er fertig...
  116. 	return 1;
  117. }

Fazit

Eine Liste ist eine dynamische Speicherstruktur und kann nur dort alle ihre Stärken ausspielen. Bei einer gleichen Anzahl an Elementen ist eine Liste immer größer als ein Array. Ebenfalls ist diese eventuell langsamer als ein Array. Jedoch kann die Liste in dem Sinne punkten, wo die Anzahl der Elemente nicht bekannt ist. Denn dort wissen wir nicht, wie viele Elemente wir für ein Array reservieren sollen. Auch wenn der Platz nicht ausreicht, ist die realloc Funktion sehr langsam.
Ich kann mir vorstellen, dass es viele Strategien gibt, Listen zu beschleunigen. Jedoch sind mir davon keine genauen, mit Quelle, bekannt. Was ich mir vorstellen könnte ist dies mittels Cluster zu realisieren. Damit meine ich, das man in einem Knoten nicht nur ein Element speichern kann, sondern eine bestimmte Anzahl. Dies wäre dann sozusagen ein Array von Elementen in einem Knoten. Der Vorteil davon liegt klar auf der Hand. Ich habe weniger Knoten, durch welche ich durch gehen muss. In den einzelnen Knoten kann ich dann bestimmte Elemente direkt zurück geben. Nachteile sind auch klar. Der Verwaltungsaufwand wird natürlich größer.

Links

Zeiger in C
Funktionszeiger
Der void-Zeiger
Einfach verkettete Listen
Const correctness

Persönliche Werkzeuge