作者:旧梦拾遗186
专栏:数据结构成长日记
每日励志
在通往未来的路上,每个人都是孤独的旅行者。你的人生不会辜负你的。那些转错的弯,那些流下的泪水,那些滴下的汗水,全都让你成为独一无二的自己。
前言:
今天小编带大家学习数据结构中的链表。
目录
链表
1.1链表的概念及结构
1.2链表的分类
1.3链表的实现
1.4完整代码:
SList.h
SList.c
test.c
概念:链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表 中的指针链接 次序实现的 。
现实中 数据结构中 (箭头实际上并不存在,这里只是形象化):
实际中链表的结构非常多样,以下情况组合起来就有 8 种链表结构:
结点的声明:
typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SLTNode* next; }SLTNode;
头插的实现:
void SListPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); newnode->next = *pphead; *pphead = newnode; }
需要注意,即使存储头结点的指针是一个指针,但我们在函数内操作时,需要改变指针的指向位置,所以需要使用二级指针。我们可能对最后两条代码存在疑问,我在这里解释一下:
链表打印实现
//打印 void SListPrint(SLTNode* phead) { //phead不需要断言。因为phead有可能有空,没有数据 //而顺序表的结构体不可能为空,所以要进行断言 SLTNode* cur = phead; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }
创建新结点
SLTNode* BuySLTNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("mail fail"); exit(-1); } newnode->data = x; newnode->next = NULL; return newnode; }
尾插的实现:
//尾插——有无结点。需要找前一个 void SListPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); //空,改变的是SListNode*,需要二级指针 //非空。尾插要改变的是结构体SListNode,只需要结构体的指针 if (*pphead == NULL) { *pphead = newnode; } else { //找尾 SLTNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }
可以看到,我们尾插的情况是有两个的,一个链表里面没有结点,那么就让这个要尾插的结点直接为头。而另一种则是链表中存在结点,我们需要遍历找到最后一个结点,并让这个结点的指针指向新的结点。同时需要注意,我们传的参数是二级指针,我们要解引用才能找到存放头结点地址的指针。
头删的实现:
//头删 void SListPopFront(SLTNode** pphead) { assert(pphead); assert(*pphead); /*if (*pphead == NULL) { return; }*/ SLTNode* del = *pphead; *pphead = (*pphead)->next; free(del); del = NULL; }
尾删的实现:
//尾删 void SListpopback(SLTNode** pphead) { assert(pphead); assert(*pphead); SLTNode* tail = *pphead; SLTNode* prve = NULL; if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { while (tail->next != NULL) { prve = tail; tail = tail->next; } prve->next = NULL; free(tail); tail = NULL; } }
数据的查找:
//查找 SLTNode* SListFind(SLTNode* pphead, SLTDataType x) { assert(pphead); SLTNode* cur = pphead; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }
在 pos 位置插入数据:
//在pos后插入 void SListInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySLTNode(x); newnode->next = pos->next; pos->next = newnode; }
在 pos 位置删除结点:
//删除pos位置,需要找前一个 void SListErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(pos); if (pos == *pphead) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; //检查pos不是链表中的结点 assert(prev); } prev->next = pos->next; free(pos); } }
#define _CRT_SECURE_NO_WARNINGS #pragma once #include
#include #include typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SLTNode* next; }SLTNode; //打印 void SListPrint(SLTNode* phead); //创建新结点 SLTNode* BuySLTNode(SLTDataType x); //销毁 void SListDestory(SLTNode** pphead); //头插 void SListPushFront(SLTNode** pphead, SLTDataType x); //尾插 void SListPushBack(SLTNode** pphead, SLTDataType x); //尾删 void SListPopBack(SLTNode**pphead); //头删 void SListPopFront(SLTNode**pphead); //查找 SLTNode* SListFind(SLTNode* pphead,SLTDataType x); //在pos之前插入 void SListInsert(SLTNode** pphead,SLTNode*pos,SLTDataType x); //在pos之后插入 void SListInsertAfter(SLTNode* pos, SLTDataType x); //删除pos void SListErase(SLTNode** pphead,SLTNode*pos); //删除pos后面位置,了解 void SListEraseAfter(SLTNode* pos);
#define _CRT_SECURE_NO_WARNINGS #include "SList.h" //打印 void SListPrint(SLTNode* phead) { //phead不需要断言。因为phead有可能有空,没有数据 //而顺序表的结构体不可能为空,所以要进行断言 SLTNode* cur = phead; while (cur != NULL) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); } //创建新结点 SLTNode* BuySLTNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("mail fail"); exit(-1); } newnode->data = x; newnode->next = NULL; return newnode; } //销毁 void SListDestory(SLTNode** pphead) { assert(pphead); SLTNode* cur = *pphead; while (cur) { SLTNode* next = cur->next; free(cur); cur = next; } *pphead = NULL; } //头插 void SListPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); newnode->next = *pphead; *pphead = newnode; } //尾插——有无结点。需要找前一个 void SListPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySLTNode(x); //空,改变的是SListNode*,需要二级指针 //非空。尾插要改变的是结构体SListNode,只需要结构体的指针 if (*pphead == NULL) { *pphead = newnode; } else { //找尾 SLTNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } } //尾删——有无结点。需要找前一个 void SListPopBack(SLTNode** pphead) { assert(pphead); SLTNode* tail = *pphead; SLTNode* prev = NULL; assert(*pphead != NULL); if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { while (tail->next != NULL) { prev = tail; tail = tail->next; } prev->next = NULL; free(tail); tail = NULL; /*while (tail->next->next!=NULL) { tail = tail->next; } free(tail->next); tail->next = NULL;*/ } } //头删 void SListPopFront(SLTNode** pphead) { assert(pphead); assert(*pphead); /*if (*pphead == NULL) { return; }*/ SLTNode* del = *pphead; *pphead = (*pphead)->next; free(del); del = NULL; } //查找 SLTNode* SListFind(SLTNode* pphead, SLTDataType x) { assert(pphead); SLTNode* cur = pphead; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } //在pos之前插入,需要找前一个 void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) { assert(pphead); assert(pos); if (pos == *pphead) { SListPushFront(pphead, x); } else { SLTNode* newnode = BuySLTNode(x); SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; assert(prev); } prev->next = newnode; newnode->next = pos; } } //在pos后插入 void SListInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySLTNode(x); newnode->next = pos->next; pos->next = newnode; } //删除pos位置,需要找前一个 void SListErase(SLTNode** pphead, SLTNode* pos) { assert(pphead); assert(pos); if (pos == *pphead) { SListPopFront(pphead); } else { SLTNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; //检查pos不是链表中的结点 assert(prev); } prev->next = pos->next; free(pos); } } //删除pos后面位置 void SListEraseAfter(SLTNode* pos) { assert(pos); if (pos->next == NULL) { return; } else { SLTNode* next = pos->next; pos->next = next->next; free(next); } }
#define _CRT_SECURE_NO_WARNINGS #include "SList.h" //头插、头删 void TestSList1() { SLTNode* plist = NULL; SListPushFront(&plist, 1); SListPushFront(&plist, 2); SListPushFront(&plist, 3); SListPushFront(&plist, 4); SListPrint(plist); SListPopFront(&plist); SListPrint(plist); SListPopFront(&plist); SListPrint(plist); SListPopFront(&plist); SListPrint(plist); SListPopFront(&plist); SListPrint(plist); SListPopFront(&plist); SListPrint(plist); SListDestory(&plist); } //尾插、尾删 void TestSList2() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListPopBack(&plist); SListDestory(&plist); } //查找、在pos之前插入 void TestSList3() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SLTNode* pos = SListFind(plist, 3); if (pos) { //修改 pos->data *= 10; printf("找到了\n"); } else { printf("找不到\n"); } pos = SListFind(plist, 1); if (pos) { SListInsert(&plist, pos, 10); } SListPrint(plist); SListDestory(&plist); } //删除pos位置、删除pos后面的位置 void TestSList4() { SLTNode* plist = NULL; SListPushBack(&plist, 1); SListPushBack(&plist, 2); SListPushBack(&plist, 3); SListPushBack(&plist, 4); SListPrint(plist); SLTNode* pos = SListFind(plist, 3); if (pos) { SListErase(&plist, pos); } SListPrint(plist); pos = SListFind(plist, 1); if (pos) { SListErase(&plist, pos); } SListPrint(plist); } int main() { //TestSList1(); //TestSList2(); //TestSList3(); TestSList4(); return 0; }
以上就是单链表的相关操作,我们不难发现,单链表的优势在于头插头删
而对于一些操作:如尾插尾删而言,我们都需要去找前一个位置,这是比较麻烦的。单链表我们就先介绍到这里了。
这里同时也有一个问题存在:
删除当前位置我们需要去找前一个位置,这效率是比较低的,我们如何改进这个问题❓
找pos位置删除,而就是不找前一个位置(即要求是O(1)):替换法删除,把pos的值和下一个节点的值进行交换,再把pos进行释放掉。但是有一个缺陷:pos不能是尾节点。尾节点没有下一项。
那如果在pos位置之前插入,要求为O(1)呢:
直接插入到pos后面,然后进行交换。