一.顺序表的优缺点
上一篇博客讲解了顺序表,我们来回顾一下顺序表的优缺点
缺点:
(1). 中间/头部的插入删除,时间复杂度为O(N)
(2). 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
(3). 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
优点:
(1). 可用下标进行随机访问
(2).缓存命中率比较高
在访问内存中的一个数据时,不会只加载一个数据到缓存,而是从这个数据开始加载一段数据到缓存,即预加载操作
由于数组中的数据是连续存储的,因此顺序表缓存的命中率相对于链表更高
二.链表
1.链表的概念和结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接实现的
2.链表的实现
SList.h 文件中功能的声明
// 无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* CreateSListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
说明:
pList为指向第一个结点的指针,ppList为指向 pList 的二级指针
在test.c进行测试时,首先将 pList 置为了空指针
功能实现:
(1). 动态申请一个结点
使用malloc函数分配一个结构体类型大小的空间,放入数据,置空指针
// 动态申请一个结点
SListNode* CreateSListNode(SListDataType x)
{
// 分配空间
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("申请结点失败\n");
exit(-1);
}
// 放入数据
newNode->data = x;
// 置空指针
newNode->next = NULL;
return newNode;
}
(2).单链表打印
// 单链表打印
void SListPrint(SListNode* pList)
{
SListNode* cur = pList;
while (cur != NULL)
{
// 打印结点数据
printf("%d->", cur->data);
// 不断移动cur指针,直到cur指针指向为空
cur = cur->next;
}
printf("NULL\n");
}
(3).单链表尾插
首先动态申请一个结点,调用CreateSListNode函数即可,接着分两种情况讨论
1 . 未插入过数据,此时 pList为空指针,使pList指向我们新开辟的空间即可
2 . 插入过数据,pList不为空,找到尾节点,使尾结点的next指针指向我们新开辟的空间
// 单链表尾插
void SListPushBack(SListNode** ppList, SListDataType x)
{
// 开辟结点
SListNode* newNode = CreateSListNode(x);
// 第一次插入数据
if (*ppList == NULL)
{
*ppList = newNode;
}
// 插入过数据
else
{
SListNode* tail = *ppList;
while (tail->next != NULL)
{
// 找到尾结点
tail = tail->next;
}
// 尾结点的next指针指向新开辟的空间
tail->next = newNode;
}
}
(4).单链表尾删
单链表尾删分为三种情况:
1 . 链表为空,不需要进行操作,直接返回
2 .链表有一个结点,释放掉当前结点,将 pList 指针置为空
3 .链表有两个及以上结点,找到尾结点,释放掉尾结点,并将尾结点之前的结点的指针域置为空
// 单链表尾删
void SListPopBack(SListNode** ppList)
{
// 1.空结点
if (*ppList == NULL)
{
return;
}
// 2.一个结点
else if ((*ppList)->next == NULL)
{
free(*ppList);
*ppList = NULL;
}
// 3.两个及以上结点
else
{
SListNode* prev = NULL;
SListNode* tail = *ppList;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
(5). 单链表头插
头插和尾插同样需要考虑两种情况:
1 .未插入数据,pList指针为空指针,我们新开辟一块结构体类型大小的空间,使其 next 域指向空,最后使 pList 指针指向这块空间
2 .已插入数据,新开辟一块结构体类型大小的空间,使其 next 域指向 pList 所指向的空间 , 最后使 pList 指针指向这块空间
可以看出,这两种情况操作都是一样的。
// 单链表头插
void SListPushFront(SListNode** ppList, SListDataType x)
{
// 链表为空和不为空操作一样
SListNode* newNode = CreateSListNode(x);
newNode->next = *ppList;
*ppList = newNode;
}
(6).单链表头删
头删和尾删同样分为三种情况:
1 .链表为空,不需要进行操作,直接返回即可
2 .链表有一个结点,释放掉该结点,将 pList 指针置为空
3 .链表有两个结点及以上,释放第一个结点,使 pList 指针指向下一个结点
其中2,3情况的操作是一样的
// 单链表头删
void SListPopFront(SListNode** ppList)
{
// 1. 链表为空
if (*ppList == NULL)
{
return;
}
// 2. 链表有一个及以上结点
else
{
SListNode* head = *ppList;
*ppList =(*ppList)->next;
free(head);
}
}
(7).单链表查找
原理很简单 , 只需遍历链表即可
// 单链表查找
SListNode* SListFind(SListNode* pList, SListDataType x)
{
SListNode* cur = pList;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
(8).单链表在pos位置后插入 x
// 单链表在pos位置后插入 x
void SListInsertAfter(SListNode* pos, SListDataType x)
{
assert(pos);
SListNode* newNode = CreateSListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
(9).单链表在pos位置后删除 x
// 单链表在pos位置后删除 x
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next != NULL)
{
SListNode* next = pos->next;
SListNode* nextnext = next->next;
pos->next = nextnext;
free(next);
}
}
三.完整代码实现
SList.c文件
#include"SList.h"
// 动态申请一个结点
SListNode* CreateSListNode(SListDataType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("申请结点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
// 单链表打印
void SListPrint(SListNode* pList)
{
SListNode* cur = pList;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** ppList, SListDataType x)
{
SListNode* newNode = CreateSListNode(x);
if (*ppList == NULL)
{
*ppList = newNode;
}
else
{
SListNode* tail = *ppList;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
// 单链表尾删
void SListPopBack(SListNode** ppList)
{
// 1.空结点
// 2.一个结点
// 3.两个及以上结点
if (*ppList == NULL)
{
return;
}
else if ((*ppList)->next == NULL)
{
free(*ppList);
*ppList = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = *ppList;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
// 单链表头插
void SListPushFront(SListNode** ppList, SListDataType x)
{
// 链表为空和不为空操作一样
SListNode* newNode = CreateSListNode(x);
newNode->next = *ppList;
*ppList = newNode;
}
// 单链表头删
void SListPopFront(SListNode** ppList)
{
// 1. 链表为空
if (*ppList == NULL)
{
return;
}
// 2. 链表有一个及以上结点
else
{
SListNode* head = *ppList;
*ppList =(*ppList)->next;
free(head);
}
}
// 单链表查找
SListNode* SListFind(SListNode* pList, SListDataType x)
{
SListNode* cur = pList;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
// 单链表在pos位置后插入 x
void SListInsertAfter(SListNode* pos, SListDataType x)
{
assert(pos);
SListNode* newNode = CreateSListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
// 单链表在pos位置后删除 x
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if (pos->next != NULL)
{
SListNode* next = pos->next;
SListNode* nextnext = next->next;
pos->next = nextnext;
free(next);
}
}
test.c文件
#include"SList.h"
void TestSList()
{
SListNode* 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);
SListPrint(pList);
SListPushFront(&pList, 5);
SListPushFront(&pList, 4);
SListPushFront(&pList, 3);
SListPushFront(&pList, 2);
SListPushFront(&pList, 1);
SListPrint(pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPopFront(&pList);
SListPrint(pList);
}
int main()
{
TestSList();
}