今天船长计划用简单的方式向大家介绍数据结构中重要知识点——顺序表和链表
全文干货,强烈建议先赞后看哦!话不多说,让我们马上开始今天的学习之旅吧!
目录
1.线性表
线性表(linear list)是n个具有相同特性的数据元素组成的有限序列;
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续,
线性表在物理上存储时,通常以数组和链式结构的形式存储;
线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
2.顺序表
2.1顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表
- 动态顺序表
静态顺序表需要自行指定数组的大小,而动态顺序表具有可以按需分配空间,所以接下来我主要介绍动态顺序表的接口实现
2.2动态顺序表的接口实现
2.2.1头文件的包含
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
2.2.2构造顺序表结构
typedef struct seqlist
{
SLDataType* a;//指向动态数组的指针
int size; //数据个数
int capacity; //内存空间大小,容量
}SL;
2.2.3构造接口
//初始化
void seqlistinit(SL* ps);//初始化,要加分号;
//删除
void SLDestory(SL* ps);
//检查容量
void SLCheckCapacity(SL* ps);
//输出
void SLPrint(SL* ps);
//尾插、尾删、头插、头删
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//某个位置插入
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFine(SL* ps, SLDataType x);
//修改
void SLModifty(SL* ps, int pos, SLDataType x);
2.2.4初始化、删除顺序表
void seqlistinit(SL* ps)//初始化+s(第一种),改为指针为了初始化结构体中的参数
{
ps->a = NULL;
ps->size = ps->capacity = 0;
}
void SLDestory(SL* ps)//删除顺序表
{
free(ps->a);//检查指针问题
ps->a = NULL;
ps->capacity = ps->size = 0;
}
2.2.5检查容量、输出(函数)
void SLCheckCapacity(SL* ps)
{
//检查容量空间,满了就要扩容
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//size_t size这里要输入的是新空间的大小(字节)
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//realloc默认返回void*
if (tmp == NULL)
{
printf("realloc fail\n");
//return;
exit(-1);//退出
}
ps->a = tmp;//将新的拥有空间的指针给ps->a
ps->capacity = newcapacity;
}
}
//输出
void SLPrint(SL* ps)
{
assert(ps != NULL);
for (int i = 0; i < ps->size; i++)
{
printf("%d", ps->a[i]);
printf("\n");
}
}
2.2.6尾插、尾删、头插、头删
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);//这里传指针ps,不要SL*ps
ps->a[ps->size] = x;//把x插入顺序表尾部
ps->size++;//数据个数++
}
//尾删
void SLPopBack(SL* ps)
{
//暴力检查,可以直接查验错误
assert(ps->size > 0);//可能size<0会越界
ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);
int end = ps->size - 1;
//挪动数据
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
}
2.2.7从某个位置插入或删除
//某个位置(pos)插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)//重叠的时候终止
{
ps->a[end + 1] = ps->a[end];
end--;//移动end
}
ps->a[pos] = x;
ps->size++;
}
//删除pos位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos;
while (begin < ps->size - 1)//避免往后越界,因为这里有begin+1
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
2.2.8查找、修改
//查找是否有x
int SLFine(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
return -1;//没有找到
}
}
//修改pos位置数据
void SLModifty(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
3.链表
3.1链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 ;
3.2链表的结构
如下图所示
由链表的性质我们可以得知
- 链表结构的逻辑是连续的,但是物理存储结构不一定连续 ;
- 现实中链表的结点都是在堆中申请出来的;
- 堆上申请的空间是按照一定策略分配的,申请两次的空间可能连续,也可能不连续;
3.3链表的分类
详细的链表分类见:(数据结构)带头双向循环列表的实现 - 2
3.4无头单向链表的接口实现
3.4.1头文件包含
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
3.4.2构造链表结构体
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
3.4.3构造链表接口
//输出
void SListPrint(SLTNode* phead);
//创建新结点
SLTNode* buySlistNode(SLTDataType x);
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
// 在pos位置之前(之后)插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);
3.4.4输出、创建新结点
//输出
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;//指向本结点的下一个结点的地址
}
printf("NULL\n");
}
//创建新结点
SLTNode* buySlistNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//设立一个新的结点
assert(newnode);
newnode->data = x;
newnode->next = NULL;
return newnode;//返回新指针
}
3.4.5尾插、尾删、头插、头删
//尾插
//要改变int* 要传int*的地址 用int**接收
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = buySlistNode(x);
if (*pphead == NULL)//phead为空指针
{
*pphead = newnode;
}
else
{
//找尾结点
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//断言
if ((*pphead)->next == NULL)//这里要注意(*pphead),验证是否只有一个节点
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = buySlistNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;//要先让指针指向next,再释放空间
free(*pphead);
*pphead = next;
}
3.4.6查找
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)//空指针为0,判定为假
{
if (cur->data == x)
return cur;//返回指针,在测试时仍要写额外的语句
cur = cur->next;
}
return NULL;
}
3.4.7在pos之前(之后)插入、删除pos位置的值
//在pos之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//传头部地址、pos位置
{
assert(pos);
assert(pphead);
// 头插
if (pos == *pphead)
{
SListPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
/*SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;*/
// 不在乎链接顺序 - 下列方法(用next指针指向pos->next,防止丢失)
SLTNode* newnode = BuySListNode(x);
SLTNode* next = pos->next;
// pos newnode next
pos->next = newnode;
newnode->next = next;
}
// 删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
4.结语
看到这里,相信老铁们对顺序表和链表的知识已经有了相应的了解。
我是计算机海洋的新进船长Captain_ldx,如果我的文章能对您有帮助的话,麻烦各位观众姥爷们点赞、收藏、关注,你们的每一次举手之劳都将化为船长的前进动力!