我们之前接触过顺序表(如果没有了解的童鞋可以看看我的另一篇Blog)
数据结构:顺序表
顺序表的优点与缺点:
我们知道顺序表存储数据可以很容易的对数据进行访问(即随机下标访问)时间复杂度O(1),但是它也有自己的缺陷:比如在频繁增删的场景下时间复杂度很不友好O(n),扩容时会导致空间的浪费等一系列问题。
面对这些问题,还有一种存储结构被称作链表。

今天我们就来引入了链表的概念:
链表是逻辑上有连续关系,但是物理上并不连续的链式存储结构。
数据的逻辑顺序是通过链表中指针的连续次序实现的,可以通过结构体指针实现动态的存储和分配。
链表的优点与缺点:
插入和删除的效率高,只需要改变指针的指向即可完成插入和删除,时间复杂度O(1)。内存利用率高,可以使用内存中细小的不连续的空间,只有在需要时创建空间,大小不固定,拓展灵活。但是链表的查找效率比较低,因为链表是从第一个结点向后遍历查找,时间复杂度O(n)。
链表的结点:
链表中的每个结点都分为两个域,一个数据域和一个指针域,如同铁链一样,将数据连接在一起,第一个元素指向第二个,第二个指向……一直到NULL链表结束。

链表的结点结构体:
typedef struct Node{
int value;
struct Node *next;//保存着下一个结点的地址
}Node;
和顺序表一样,我们也会对链表进行一些列的增删查改等操作,接下来我们一起看看吧。
链表的初始化
Main.c
Node *first;
LinkListInit(&first);
LinkList.c
实则就是初始化一条空链表(一个结点都没有的链表)
*first的地址&(*first),一级指针的地址用二级指针来接收。Node **ppFirst
void LinkListInit(Node **ppFirst){
*ppFirst = NULL;
}
链表的头插

- 被插入的数据,首先应当申请自己的空间 malloc。
- 再将数据域和指针域给向这块空间。
- 指针域是原本 *ppFirst的指向。
- 头插结束改变 *ppFirst指向新加入元素的结点地址。
void LinkListPushFront(Node **ppFirst,int v){
Node *node = (Node*)malloc(sizeof(Node));
node->value = v;
node->next = *ppFirst;
*ppFirst = node;
}
链表的头删

- 释放头空间前应当首先存储第一个结点的next(指针域)。
- 释放头空间 free(*ppFirst)
- 改变*ppFirst的指向新的首结点地址。
void LinkListPopFront(Node **ppFirst){
assert(*ppFirst != NULL);
Node *next = (*ppFirst)->next;
free(*ppFirst);
*ppFirst = next;
}
链表的尾插

- 首先寻找最后一个结点的位置,才能尾插。(从第一个结点向后遍历,直到指针域为NULL,说明到达最后一个结点)
- 和头插一样,新的数据要申请自己的空间才能加入链表。
- 给这块空间赋予value和指针域,因为是尾插,所以指针域为NULL。
- 这时,还应该改变插入前最后一个结点的指针域为新空间的地址。
void LinkListPushBack(Node **ppFirst, int v){
if (*ppFirst == NULL){
LinkListPushFront(ppFirst, v);
return;
}
//寻找最后一个结点(至少一个结点)
Node *cur = *ppFirst;
while (cur->next != NULL){
cur = cur->next;
}
//cur就是最后一个结点
Node *node = (Node*)malloc(sizeof(Node));
node->value = v;
node->next = NULL;
//原来最后一个结点的next = 新的结点
cur->next = node;
}
链表的尾删

- 尾删得找到倒数第二个结点,改变其指针域为NULL。
- 释放最后一个结点的空间。
void LinkListPopBack(Node **ppFirst){
assert(*ppFirst != NULL);
//只有一个结点
if ((*ppFirst)->next = NULL){
free(*ppFirst);
*ppFirst = NULL;
return;
}
//找到倒数第二个结点(至少两个结点)
Node *cur = *ppFirst;
while (cur->next->next != NULL){
cur = cur->next;
}
//释放最后一个结点
free(cur->next);
cur->next = NULL;
}
链表的查找
- 链表的查找为遍历查找
Node * LinkListFind(const Node *first, int v){
for (Node *cur = first; cur != NULL;cur = cur->next){
if (cur->value = v){
return cur;
}
}
return NULL;
}
链表的释放
void LinkListDestroy(Node *first){
Node *next;
for (Node *cur = first; cur != NULL; cur = next){
next = cur->next;
free(cur);
}
}
链表中删除第一个遇到的目标结点

- 用cur->next的value和v比较
- 删除目标结点前,先记录cur->next的值,赋给next,删除目标结点后,cur->next = next->next。
void LinkListRemove(Node **ppFirst, int v){
if (*ppFirst == NULL){
return;
}
Node *cur = *ppFirst;
if (cur->value == v){
*ppFirst = cur->next;
free(cur);
return;
}
while (cur->next != NULL){
if (cur->next->value = v){
Node *next = cur->next;
cur->next = next->next;
free(next);
return;
}
cur = cur->next;
}
}
本文深入探讨链表数据结构的特点,对比顺序表,解析链表的优劣,介绍链表的基本操作如初始化、插入、删除及查找,并提供链表结点的结构体定义和关键算法实现。
523

被折叠的 条评论
为什么被折叠?



