完整教程:顺序表和链表

目录

1.线性表

2.顺序表

2.1 概念与结构

2.2 分类

2.2.1 静态顺序表

2.2.2 动态顺序表

2.3 动态顺序表的达成

2.4 顺序表算法题

2.4.1 移除元素

2.4.2 删除有序数组中的重复项

2.4.3 合并两个有序数组

2.5 顺序表问题与思考

3.单链表

3.1 概念与结构

3.1.1 结点

3.1.2 链接的性质

3.2 实现单链表

3.3 链表的分类

3.4 单链表算法题

3.4.1 移除链表元素

3.4.2 反转链表

3.4.4 合并两个有序链表

3.4.5 链表分割

3.4.6 链表的回文结构

3.4.7 相交链表

3.4.8 环形链表I

3.4.9 环形链表II

3.4.10 随机链表的复制

4.双向链表

4.1 概念与结构

4.2 实现双向链表

5.顺序表与链表的分析


1.线性表

线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串....

2.顺序表

2.1 概念与结构

概念:顺序表是用一段物理地址连续的存储单元一次存储数据元素的线性结构,一般情况下采用数组存储

顺序表的底层结构是数组,对数组的封装,实现了常用的增删查改等接口

2.2 分类

2.2.1 静态顺序表

概念:使用定长数组存储元素

静态顺序表的缺陷:空间给少了不够用,给多了造成空间浪费

2.2.2 动态顺序表

2.3 动态顺序表的实现

SeqList.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

#define N 1000
typedef int SLDataType;
////静态顺序表
//typedef struct Seqlist
//{
// SLDataType arr[N];
// int size;//有效数据个数
//}SL;
//typedef struct SeqList SL

//动态顺序表
typedef struct SeqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//容量大小
}SL;

//初始化
void SLInit(SL* ps);

//尾插
void SLPushBack(SL* ps, SLDataType x);
//头插
void SLPushFront(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//打印
void SLPrint(SL* ps);
//查找
int SLFind(SL* ps, SLDataType x);
//指定位置之前插入素材
void SLInsert(SL* ps, int pos, SLDataType);
//删除指定位置的数据
void SLErase(SL* ps, int pos);
//销毁
void SLDesTroy(SL* ps);

SeqList.c

#include"SeqList.h"

//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//销毁
void SLDesTroy(SL* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}

void SLcheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//空间不够-2倍增容
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * 2 * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
//温柔的处理方式
if (ps == NULL)
{
return;
}
//粗暴-断言
assert(ps != NULL);
SLcheckCapacity(ps);
ps->arr[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLcheckCapacity(ps);
//直接头插
//数据向后挪动一位
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps && ps->size > 0);
--ps->size;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//查找
int SLFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
//
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLcheckCapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
++ps->size;
}
//删除指定位置数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}

2.4 顺序表算法题

2.4.1 移除元素

https://leetcode.cn/problems/remove-element/description/

2.4.2 删除有序数组中的重复项

https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/

2.4.3 合并两个有序数组

https://leetcode.cn/problems/merge-sorted-array/description/

2.5 顺序表问题与思考

~中间/头部的插入删除,时间复杂度为O(N)

~增容需要申请新空间,拷贝素材,释放旧空间。会有不小的消耗

~增容一般呈2倍的增长,势必会有一定的空间浪费

3.单链表

3.1 概念与结构

经过链表中的指针链接次序搭建的就是概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序

3.1.1 结点

与顺序表不同的是,链表里的每一个小单元都是独立申请下来的空间,我们称之为“结点”

链表中的每个结点都是独立申请的,我们需要利用指针变量来保存下一个结点位置才能从当前结点找到下一个结点

3.1.2 链接的性质

1、链式结构在逻辑上是连续的,但在物理结构上不一定连续

2、结点一般是从堆上申请的

3、从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续

3.2 实现单链表

SList.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;
//定义链表节点的结构
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;


//打印
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** phead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

SList.c

