前言
上篇文章我们讲述了链表,链表有带头、不带头,循环、不循环,单向、双向,三种大的形式这三种形式可以组合出8中结构,今天我们学习最常用的其中一种——带头双向循环链表。
一、带头双向循环链表的结构
我们可以看出这个结构和上篇文章的无头单向非循环链表最大的区别是含有一个头部和每个结构内都含有两个指针,一个指向下个结构,一个指向前一个结构。
二、 带头双向循环链表的实现
2.1链表的创建
typedef int SLTDatatype; typedef struct SListNode { SLTDatatype data; struct SListNode* next; struct SListNode* prev; }SLTNode;
从这个结构体我们就可以看到里面含有两个指针一个next指向下一个,一个prev指向前一个。
2.2开辟新的结点
SLTNode* BuySLT( SLTDatatype x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode==NULL) { perror("malloc failed"); exit(-1); } newnode->data = x; newnode->next = NULL; newnode->prev = NULL; return newnode; }
2.3初始化
SLTNode* SLTInit() { SLTNode* phead = BuySLT(0); phead->next = phead; phead->prev = phead; return phead; }
这个初始化会创建一个头,我们这个头是循环的,要进行的增删查改的操作也是以这个头为根基进行操作的。
2.4释放销毁
void SLTDer(SLTNode* phead) { SLTNode* cur = phead->next; while (cur != phead) { SLTNode* next = cur->next; free(cur); cur = next; } free(phead); }
我们要使用malloc函数开辟空间,在程序结束时也要依次释放,否则会造成内存泄漏。
2.5链表的打印
void SLprint(SLTNode*phead) { SLTNode* tail = phead->next; printf("phead<=>"); while (tail!= phead) { printf("%d<=>", tail->data); tail = tail->next; } printf("\n"); }
2.6查找链表中的数据
SLTNode* SLFind(SLTNode* phead, SLTDatatype x) { assert(phead); SLTNode* cur = phead->next; while (cur!= phead) { if (cur->data == x) { return cur; } else { cur = cur->next; } } return NULL; }
由于我们的链表是循环的不能以结尾指向空的指针结尾,要根据这个结构以不指向头的指针为判断条件创建循环,依次打印每个结构中的有效数据。
2.7尾插
void SLPushBack(SLTNode* phead, SLTDatatype x) { assert(phead); SLTNode* newnode = BuySLT(x); SLTNode* tail = phead->prev; newnode->prev = tail; tail->next = newnode; newnode->next = phead; phead->prev = newnode; }
根据这个链表的结构特点,我们可以通过头指针里的指向后一个也就是末尾的结构体,然后将末尾的结构和新结点里的两个指针连接起来,再将新节点和头结点链接起来,尾插就完成了,整个结构也就串联起来了。
2.8尾删
void SLPopBack(SLTNode* phead) { assert(phead); if (phead->next != phead) { SLTNode* tail = phead->prev; SLTNode* first = tail->prev; first->next = phead; phead->prev = first; free(tail); } }
通过头指针里的指向后一个指针就可以找到链表的尾,再根据链表的尾找到前一个保存前一个释放掉尾,然后再将其和头串联起来。
2.9头插
void SLPushFront(SLTNode* phead,SLTDatatype x) { assert(phead); SLTNode* newnode = BuySLT(x); SLTNode* first = phead->next; newnode->next = first; first->prev = newnode; phead->next = newnode; newnode->prev = phead; }
还是根据这个链表的结构特点就很容易完成头插的操作
2.10头删
void SLPopFront(SLTNode* phead) { assert(phead); SLTNode* first = phead->next; SLTNode* cur = first->next; cur->prev = phead; phead->next = cur; free(first); }
和前面的操作大差不差根据这个链表的特点完成这个操作
三、带头双向循环链表中间随机值的插入和删除
这个附加内容就不详解了,通过前面的文章大家应该能有自己的思路了吧!给大家个参考。
3.1在pos位置插入x
void SLTInsert( SLTNode* pos, SLTDatatype x) { assert(pos); SLTNode* posprev = pos->prev; SLTNode* newnode = BuySLT(x); posprev->next = newnode; newnode->prev = posprev; newnode->next = pos; pos->prev = newnode; }
3.2删除pos位置的值
void SLTErase(SLTNode* pos) { assert(pos); SLTNode* first = pos->prev; SLTNode* cur = pos->next; first->next = cur; cur->prev = first; free(pos); }
四、完整代码
#define _CRT_SECURE_NO_WARNINGS 67 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int SLTDatatype; typedef struct SListNode { SLTDatatype data; struct SListNode* next; struct SListNode* prev; }SLTNode; SLTNode* BuySLT( SLTDatatype x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode==NULL) { perror("malloc failed"); exit(-1); } newnode->data = x; newnode->next = NULL; newnode->prev = NULL; return newnode; } void SLTDer(SLTNode* phead) { SLTNode* cur = phead->next; while (cur != phead) { SLTNode* next = cur->next; free(cur); cur = next; } free(phead); } SLTNode* SLTInit() { SLTNode* phead = BuySLT(0); phead->next = phead; phead->prev = phead; return phead; } void SLprint(SLTNode*phead) { SLTNode* tail = phead->next; printf("phead<=>"); while (tail!= phead) { printf("%d<=>", tail->data); tail = tail->next; } printf("\n"); } SLTNode* SLFind(SLTNode* phead, SLTDatatype x) { assert(phead); SLTNode* cur = phead->next; while (cur!= phead) { if (cur->data == x) { return cur; } else { cur = cur->next; } } return NULL; } //尾插 void SLPushBack(SLTNode* phead, SLTDatatype x) { assert(phead); SLTNode* newnode = BuySLT(x); SLTNode* tail = phead->prev; newnode->prev = tail; tail->next = newnode; newnode->next = phead; phead->prev = newnode; } //尾删 void SLPopBack(SLTNode* phead) { assert(phead); if (phead->next != phead) { SLTNode* tail = phead->prev; SLTNode* first = tail->prev; first->next = phead; phead->prev = first; free(tail); } } //头插 void SLPushFront(SLTNode* phead,SLTDatatype x) { assert(phead); SLTNode* newnode = BuySLT(x); SLTNode* first = phead->next; newnode->next = first; first->prev = newnode; phead->next = newnode; newnode->prev = phead; } //头删 void SLPopFront(SLTNode* phead) { assert(phead); SLTNode* first = phead->next; SLTNode* cur = first->next; cur->prev = phead; phead->next = cur; free(first); } // void SLTInsert( SLTNode* pos, SLTDatatype x) { assert(pos); SLTNode* posprev = pos->prev; SLTNode* newnode = BuySLT(x); posprev->next = newnode; newnode->prev = posprev; newnode->next = pos; pos->prev = newnode; } // void SLTErase(SLTNode* pos) { assert(pos); SLTNode* first = pos->prev; SLTNode* cur = pos->next; first->next = cur; cur->prev = first; free(pos); } int main() { SLTNode* plist = SLTInit(); //尾插 SLPushBack(plist, 1); SLPushBack(plist, 2); SLPushBack(plist, 3); SLPushBack(plist, 4); SLPushBack(plist, 5); //尾插测试 printf("尾插:\n"); SLprint(plist); //尾删 SLPopBack(plist); printf("尾删:\n"); SLprint(plist); //头插 SLPushFront(plist,10); printf("头插:\n"); SLprint(plist); //头删 SLPopFront(plist); printf("头删:\n"); SLprint(plist); //在pos位置后插入x SLTNode* pos1 = SLFind(plist, 4); SLTInsert(pos1, 5); printf("在pos位置插入x:\n"); SLprint(plist); //删除pos位置的值 SLTNode* pos2 = SLFind(plist, 5); SLTErase(pos2); printf("删除pos位置的值\n"); SLprint(plist); SLTDer(plist); plist = NULL; return 0; }
这个就是本篇文章的所有内容了,通读全文我们可以发现带头双向循环链表这个结构确实实用,使用起来也是非常的方便简洁,数据结构链表的内容到这就基本结束了,欢迎大家在评论区留言,交流和探讨,说出自己的想法。