1.顺序表定义
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表又分为:静态顺序表和动态顺序表。
结构体定义
结构体定义:
//定义变量类型
#define DataType int
#define MAX 100
//顺序表结构声明
静态顺序表
//struct SeqList {
// DataType data[MAX];
// int size;
//};
//动态顺序表
typedef struct SeqList
{
DataType* data;
int size; //顺序表当前大小
int capacity; //容量
}SeqList;
本文主要实现动态结构体
初始化
在使用数据之前,我们需要将数据进行初始化:
//顺序表初始化
void SeqListInit(SeqList* psl)
{
assert(psl);
psl->data = NULL;
psl->capacity = 0;
psl->size = 0;
}
扩容函数
动态顺序表有随时扩容的优点,因此当容量不足时,要能随时扩容
//空间不足,开辟空间
void CheckCapacity(SeqList* psl)
{
//空间满了
if (psl->capacity == psl->size)
{
//首次开辟默认4,以后开辟会多2倍
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
DataType* tem = realloc(psl->data, sizeof(DataType) * newcapacity);
if (tem == NULL)
{
//开辟失败,中止程序
printf("开辟空间失败");
//开辟不足直接中止函数
exit(-1);
}
psl->data = tem;
psl->capacity = newcapacity;
}
}
打印函数
为了方便观察代码的逻辑还写了一个打印函数
//打印顺序表
void SeqListPrint(SeqList* psl)
{
//断言的作用:防止传进来的指针为空!
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->data[i]);
}
printf("\n");
}
尾插和尾删
直接在尾部插入和删除数据。
//尾插
void SeqListPushBack(SeqList* psl, DataType x)
{
//断言的作用:防止传进来的指针为空!
assert(psl);
//空间不够开辟空间
CheckCapacity(psl);
psl->data[psl->size] = x;
psl->size++;
}
//尾删
void SeqListPopBack(SeqList* psl)
{
//断言的作用:防止传进来的指针为空!
assert(psl);
assert(psl->size > 0);
psl->size--;
}
头插和头删
在头部插入和删除数据。
//头插
void SeqListPushFront(SeqList* psl, DataType x)
{
//断言的作用:防止传进来的指针为空!
assert(psl);
CheckCapacity(psl);
//尾坐标
int end = psl->size - 1;
while (end >= 0)
{
psl->data[end + 1] = psl->data[end];
end--;
}
psl->data[0] = x;
psl->size++;
}
//头删
void SeqListPopFront(SeqList* psl)
{
//断言的作用:防止传进来的指针为空!
assert(psl);
assert(psl->size > 0);
int end = 1;
while (end < psl->size)
{
psl->data[end - 1] = psl->data[end];
end++;
}
psl->size--;
}
查找函数
按值查找,查找成功返回下标,查找失败返回-1.
//顺序表查找
int SeqListFind(SeqList* psl, DataType x)
{
//成功找到返回下标
for (int i = 0; i < psl->size; i++)
{
if (psl->data[i] == x)
{
return i;
}
}
//找不到返回-1
return -1;
}
指定位置插入和删除
在指定的位置插入和删除数据
//在pos位置插入
void SeqListInsert(SeqList* psl, int pos, DataType x)
{
//断言的作用防止指针为空,防止传进来的pos非法!
assert(psl);
assert(pos >= 0 && pos <= psl->size);
CheckCapacity(psl);
int end = psl->size - 1;
while (end >= pos)
{
psl->data[end + 1] = psl->data[end];
end--;
}
psl->data[pos] = x;
psl->size++;
}
//在pos位置删除
void SeqListErase(SeqList* psl, int pos)
{
//断言的作用防止指针为空,防止传进来的pos非法!
assert(psl);
assert(pos >= 0 && pos < psl->size);
int end = pos + 1;
while (end < psl->size)
{
psl->data[end - 1] = psl->data[end];
end++;
}
psl->size--;
}
顺序表销毁
在使用完之后,要及时销毁顺序表。
//顺序表销毁
void SeqListDestory(SeqList* psl)
{
assert(psl);
free(psl->data);
psl->data = NULL;
psl->size = psl->capacity = 0;
}
2.单链表定义
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
结构体定义(无头单向非循环链表)
typedef int SLDateType; //数据类型
//无头,单向,非循环链表增删改查
typedef struct SListNode {
SLDateType data;
struct SListNode* next;
}SListNode; //结构体定义
打印函数
//打印函数
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d -> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
申请新节点
//申请一个新节点
SListNode* BuyNewNode(SLDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
printf("申请新结点失败");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
尾插
//尾插
void SListPushBack(SListNode** pphead, SLDateType x)
{
assert(pphead);
//申请新节点
SListNode* newnode = BuyNewNode(x);
//链表为空时
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找到最后一个结点
SListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
//最后一个节点指向新节点
tail->next = newnode;
}
}
头插
//头插
void SListPushFront(SListNode** pphead, SLDateType x)
{
assert(pphead);
SListNode* newnode = BuyNewNode(x);
//新节点指向头指针,然后头指针指向新节点.
newnode->next = *pphead;
(*pphead) = newnode;
}
尾删
//尾删
void SListPopBack(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
//只有一个结点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找到保存尾结点的前一个结点
SListNode* tailPrv = *pphead;
SListNode* tail = *pphead;
while (tail->next != NULL)
{
//保存tail的前一个结点
tailPrv = tail;
tail = tail->next;
}
tailPrv->next = NULL;
free(tail);
tail = NULL; //好习惯
}
}
头删
//头删
void SListPopFront(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
SListNode* del = *pphead;
*pphead = (*pphead)->next;
free(del);
del = NULL;
}
按值查找
//查找
SListNode* SListFind(SListNode* phead, SLDateType x)
{
assert(phead);
//用cur去遍历
SListNode* cur = phead;
while (cur != NULL)
{
if (cur->data == x)
{
//printf("找到啦\n");
return cur;
}
cur = cur->next;
}
//printf("没找到\n");
//买找到返回空指针
return NULL;
}
在pos前面插入(不推荐效率低)
//在pos前面插入
void SListInsert(SListNode** pphead, SListNode* pos, SLDateType x)
{
assert(pos);
assert(pphead);
assert(*pphead);
//第一个位置插入变成头插
if ((*pphead)==pos)
{
SListPushFront(pphead, x);
}
else
{
SListNode* cur = *pphead;
SListNode* newnode = BuyNewNode(x);
while (cur->next != pos)
{
cur = cur->next;
}
newnode->next = pos;
cur->next = newnode;
}
}
删除pos位置(不推荐效率低)
//删除pos位置
void SListEraser(SListNode** pphead, SListNode* pos)
{
assert(pos);
assert(pphead);
assert(*pphead);
//头删
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
在pos后面插入(推荐效率高)
//在pos后面插入
void SListInsertAfter(SListNode** pphead, SListNode* pos, SLDateType x)
{
assert(pos);
assert(pphead);
assert(*pphead);
SListNode* newnode = BuyNewNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
删除pos后的值(推荐效率高)
//删除pos之后的值
void SListEraserAfter(SListNode** pphead, SListNode* pos)
{
assert(pos);
assert(pphead);
assert(*pphead);
assert(pos->next); //保证pos后面必须有元素
SListNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
3.双链表
结构体定义(带头双向循环链表)
这种链表结构非常完美,能在O(1)时间内完成插入删除的任务。
typedef int DataType;
//带头双向循环链表
typedef struct ListNode
{
DataType data; //存储数据
struct ListNode* next; //指向下一结点
struct ListNode* prev; //指向前一结点
}ListNode;
初始化接口
//新型初始化接口(带头循环链表)
ListNode* ListInit()
{
ListNode* newnode = BuyListNode();
newnode->next = newnode;
newnode->prev = newnode;
return newnode;
}
申请新结点
//申请结点
ListNode* BuyListNode()
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
//申请失败,报错终止程序.
perror("mallocc:");
exit(-1);
}
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
打印链表
// 打印链表
void ListPrint(ListNode* phead)
{
assert(phead);
//cur为遍历指针
ListNode* cur = phead->next;
//当cur等于phead时,遍历完毕
while (cur != phead)
{
printf("%d-> ", cur->data);
cur = cur->next;
}
//最后打印空指针
printf("NULL\n");
}
尾插和头插
//尾插
void ListPushBack(ListNode* phead, DataType x)
{
assert(phead);
//申请新节点
ListNode* newnode = BuyListNode();
newnode->data = x;
//找到尾结点
ListNode* tail = phead->prev;
//链接
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
//头插
void ListPushFront(ListNode* phead, DataType x)
{
assert(phead);
//申请新节点
ListNode* newnode = BuyListNode();
newnode->data = x;
//存住第一个结点
ListNode* next = phead->next;
//链接
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
尾删和头删
//尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next!=phead);
//保存尾结点和尾结点的前一个结点
ListNode* tail = phead->prev;
ListNode* PrevTail = tail->prev;
//链接
PrevTail->next = phead;
phead->prev = PrevTail;
//释放尾结点
free(tail);
tail = NULL;
}
//头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
//保存第一个节点和第一个结点的下一个结点
ListNode* cur = phead->next;
ListNode* next = cur->next;
//链接加释放
phead->next = next;
next->prev = phead;
free(cur);
cur = NULL;
}
查找函数
//查找
ListNode* ListFind(ListNode* phead, DataType x)
{
assert(phead);
//cur为遍历指针
ListNode* cur = phead->next;
while (cur != phead)
{
//找到了
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
//没找到
return NULL;
}
在pos前插入
//在pos前面进行插入
void ListInsert(ListNode* pos, DataType x)
{
assert(pos);
//申请新节点
ListNode* newnode = BuyListNode();
newnode->data = x;
//保存前一个结点
ListNode* prevPos = pos->prev;
//链接
prevPos->next = newnode;
newnode->prev = prevPos;
newnode->next = pos;
pos->prev = newnode;
}
删除pos位置的值
//删除pos位置的结点
void ListErase(ListNode* pos)
{
assert(pos);
//保存pos的前一结点
ListNode* prevPos = pos->prev;
//链接加释放
prevPos->next = pos->next;
pos->next->prev = prevPos;
free(pos);
pos = NULL;
}
链表判空
//链表判空
int ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead;
}
链表销毁
//链表销毁
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* tem = cur->next;
free(cur);
cur = tem;
}
free(phead);
phead = NULL;
}
4.顺序表和链表对比
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
5.栈
一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
结构体定义
栈一般用顺序表来实现,因为链表在队尾不易删除元素,本文用动态顺序表实现。
//数据类型命名
typedef int DataType;
//支持动态增长
typedef struct Stack
{
DataType* data;
int top; //栈顶指针
int capacity; //容量
}Stack;
栈初始化
//初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->data = NULL; //指针置空
ps->capacity = ps->top = 0;
}
入栈
//入栈
void StackPush(Stack* ps, DataType x)
{
assert(ps);
//栈满
if (ps->top == ps->capacity)
{
int newcapacity = (ps->capacity == 0 ? 4 : (ps->capacity = (ps->capacity * 2)));
DataType* tem = (DataType*)realloc(ps->data, newcapacity*sizeof(DataType));
if (tem == NULL)
{
perror("realloc Failed:");
exit(-1);
}
ps->data = tem;
ps->capacity = newcapacity;
}
ps->data[ps->top] = x;
ps->top++;
}
出栈
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps)); //栈为空则断言
ps->top--;
}
栈判空
//判空:空返回1,非空返回0
bool StackEmpty(Stack* ps)
{
assert(ps);
//非空
if (ps->top > 0)
{
return false;
}
//空
return true;
}
获取栈顶元素
//获取栈顶元素
DataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->data[ps->top - 1];
}
销毁栈
//销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->data);
ps->data = NULL;
//不是申请的空间不能free
//free(ps);
//ps = NULL;
}
6.队列
只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。
队列结构体定义
因为队列的首尾都需要操作,如果用顺序表实现,出队后对头的空间不易利用的缺点。因此,本文使用单链表来实现队列。
typedef int DataType;
//链式队列
//结点结构体
typedef struct QueueNode
{
DataType data;
struct QueueNode* next;
}QueueNode;
//队列头尾指针
typedef struct Queue
{
struct QueueNode* head;
struct QueueNode* tail;
}Queue;
初始化队列
//初始化队列
void QueueInit(Queue* ps)
{
assert(ps);
//QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
//if (newnode == NULL)
//{
// perror("malloc Failed");
// exit(-1);
//}
//newnode->next = NULL;
//指向空结点
ps->head = NULL;
ps->tail = NULL;
}
入队
//入队
void QueuePush(Queue* ps, DataType x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc Failed");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
//尾插
if (ps->head == NULL)
{
ps->head = ps->tail = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = ps->tail->next;
}
}
出队
//出队
void QueuePop(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
//1.只有一个结点
//2.有多个结点
if (ps->head->next == NULL)
{
free(ps->head);
ps->head = ps->tail = NULL;
}
else
{
//暂存头节点,head指向下一个结点
QueueNode* tem = ps->head;
ps->head = ps->head->next;
free(tem);
}
}
队列判空
//判空
bool QueueEmpty(Queue* ps)
{
assert(ps);
return ps->head == NULL;
}
获取头部元素
//获取头部元素
DataType QueueFront(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->head->data;
}
获取尾部元素
//获取尾部元素
DataType QueueBack(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->tail->data;
}
销毁队列
//销毁队列
void QueueDestroy(Queue* ps)
{
assert(ps);
while (!QueueEmpty(ps))
{
QueuePop(ps);
}
}
以上是线性表、栈和队里欸实现的相关接口,如有问题,恳请大佬们指点!💞