[Structure de données - langage C] Réalisation d'une liste chaînée circulaire bidirectionnelle

Table des matières

1. Introduction d'une liste liée circulaire bidirectionnelle

2. L'interface de la liste liée circulaire principale bidirectionnelle

3. Mise en œuvre de l'interface

3.1 Ouvrir les nœuds

3.2 Créer le nœud principal de la liste chaînée renvoyée

3.3 Déterminer si la liste chaînée est vide

3.4 impression

3.5 Recherche de listes doublement chaînées

3.6 La liste doublement chaînée est insérée devant pos

3.6.1 Prise

3.6.2 Prise arrière

3.6.3 Mettre à jour la méthode d'écriture de l'insert de tête et de l'insert de queue

3.7 La liste doublement chaînée supprime le nœud à la position pos

3.7.1 Suppression d'en-tête

3.7.2 Suppression de queue

3.7.3 Mettre à jour la suppression de la tête et de la queue

3.8 Destruction de listes doublement chaînées

4. Code complet

5. Essai de fonctionnement


1. Introduction d'une liste liée circulaire bidirectionnelle

Nous avons divisé ce sujet pour extraire trois mots-clés : bidirectionnel, en tête et en boucle. Commençons par ces trois mots-clés :

La première est bidirectionnelle : bidirectionnelle signifie que ce nœud peut trouver son prédécesseur et son successeur, ce qui est essentiellement différent de la liste simplement chaînée ;

Le second est le lead : le lead montre que la liste chaînée a un nœud principal, qui peut aussi être appelé le nœud principal de la position sentinelle. Ce nœud est le même que les autres nœuds, sauf que son champ de données est rempli de valeurs aléatoires ​​(Certains stockeront la longueur de la liste chaînée);

Le dernier est la boucle : la boucle montre que sa structure est un anneau, le nœud prédécesseur du nœud principal stocke le nœud de queue, et le nœud successeur du nœud de queue stocke le nœud de tête.

2. L'interface de la liste liée circulaire principale bidirectionnelle

La fonction de la liste liée circulaire bidirectionnelle est la même que celle de la liste liée unique, et les deux peuvent être ajoutées, supprimées, vérifiées et modifiées, mais les caractéristiques de la liste liée circulaire bidirectionnelle améliorent considérablement notre efficacité. lors de l'écriture d'une telle fonction.

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

3. Mise en œuvre de l'interface

3.1 Ouvrir les nœuds

Notre liste chaînée est une liste chaînée dynamique, nous devons donc mallocer un nœud chaque fois que nous l'insérons, et nous l'encapsulons dans une fonction pour faciliter la réutilisation ultérieure du code.

ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (NULL == newnode)
	{
		perror("malloc fail:");
		return NULL;
	}
	
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

3.2 Créer le nœud principal de la liste chaînée renvoyée

Cette étape est l'endroit où nous créons un nœud principal sentinelle pour la liste liée. Parce qu'il s'agit d'une liste chaînée circulaire bidirectionnelle, nous laissons les pointeurs prev et next pointer vers nous-mêmes, de sorte que même s'il n'y a qu'un seul nœud principal, nous sommes également une structure circulaire bidirectionnelle.

ListNode* ListCreate()
{
	ListNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

3.3 Déterminer si la liste chaînée est vide

Cette étape est une fonction de jugement vide que nous encapsulons pour les suppressions ultérieures. Lorsque la liste chaînée existe, cela n'exclut pas le cas où la liste chaînée est vide, nous encapsulons donc cette fonction afin que les interfaces suivantes puissent être réutilisées.

bool ListEmpty(ListNode* pHead)
{
	assert(pHead);

	return pHead->next == pHead;
}

3.4 impression

Nous sommes déjà familiers avec la fonction d'impression, mais nous devons faire attention à la condition de fin d'impression de la liste chaînée circulaire à double sens. Ici, ce n'est plus le précédent cur == NULL, mais cur != pHead. Lorsque cur atteint pHead , cela signifie que nous avons traversé. Toute la liste chaînée a disparu.

void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	printf("guard");
	while (cur != pHead)
	{
		printf("<==>%d", cur->data);
		cur = cur->next;
	}
	printf("<==>\n");
}

3.5 Recherche de listes doublement chaînées

Ce n'est pas difficile à trouver, mais ici, il convient de noter que le premier nœud de la liste chaînée bidirectionnelle est le nœud sentinelle, nous devons donc parcourir la recherche à partir du nœud suivant de la sentinelle (cur = pHead-> next), la fin de la boucle La condition est toujours cur != pHead.

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	while (cur != pHead)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	return NULL;
}

3.6 La liste doublement chaînée est insérée devant pos

