链表
概念与结构
链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。
链表中的节点在内存中可以分布在任意位置,不像数组那样需要连续的存储空间。每个节点都包含了存储的数据以及指向下一个节点的指针。通过这种方式,链表可以灵活地分配和管理内存空间。就像一节节连动的火车车厢;
在数据结构中,呈现:
逻辑图中,呈现:
在逻辑图中,链式结构是连续性的,但实际上不一样连续;从数据结构中看出,链表是通过地址来联系在一起的,不需要地址的连续性;在我们要解决链表相关问题时,只需要画出逻辑图即可;
注意:
结点的空间在堆区中开辟;堆区中申请出的空间,会按照一定的策略进行分配,两次申请的空间可能连续,可能不连续;
链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
可以通过一定的组合达成不同种类的链表;
这里我们比较常用的是有两种结构:
在这里,我们将先实现无头单向非循环链表,这是链表中结构最为简单的;简称单链表。
单链表的接口实现
先写一下它的结构:
#include<stdio.h> #include<assert.h> #include<stdlib.h> typedef int SLTDataType; typedef struct SLTNode { SLTDataType data; struct SListNode* next; }SLTNode;
结构体中放入一个数据存储类型和一个结构体指针;结构体指针存放下一个结点的地址;
单链表打印
void SLTrint(SLTNode* phead) { SLTNode* cur = phead; while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); }
将链表从头到尾遍历一遍,用一个cur指针来进行移动,在while循环中不断遍历打印出结果;打印完就进入下一个结点;
增加链表结点
SLTNode* BuySListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("mallco fail"); exit(-1); } newnode->data = x; newnode->next = NULL; return newnode; }
用动态内存分配进行扩容,同时对data和next进行初始化;最后返回结点;
尾插
void SLPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySListNode(x); if (* pphead == NULL) { * pphead = newnode; } else { SLTNode* tail = * pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }
这里要注意,我们的形参用到了二级指针,因为当结构体指针为空时,我们就需要对结构体指针进行改变,用二级指针接收结构体指针的地址,能够有效的访问,否则将会报错;当结构体指针不为空时,就利用结构体指针通过循环访问到尾结点,然后在尾结点进行连接;
验证:
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); } int main() { Test3(); return 0; }
尾删
void SLPopBack(SLTNode** pphead) { assert(pphead); //判空 assert(*pphead); //一个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } //其他 else { SLTNode* tailPrev = NULL; SLTNode* tail = *pphead; while (tail->next) { tailPrev = tail; tail = tail->next; } free(tail); tailPrev->next = NULL; } }
当删除的是第一个结点,将会改变结构体指针的地址,所以形参要引用二级指针;其他情况就先找到尾结点,然后进行删除;
验证:
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLPopBack(&plist); SLTrint(plist); } int main() { Test3(); return 0; }
头插头删
void SLPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySListNode(x); newnode->next = *pphead; *pphead = newnode; } void SLPopFront(SLTNode** pphead) { assert(pphead); //判空 assert(*pphead); //其他 SLTNode* newhead = (*pphead)->next; free(*pphead); *pphead = newhead; }
头插相对尾插来说比较容易,因为有头指针,所以不用遍历循环来找到尾结点;并且无论头节点是否为空,操作程序都保持一致;
头删只要找到头结点的下一个结点,那么就可以删除了;
验证:
void Test2() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLPushBack(&plist, 5); SLTrint(plist); SLPushFront(&plist, 6); SLPushFront(&plist, 7); SLPushFront(&plist, 8); SLPushFront(&plist, 9); SLTrint(plist); SLPopFront(&plist); SLTrint(plist); } int main() { Test2(); return 0; }
查找与插入
SLTNode* SLFind(SLTNode* phead, SLTDataType x) { //判空 assert(phead); SLTNode* cur = phead; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } void SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySListNode(x); newnode->next = pos->next; pos->next = newnode; }
查找:在循环里面通过结点的data与x进行匹配,找到就返回该结点,找不到返回空;如果有多个结点的data与x一致,返回链表最接近头指针的;
插入:是在pos后面进行插入,这样插入比较方便,不用考虑头指针是否为空的问题;
验证:
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLTNode* pos = SLFind(plist, 3); SLTInsertAfter(pos, 88); SLTrint(plist); } int main() { Test3(); return 0; }
删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pos); if (pos == *pphead) { SLPopFront(pphead); } else { SLTNode* perv = *pphead; while (perv->next != pos) { perv = perv->next; } perv->next = pos->next; free(pos); } } void SLTEraseAfter(SLTNode* pos) { assert(pos); //检查尾节点 assert(pos->next); SLTNode* posNext = pos->next; pos->next = posNext->next; free(posNext); }
第一种删除是删除pos结点,但需要判断该结点是否为首结点;而且需要遍历找到pos结点的前一个结点;比较麻烦;
第二种删除是删除pos结点后一个结点,只需要通过pos结点连接到下下一个结点即可,最后free掉pos的下一个结点;
验证:
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLTNode* pos = SLFind(plist, 3); SLTInsertAfter(pos, 88); SLTrint(plist); SLTErase(&plist, pos); SLTrint(plist); } int main() { Test3(); return 0; }
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLTNode* pos = SLFind(plist, 3); SLTInsertAfter(pos, 88); SLTrint(plist); SLTEraseAfter(pos); SLTrint(plist); } int main() { Test3(); return 0; }
摧毁
void SLTDestroy(SLTNode** pphead) { assert(pphead); SLTNode* cur = *pphead; while (cur) { SLTNode* prev = cur; cur = cur->next; free(prev); } *pphead = NULL; }
通过记住头结点的下一个结点,free掉头节点,然后头节点的下一个结点成为新的头节点;
验证:
void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLTNode* pos = SLFind(plist, 3); SLTInsertAfter(pos, 88); SLTrint(plist); SLTDestroy(&plist); SLTrint(plist); } int main() { Test3(); return 0; }
完整代码
slist.h
#pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> typedef int SLTDataType; typedef struct SLTNode { SLTDataType data; struct SListNode* next; }SLTNode; void SLTrint(SLTNode* phead); SLTNode* BuySListNode(SLTDataType x); void SLPushBack(SLTNode** pphead, SLTDataType x); void SLPushFront(SLTNode** pphead, SLTDataType x); void SLPopBack(SLTNode** pphead); void SLPopFront(SLTNode** pphead); SLTNode* SLFind(SLTNode* phead, SLTDataType x); void SLTInsertAfter(SLTNode* pos, SLTDataType x); void SLTErase(SLTNode** pphead, SLTNode* pos); void SLTEraseAfter(SLTNode* pos); void SLTDestroy(SLTNode** phead);
slist.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"Slist.h" void SLTrint(SLTNode* phead) { SLTNode* cur = phead; while (cur) { printf("%d->", cur->data); cur = cur->next; } printf("NULL\n"); } SLTNode* BuySListNode(SLTDataType x) { SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("mallco fail"); exit(-1); } newnode->data = x; newnode->next = NULL; return newnode; } void SLPushBack(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySListNode(x); if (* pphead == NULL) { * pphead = newnode; } else { SLTNode* tail = * pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } } void SLPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); SLTNode* newnode = BuySListNode(x); newnode->next = *pphead; *pphead = newnode; } void SLPopBack(SLTNode** pphead) { assert(pphead); //判空 assert(*pphead); //一个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } //其他 else { SLTNode* tailPrev = NULL; SLTNode* tail = *pphead; while (tail->next) { tailPrev = tail; tail = tail->next; } free(tail); tailPrev->next = NULL; } } void SLPopFront(SLTNode** pphead) { assert(pphead); //判空 assert(*pphead); //其他 SLTNode* newhead = (*pphead)->next; free(*pphead); *pphead = newhead; } SLTNode* SLFind(SLTNode* phead, SLTDataType x) { //判空 assert(phead); SLTNode* cur = phead; while (cur) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; } void SLTInsertAfter(SLTNode* pos, SLTDataType x) { assert(pos); SLTNode* newnode = BuySListNode(x); newnode->next = pos->next; pos->next = newnode; } void SLTErase(SLTNode** pphead, SLTNode* pos) { assert(pos); if (pos == *pphead) { SLPopFront(pphead); } else { SLTNode* perv = *pphead; while (perv->next != pos) { perv = perv->next; } perv->next = pos->next; free(pos); } } void SLTEraseAfter(SLTNode* pos) { assert(pos); //检查尾节点 assert(pos->next); SLTNode* posNext = pos->next; pos->next = posNext->next; free(posNext); } void SLTDestroy(SLTNode** pphead) { assert(pphead); SLTNode* cur = *pphead; while (cur) { SLTNode* prev = cur; cur = cur->next; free(prev); } *pphead = NULL; }
test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"Slist.h" void Test1() { int n; SLTNode* plist = NULL; printf("请输入链表长度"); scanf("%d", &n); printf("请输入值"); for (int i = 0; i < n; i++) { int val; scanf("%d", &val); SLTNode* newnode = BuySListNode(val); newnode->next = plist; plist = newnode; } SLTrint(plist); } void Test2() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLPushBack(&plist, 5); SLTrint(plist); SLPushFront(&plist, 6); SLPushFront(&plist, 7); SLPushFront(&plist, 8); SLPushFront(&plist, 9); SLTrint(plist); SLPopFront(&plist); SLTrint(plist); } void Test3() { SLTNode* plist = NULL; SLPushBack(&plist, 1); SLPushBack(&plist, 2); SLPushBack(&plist, 3); SLPushBack(&plist, 4); SLTrint(plist); SLTNode* pos = SLFind(plist, 3); SLTInsertAfter(pos, 88); SLTrint(plist); SLTDestroy(&plist); SLTrint(plist); } int main() { Test3(); return 0; }