#include"SList.h"

//打印
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}

SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail!\n");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//链表非空,找尾结点
SLTNode* ptail = *pphead;
while (ptail->next)//等价于ptail->!=NULL
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
//链表为空也可以
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);

//只有一个节点的情况
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;

while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//未找到
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//在指定位置之后插入素材
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && pos);
//要删除的结点是头结点
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}

3.3 链表的分类

1.单向还是双向

2.带头或者不带头

3.循环或者不循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表

1.无头单向非循环列表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等

2.带头双向循环链表:结构最复杂,一般单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然困难,然而使用代码实现以后会发现结构会带来很多优势,建立反而简单了,后面我们代码实现了就知道了

3.4 单链表算法题

3.4.1 移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/description/

3.4.2 反转链表

https://leetcode.cn/problems/reverse-linked-list/description/

3.4.4 合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/description/

3.4.5 链表分割

https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70

3.4.6 链表的回文结构

https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

3.4.7 相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

3.4.8 环形链表I

https://leetcode.cn/problems/linked-list-cycle/description/

3.4.9 环形链表II

https://leetcode.cn/problems/linked-list-cycle-ii/description/

3.4.10 随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/description/

4.双向链表

4.1 概念与结构

带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何奏效元素

4.2 实现双向链表

List.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDatatype;
//定义双向链表结构
typedef struct ListNode
{
LTDatatype data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;

//初始化
LTNode* LTInit();
//void LTInit(LTNode** pphead);
// //打印
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDatatype x);
//头插
void LTPushFront(LTNode* phead, LTDatatype x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x);
//在pos位置之后插入信息(和在pos位置之前插入根本是一样的)
void LTInsert(LTNode* pos, LTDatatype x);
//删除pos位置的资料
void LTErase(LTNode* pos);
//销毁
//void LTDesTroy(LTNode** pphead);
void LTDesTroy(LTNode* phead);

List.c

#include"List.h"

LTNode* buyNode(LTDatatype x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
node->data = x;
node->next = node->prev = node;

return node;
}
//初始化
LTNode*LTInit()
{
//创建头结点
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->data = 0;
phead->next = phead->prev = phead;
// LTNode*phead=buyNode(-1);
return phead;
}
//void LTInit(LTNode** pphead)
//{
// assert(pphead);
// *pphead = (LTNode*)malloc(sizeof(LTNode));
// (*pphead)->data = -1;
// (*pphead)->next = (*pphead)->prev = *pphead;
//}

//打印
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
LTNode* newnode = buyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;

phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
assert(phead);
LTNode* newnode = buyNode(x);
newnode->next = phead->next;
newnode->prev = phead;

phead->next->prev = newnode;
phead->next = newnode;
}

bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
del->prev->next = phead;
phead->prev = del->prev;

free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(!LTEmpty(phead));

LTNode* del = phead->next;
del->next->prev = phead;
phead->next = del->next;

free(del);
del = NULL;
}
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{
assert(pos);

LTNode* newnode = buyNode(x);
newnode->next = pos->next;
newnode->prev = pos;

pos->next->prev = newnode;
pos->next = newnode;
}
//删除pos位置的数据
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;

free(pos);
pos = NULL;
}
//销毁
//void LTDesTroy(LTNode** pphead)
//{
// LTNode* pcur = (*pphead)->next;
// while (pcur != pphead)
// {
// LTNode* next = pcur->next;
// free(pcur);
// pcur = next;
// }
// free(*pphead);
// *pphead = NULL;
//
//}
void LTDesTroy(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}

5.顺序表与链表的分析

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,物理上不一定连续
随机访问支持O(1)O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只要求修改指针指向
插入动态顺序表,空间不够时得扩容和空间浪费没有容量的概念,按需申请释放,不存在空间浪费
应用场景元素高效存储+频繁访问任意位置高效插入和删除

posted @ 2025-08-14 10:35  yjbjingcha  阅读(3)  评论(0)    收藏  举报