Après avoir trouvé la position pos, nous l'insérons avant la position pos et nous l'exécutons selon le processus suivant :

1. Si nous voulons insérer un data4 avant data3, nous définissons d'abord une variable de pointeur de structure prev, et sauvegardons d'abord le nœud data3->prev (data2) dans la variable prev ;

2. Changez ensuite prev->next en data4, puis changez data4->prev en prev;

3. Enfin, définissez data4->next = data3, puis définissez data3->prev = data4.

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);
	
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

3.6.1 Prise

Pour l'insertion de la tête, nous sommes cohérents avec l'insertion avant la position pos, enregistrez d'abord le premier nœud, puis insérez.

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* newnode = BuyListNode(x);
	ListNode* first = pHead->next;//多写这一个变量下面的插入语句就可以无序写

	pHead->next = newnode;
	newnode->next = first;
	newnode->prev = pHead;
	first->prev = newnode;
}

3.6.2 Prise arrière

Pour l'insertion de queue, enregistrez d'abord le nœud de queue de la liste chaînée, puis insérez-le, ce qui revient toujours à insérer avant la position pos.

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListNode* tail = pHead->prev;
	ListNode* newnode = BuyListNode(x);

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = pHead;
	pHead->prev = newnode;
}

3.6.3 Mettre à jour la méthode d'écriture de l'insert de tête et de l'insert de queue

En comparant le code d'insertion, il n'est pas difficile de constater que la logique de l'insertion de la tête et de la queue est la même que le code inséré avant la position pos, et les fonctions implémentées n'ont fondamentalement pas changé, nous pouvons donc directement réutiliser ListInsert lors de l'insertion la tête et la queue.L'interface peut être réalisée.

1> prise

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead->next, x);
}

2> bouchon de queue

Parce qu'elle est insérée avant la position pos, la liste chaînée est un cycle bidirectionnel, donc le premier paramètre passé est pHead, et le nœud avant le nœud sentinelle est le nœud de queue.

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);

	ListInsert(pHead, x);
}

3.7 La liste doublement chaînée supprime le nœud à la position pos

Après avoir trouvé le nœud pos, nous supprimons le nœud pos et nous exécutons le processus suivant :

1. Définissez deux variables de pointeur de structure posPrev et posNext, et stockez les nœuds avant et après la position pos dans les variables de pointeur posPrev et posNext respectivement ;

2. Définissez posPrev->next = posNext, puis définissez posNext->prev = posPrev ;

3. Relâchez le nœud pos, free(pos).

void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

3.7.1 Suppression d'en-tête

Si la tête est supprimée, nous sauvegardons d'abord le nœud pHead->next et le nœud pHead->next->next, connectons le nœud sentinelle au nœud suivant du nœud principal, puis relâchons le nœud principal.

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* first = pHead->next;
	ListNode* second = first->next;

	pHead->next = second;
	first->prev = pHead;
	free(first);
}

3.7.2 Suppression de queue

Si la queue est supprimée, nous sauvegardons d'abord le nœud pHead->prev et le nœud pHead->prev->prev, connectons le nœud sentinelle au nœud précédent du nœud de queue, puis relâchons le nœud de queue.

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

	tailPrev->next = pHead;
	pHead->prev = tailPrev;
    free(tail);
}

Résumé : Avant de supprimer la tête et la queue, il est nécessaire de juger si la liste chaînée est vide.

3.7.3 Mettre à jour la suppression de la tête et de la queue

En comparant le code de suppression, il n'est pas difficile de constater que la logique de code de suppression de la suppression de la tête et de la suppression de la queue est la même que celle de la suppression du nœud de position pos, et les fonctions implémentées n'ont fondamentalement pas changé, nous pouvons donc directement réutiliser ListErase lors de la suppression de la tête et de la queue.L'interface peut être réalisée.

1> supprimer la tête

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListErase(pHead->next);
}

2> supprimer la queue

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	ListErase(pHead->prev);
}

3.8 Destruction de listes doublement chaînées

La destruction de la liste chaînée consiste principalement à traverser la liste chaînée une fois, à partir de cur = pHead->next, la condition de fin de boucle est cur != pHead, après traversée, le nœud sentinelle est libéré, et toute la liste chaînée est libéré.

void ListDestory(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);

		cur = next;
	}
	free(pHead);
}

4. Code complet

Le code complet est dans l'entrepôt de code, entrée : Langage C : Code d'apprentissage du langage C, revoir plus - Gitee.com

5. Essai de fonctionnement

Je suppose que tu aimes

Origine blog.csdn.net/Ljy_cx_21_4_3/article/details/130729905
conseillé
Classement