文章目录
前言
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的
数据元素的集合。 今天介绍的有两种数据结构顺序表和链表,它们都是属于线性表。
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储 。
顺序表
概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
顺序表一般可以分为:
-
静态顺序表:使用定长数组存储元素。
-
动态顺序表:使用动态开辟的数组存储。
其中静态1顺序表只适用于知道具体的存储数据的多少才能使用,无法扩大容量。所以在实际应用中最多用到的还是动态顺序表。
下面介绍动态顺序表的实现。
动态顺序表的实现
1.定义一个顺序表结构体
代码如下
typedef int SLDataType
typedef struct SeqList
{
SLDataType* array; // 指向动态开辟的数组
size_t sz ; // 有效数据个数
size_t capicity ; // 容量空间的大小
}SL;
从简入繁,从初始化与销毁开始
2.顺序表的初始化
这就不用过多的介绍,指针为空,其他为零就行
void SeqListInit(SL* ps)
{
assert(ps);
ps->a = NULL;
ps->sz = 0;
ps->capacity = 0;
}
3.顺序表的销毁
free掉数组所开辟的空间其他置为0就行代码如下
void DesSeqList(SL* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->sz = 0;
ps->capacity = 0;
}
4.顺序表的增删查改
4.1删
删除又分为头删与尾删,顺序表的尾删很简单只需要把数组的大小减一就行
即
void PopbackSeqList(SL* ps)
{
assert(ps);
assert(ps->sz > 0);
ps->sz--;
}
头删的话我们用后面的数据覆盖住前面的数据然后sz–就可以了
void PopfrontSeqList(SL* ps)
{
assert(ps);
assert(ps->sz > 0);
for (int i =1; i<ps->sz;i++)
{
ps->a[i - 1] = ps->a[i];
}
ps->sz--;
}
也可以删除任一位置的数据只需将这个数据之后的数据向前覆盖即可。
void SeqListErase(SL* ps, int pos)
{
assert(ps);
assert(pos > 0);
for (int i = pos; i < ps->sz - 1; i++)
{
ps->a[pos - 1] = ps->a[pos];
}
ps->sz--;
}
4.2增
在加入新的数据之前我们得先检查数组开辟的空间够不够因此我们可以写一个函数来做到一点,防止越界访问。如果空间不够这个函数可以直接帮数组扩容,即
void Checkcapacity(SL* ps)
{
assert(ps);
if (ps->sz == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newCapacity;
}
}
注意这里我们每次扩容时都是两倍,也可以是其他倍数,都是为了效率与节省空间这里选择两倍。
同样数据的插入也分为头插与尾插。
头插,如果数组是空的直接插入就行,如果不为空则可以先把其他数据往后移动(从后往前挪动)再插入新的数据。
代码如下
}void PushfrontSeqList(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);
for (int i = ps->sz; i>0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->sz++;
}
顺序表的尾插非常简单
void PushbackSeqList(SL* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps, ps->capacity);
ps->a[ps->sz] = x;
ps->sz++;
}
当然也可以在任意位置插入,与头插类似,在插入的位置的数据向后移动,这个位置之前的数据不用动。再在这个位置插入新的数据
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos > 0);
Checkcapacity(ps);
for (int i = ps->sz - 1; i >= pos - 1; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->a[pos - 1] = x;
ps->sz++;
}
4.3查
顺序表查某个数据
只需要遍历数组查找就行,查到之后返回这个数的下标方便访问
int SeqListFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->sz; i++)
{
if (ps->a[i] == x)
return i;
}
printf("找不到这个数据\n");
}
链表
1.链表的概念
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
注意
- 从图上可以看出链表在物理上是不一定连续的,而逻辑上是连续的
- 链表上的结点一般是从堆中申请过来的
- 从堆上申请的空间按照一定的策略分配可能是连续的也可能是不连续的
2.链表的分类
实际中的链表是多种多样的如
1.单向或双向
2.带头或者不带头
3.循环或非循环
但是实际应用中最常用的还是这两种
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
-
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了 。
这里给大家介绍单链表的实现
3.单链表的实现
3.1单链表的定义及结点的创建
单链表的每个结点都应包括一个数据和下一个指向下一个结点的指针具体代码如下
定义
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
创建一个结点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* plist = (SListNode*)malloc(sizeof(SListNode));
if (plist== NULL)
{
perror("malloc fail");
}
else {
plist->data = x;
plist->next = NULL;
return plist;
}
}
3.2单链表的增删查改
尾插我们需要找到单链表的尾部结点,在这个结点之后创立一个结点就可以
即
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode*newnode=BuySListNode(x);
if (*pplist == NULL)
(*pplist)= newnode;
else
{
SListNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
头插只需要将头结点更换即可
具体代码
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
尾删于尾插一样我们需要找到尾,将尾部结点删除
void SListPopBack(SListNode** pplist)
{
assert(*pplist);
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else {
SListNode* tail = *pplist;
//3 2
while (tail->next->next!= NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
头删只需要注意链表为不为空就行
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
SListNode* tmp = (*pplist)->next;
free(*pplist);
*pplist = tmp;
}
查找数据只需要从头结点开始遍历链表即可
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
在某个结点之后插入数据我们只需遍历单链表找个这个位置进行插入即可
void SListInsertAfter(SListNode*plist,SListNode* pos, SLTDateType x)
{
/*assert(*plist);*/
assert(pos);
assert(plist);
SListNode* cur = plist;
while (cur)
{
if (cur == pos)
{
SListNode* newnode = BuySListNode(x);
SListNode* tmp = cur->next;
cur->next = newnode;
newnode->next = tmp;
return;
}
else
{
cur = cur->next;
}
}
}
删除某个位置之后的数据也是如此
void SListEraseAfter(SListNode* plist, SListNode* pos)
{
SListNode* cur = plist;
while (cur)
{
if (cur == pos)
{
SListNode* tmp = cur->next->next;
free(cur->next);
cur->next = tmp;
return;
}
else
{
cur = cur -> next;
}
}
}
3.3单链表的销毁与判空
比较好理解直接上代码
销毁
void SListDestroy(SListNode** plist)
{
SListNode* cur = *plist;
while (cur)
{
*plist = (*plist)->next;
free(cur);
cur = *plist;
}
}
判空
int SListEmpty(SListNode* plist)
{
return plist == NULL;
}
#### 3.3单链表的销毁与判空
比较好理解直接上代码
销毁
```c
void SListDestroy(SListNode** plist)
{
SListNode* cur = *plist;
while (cur)
{
*plist = (*plist)->next;
free(cur);
cur = *plist;
}
}
判空
int SListEmpty(SListNode* plist)
{
return plist == NULL;
}
总结
这次主要介绍了两种线性表顺序表与链表,以及实现了动态顺序表已经单链表,后续会介绍更多的数据结构。