目录
1. 顺序表
1.1 顺序表的存储
1.1.1 顺序表的静态存储
#define N 10000 // 顺序表的最大长度
typedef int SLDataType;
typedef struct SeqList
{
SLDataType a[N]; // 顺序表的元素
int size; // 顺序表的当前长度
}SL;
1.1.2 顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; // 指示动态分配数组的指针
int size; // 顺序表的当前长度
int capacity; // 顺序表的容量
}SL;
1.2 动态顺序表的接口实现
1.2.1 初始化
void SLInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
1.2.2 检查容量
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity) // 如果当前长度==容量,需要扩容
{
// 如果容量为0,则新容量设为4;如果容量不为0,则扩容2倍
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
// 扩容,开辟新空间
SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
// 如果开辟空间失败,打印错误信息并退出程序
if (tmp == NULL)
{
perror("realloc failed");
exit(-1);
}
// 如果开辟空间成功,将临时指针变量的值赋给原指针变量
ps->a = tmp;
// 改变容量的值为新容量
ps->capacity = newCapacity;
}
}
1.2.3 尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps); // 检查容量,满了则扩容
ps->a[ps->size] = x; // 表尾插入元素
ps->size++; // 表的当前长度+1
}
1.2.4 尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--; // 表的当前长度-1
}
1.2.5 头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps); // 检查容量,满了则扩容
for (int i = ps->size; i >= 1; i--) // 所有元素后移
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x; // 表头插入元素
ps->size++; // 表的当前长度+1
}
1.2.6 头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
for (int i = 1; i < ps->size; i++) // 除第一个元素外,所有元素前移
{
ps->a[i - 1] = ps->a[i];
}
ps->size--; // 表的当前长度-1
}
1.2.7 查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x) // 查找第一个x
{
return i; // 查找成功,返回下标
}
}
return -1; // 查找失败,返回-1
}
1.2.8 插入
在下标(不是位序)为pos的位置插入元素
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--) // 下标从pos到size-1的元素后移
{
ps->a[i] = ps->a[i - 1];
}
ps->a[pos] = x; // pos下标的位置插入元素
ps->size++; // 表的当前长度+1
}
1.2.9 删除
删除下标(不是位序)为pos的位置的元素
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos + 1; i < ps->size; i++) // 下标从pos+1到size-1的元素前移
{
ps->a[i - 1] = ps->a[i];
}
ps->size--; // 表的当前长度-1
}
1.2.10 打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
1.2.11 销毁
void SLDestroy(SL* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
}
2. 单链表
以下以无头单向非循环链表为例(头指哨兵位头)
2.1 单链表的结点
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data; // 数据域
struct SListNode* next; // 指针域
}SLTNode;
2.2 单链表的接口实现
2.2.1 申请结点
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); // 动态申请结点
if (newnode == NULL)
{
perror("malloc failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
2.2.2 创建单链表
单链表有n个结点:
SLTNode* CreateSList(int n)
{
SLTNode* phead = NULL;
SLTNode* ptail = NULL;
int x = 0;
for (int i = 0; i < n; i++)
{
scanf("%d", &x); // 输入结点数据域的值
SLTNode* newnode = BuySLTNode(x);
if (phead == NULL)
{
ptail = phead = newnode; // 只有一个结点时,尾结点等于头结点
}
else
{
ptail->next = newnode; // 头结点不变,将新结点链接到原尾结点的尾部
ptail = newnode; // 新结点变成尾结点
}
}
return phead;
}
2.2.3 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
如果形参是一级指针phead,那么当phead为空时,会令phead=newnode,但是对形参的修改不影响实参,所以要修改一级指针phead,形参就应该是二级指针pphead。要修改谁,就传谁的地址。
2.2.4 尾删
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.2.5 头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
2.2.6 头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
2.2.7 查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.2.8 插入
在pos位置之前插入x:
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySLTNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
在pos位置之后插入x:
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
2.2.9 删除
删除pos位置的元素:
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
删除pos位置之后的元素:
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SLTNode* nextNode = pos->next;
pos->next = nextNode->next;
free(nextNode);
}
}
2.2.10 打印
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
2.2.11 销毁
void SLTDestroy(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
3. 带头双向循环链表
3.1 带头双向循环链表的结点
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
3.2 带头双向循环链表的接口实现
3.2.1 申请结点
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc failed");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
3.2.2 初始化
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1); // 申请一个哨兵位头结点
phead->next = phead; // next指向自己
phead->prev = phead; // prev指向自己
return phead;
}
3.2.3 尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
3.2.4 尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead); // 保证链表除了有哨兵位头结点,还有其他结点
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
}
3.2.5 头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
3.2.6 头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
3.2.7 查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
3.2.8 插入
在pos位置之前插入x:
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
3.2.9 删除
删除pos位置的元素:
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
free(pos);
prev->next = next;
next->prev = prev;
}
3.2.10 判空
bool LTEmpty(LTNode* phead)
{
assert(phead);
/*if (phead->next == phead)
{
return true;
}
else
{
return false;
}*/
return phead->next == phead;
}
3.2.11 表长
size_t LTSize(LTNode* phead)
{
assert(phead);
size_t size = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
3.2.12 打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
3.2.13 销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
4. 顺序表和链表的比较
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持 O(1) | 不支持 O(n) |
任意位置插入或删除元素 | 可能需要搬移元素,效率低 O(n) | 只需修改指针指向 |
插入 | 动态顺序表空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |