1、顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储。
2. 动态顺序表:使用动态开辟的数组存储。
在C语言中,我们将用结构体来实现我们的顺序表。
现在让我们来一一实现吧!
顺序表的静态存储
#define N 100 为了方便以后修改顺序表的长度,我们将顺序表的长度作为宏定义.
typedef int SLDataType;
因为顺序表存储的类型是不确定的,因此我们用SLDataType定义类型,如果以后要修改为char类型,
直接改这里就可以了.
下面就是顺序表的实现了.
typedef struct SeqList
{
SLDataType array[N]; 定长数组 静态顺序表使用数组来存放元素
size_t size; 有效数据的个数
}SeqList;
定义好了顺序表,我们就需要完成它所需要的窗口了.
《窗口目录》
初始化线性表
void SeqlistInit(SL* sl);
首插
void SeqlistPushFront(SL* sl, SqDatatype x);
尾插
void SeqlistPushBack(SL* sl, SqDatatype x);
首删
void SeqlistPopFront(SL* sl);
尾删
void SeqlistPopBack(SL* sl);
查找
int SeqlistFind(SL sl, SqDatatype x);
修改
void SeqlistModify(SL* sl, SqDatatype x, int n);
打印
void SeqlistPrint(SL sl);
现在让我们来实现它们吧!
void SeqlistInit(SL* sl)
{
我们要为顺序表中的参数进行定义
memset(sl->a,0,sizeof(SLDataType)*N);
memset的作用就是将数组里面的所有元素赋值为0,
不过不用担心数组里面的元素,因为待会插入的时候,里面的值会被
覆盖,即使没有输入的位置,也不会输出,因此不影响。
sl->size = 0;
现在有效元素个数为0
}
尾插
void SeqlistPushBack(SL* sl, SqDatatype x)
{
assert(sl);
得先判断容量
if(sl->size == N)
{
printf("容量已经满了\n");
return;
}
sl->a[sl->size]=x;
sl->size++;
每插入一个值以后,size就要++,方便下一个数的插入。
}
前插
void SeqlistPushFront(SL* sl, SqDatatype x)
{
前插比尾插稍微复杂一点.
我们不能够直接进行插入,我们得先将之前已经存储的元素向后移
一个位置,再进行插入.
}
假如原来的顺序表已经插入了这些数字,现在要插入一个新的值。
我们应该留出第一个值的位置,再进行插入.
继续完善我们的代码.
插入之前我们得先判断容量是否足够.
if(sl->size == N)
{
printf("容量已经满了\n");
return;
}
else
{
向后挪动,得从最后一个元素开始往后挪
for(int i =sl->size;i>0;i--)
{
sl->a[i]=sl->a[i-1];
}
挪完以后再进行插入.
sl->a[0]=x;
sl->size++;
}
再来实现一个头删
void SeqlistPopFront(SL* sl)
{
assert(sl);
头删的思路在于向前覆盖元素.
如何覆盖是关键.
如果数组中没有元素,就不能删除,直接返回
if(sl->size==0)
{
return;
}
如果数组中只有一个元素,直接删除就行
else if(sl->size==1)
{
sl->size--;
}
如果数组中的元素有两个或两个以上,从第二个元素开始向前覆盖。
else
{
for(int i =0;i<sl->size-1;i++)
{
sl->a[i]=sl->a[i+1];
}
sl->size--;
}
}
尾删比较简单,在这里给大家留个作业.
查找的接口(在这里不考虑数组中有相同元素的情况)
int SeqlistFind(SL sl, SqDatatype x)
{
int i =sl.size-1;
while(sl.a[i]!=x && i>0)
{
i--;
}
if(i!=0)
{
证明找到了这个位置
return i;
}
else
{
没有找到,返回-1;
return -1;
}
}
剩余两个接口,就留给大家了,因为不难,相信大家可以自己完成.
我们现在来看一下 顺序表的动态存储
typedef struct Seqlist
{
SqDatatype* a;
int capacity;
int size;
}SL;
依旧需要完成以下的接口
//初始化线性表
void SeqlistInit(SL* sl);
//首插
void SeqlistPushFront(SL* sl, SqDatatype x);
//尾插
void SeqlistPushBack(SL* sl, SqDatatype x);
//首删
void SeqlistPopFront(SL* sl);
//尾删
void SeqlistPopBack(SL* sl);
//查找
void SeqlistFind(SL sl, SqDatatype x);
//修改
void SeqlistModify(SL* sl, SqDatatype x, int n);
//打印
void SeqlistPrint(SL sl);
void SeqlistInit(SL* sl)
{
为什么是动态存储呢?因为我们需要给他定义空间,并进行扩容
我们可以在初始化的时候,手动定义变量
sl->capacity =2;
sl->a=(SqDatatype*)malloc(sizeof(SqDatatype)*sl->capacity);
sl->size=0;
}
void SeqlistPushBack(SL* sl, SqDatatype x)
{
尾插的时候,同样也要考虑容量的问题,需要我们自行判断一下
if(sl->size ==sl->capacity)
{
满了就需要扩容.
int newcapacity = sl->capacity*2;
SqDatatype* tmp = (SqDatatype*)realloc
(sl->a,newcapacity*sizeof(SqDatatype));
判断是否扩容成功.
if (tmp == NULL)
{
printf("realloc失败了\n");
exit(-1);
}
else
{
sl->a= tmp;
sl->capacity = newcapacity;
}
sl->a[sl->size] = x;
sl->size++;
}
}
再来看看头插
void SeqlistPushFront(SL* sl, SqDatatype x)
{
像刚刚尾插一样,需要判断容量
头插的方式和静态存储是一样的,这里就不再赘述了.
}
剩余的接口,可以参考静态存储。
顺序表存在以下的问题:1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200.
我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
于是提出了链表的概念
2、链表
2.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1.单向、双向
2.带头、不带头
3.循环、非循环
今天主要介绍单向不带头非循环链表。
链表的实现:
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
单链表的尾删
void SListPopBack(SListNode** pplist);
单链表头删
void SListPopFront(SListNode** pplist);
因为链表是一个节点一个节点链接起来的,因此我们将创建新节点的函
数进行封装一下
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
newnode->next =NULL;
newnode->data =x;
可以进行判断一下是否成功创建
if(newnode==NULL)
{
return NULL;
}
else
{
return newnode;
}
}
尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
SListNode* newnode= BuySListNode(SLTDateType x);
先判断*plist是否为空指针,因为plist的类型是 SListNode**。
如果是空指针的话,那么说明链表中没有任何节点。
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(SLTDateType x);
if(*pplist == NULL)
{
*pplist=newnode;
}
else
{
因为传进来的*pplist就是链表的头
newnode->next = *pplist;
*pplist = newnode;
头插结束.
}
}
尾删
void SListPopBack(SListNode** pplist)
{
得先判断*pplist是否为空
if(*pplist == NULL)
{
return;
}
//如果只有一个节点
else if((*pplist)->next ==NULL)
{
free(*pplist);
*pplist=NULL;
}
else
{
要尾删就得找到尾的前一个.
SListNode* tail = *pplist;
SListNode* prev = *pplist;
while(tail->next !=NULL)
{
tail=tail->next;
prev->next =tail;
}
因为链表是动态开辟出来的,因此我们得自己手动释放掉.
free(tail);
然后将尾的下一个指针令为NULL。
prev->next =NULL;
尾删结束
}
}
头删
void SListPopFront(SListNode** pplist)
{
与尾删相同,得先判断*pplist是否为空
if(*pplist == NULL)
{
return;
}
//如果只有一个节点
else if((*pplist)->next ==NULL)
{
free(*pplist);
*pplist=NULL;
}
else
{
先找到头的下一个,不然释放掉头的话,找不到下一个的地址,
无法链接
SListNode* next =(*plist)->next;
free(*plist);
*plist=next;
头删结束
}
}
欢迎大家的观看!如果满意,帮忙点个赞吧!