使用顺序表存储时的优势就是可以随机访问任意一个数据,因为其底层就是数组实现;
使用链表的优势则是插入、删除等操作非常方便。具体的不同如下表:
1、顺序表
顺序表的底层就是数组,这时用数组存储数据,那么就会有静态数组、动态数组的区别。在实际应用场景中,静态数组比较呆板,在初始化时指定数组的大小之后就不能改变;动态数组就很灵活,在空间不够时可以通过扩容操作从而满足需求,所以动态数组使用的比较多。
顺序表的结构:
typedef struct SqList{
SLDataType* a;
int size;//表示顺序表的大小
int compacity;//表示数组的容量
}SQlist
这里的结构,需要注意SLDataType是为了方便对不同数据类型的顺序表做一个修改,例如整数顺序表、字符顺序表。
顺序表在初始化时需要注意,有两种方式:给一定初始空间和不给初始空间,这两种方式都可以。两种方式对应的扩容方法的书写也就有所差别。下面给一种初始化数组大小为4个SLDataType的方式:
void SLInit(SL* qs)
{
//初始化,给数组的容量设置为4
SLDataType* tmp = (SLDataType*)malloc(4 * sizeof(SLDataType));
if (tmp==NULL)
{
perror("malloc");
return 1;
}
qs->a = tmp;
tmp = NULL;
qs->size = 0;
qs->compacity = 4;
}
这里对传的参数做一个解释。传参分为传值和传地址两种,举这里的顺序表为例。顺序表是一个结构体,其内部有SLDataType指针和两个整数。我们在调用函数时,是为了改变这个结构体,那么就需要传递这个结构体的指针。当然还有另一种方式,就是调用函数返回一个新的结构体,然后在主函数里用一个新的结构体去接收。后一种方式是较为麻烦的,这里就用前一种方式。
其他的对顺序表所做的操作,这里不再多赘述;一般对顺序表有几大操作:初始化和销毁,头插尾插,头删尾删,任意位置插入和删除,判断为空。一般在向一个数据结构中插入数据时要判断是否为满,但这里可以扩容,就变成了判断是否为满,满了之后进行扩容;在一个数据结构中删除一个数据时要判断是否为空。
void dila_sl(SL* qs)
{
if (qs->size == qs->compacity)
{
//当size等于compacity时扩容
SLDataType* tmp = (SLDataType*)realloc(qs->a, (size_t)(2 * (qs->compacity) * sizeof(SLDataType)) );
if (tmp == NULL)
{
perror("realloc");
return 1;
}
qs->a = tmp;
tmp = NULL;
qs->compacity *= 2;
}
}
int SLIsEmpty(SL* qs)
{
if (qs->size==0)
{
return 0;//size=0表示该顺序表为空
}
return 1;
}
void SLDestory(SL* qs)
{
//释放前要判断顺序表是否为空
assert(qs);
qs->size = 0;
qs->compacity = 0;
free(qs->a);
qs->a = NULL;
}
void SLPushBack(SL* qs, SLDataType data)
{
//从尾部插入数据
assert(qs);
//空间是否足够
//不够要进行扩容
dila_sl(qs);
//扩容后空间足够,直接在尾部插入数据
qs->a[qs->size] = data;
(qs->size)++;
}
void SLPushFront(SL* qs, SLDataType data)
{
//从头部插入数据
assert(qs);
//空间是否足够
//不够要进行扩容
dila_sl(qs);
//扩容后在头部插入数据
//从后往前往右移一位
for (int i = qs->size; i > 0; i--)
{
qs->a[i] = qs->a[i - 1];
}
qs->a[0] = data;
(qs->size)++;
}
void SLPopBack(SL* qs)
{
//在尾部删除
assert(qs);
//size的值大于0时进行删除,否则什么也不做
if (SLIsEmpty(qs))
{
qs->size--;
}
}
void SLPopFront(SL* qs)
{
//在头部删除
//size的值大于0时进行删除,否则什么也不做
assert(qs);
if (SLIsEmpty(qs))
{
for (int i = 1; i < qs->size ; i++)
{
qs->a[i - 1]=qs->a[i] ;
}
qs->size--;
}
}
void SLInsert(SL* qs, int index, SLDataType data)
{
assert(qs);
//限制index的范围 index>=0 && index<=size
assert(index >= 0 && index <= qs->size);
//判断需不需要扩容
dila_sl(qs);
//容量足够时直接插入
for (int i = qs->size-1; i >index-1 ; i--)
{
qs->a[i + 1] = qs->a[i];
}
qs->a[index] = data;
qs->size++;
}
void SLDelete(SL* qs, int index)
{
assert(qs);
//限制index的范围 index>=0 && index<=size
assert(index >= 0 && index <= qs->size);
if (SLIsEmpty(qs))
{
//非空才进行删除,否则不做任何事情
for (int i = index; i <qs->size-1 ; i++)
{
qs->a[i] = qs->a[i + 1];
}
qs->size--;
}
}
void SLPrint(SL* qs)
{
for (int i = 0; i < qs->size; i++)
{
printf("%d ", qs->a[i]);
}
printf("\n");
}
2、链表
链表根据连接方式可分为单向链表、双向链表,根据是否带头分为带头链表、不带头链表,根据是否循环分为循环链表、非循环链表,总共2^3=8种链表。最常用的是单向不带头非循环链表和双向带头循环链表,前者主要作为其他数据结构的底层基础使用,例如链式队列,在oj题中没有特殊说明的链表也是单向不带头非循环链表;后者则是在实际应用的过程中使用。
单链表的结构:分为数据域和指针域(这里的指针的声明,不能用SLNode*,因为typedef的名字在结构体声明之后生效,只能用struct SListNode*)
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
} SLNode;
其他的一些操作,也是初始化与销毁、头插尾插、头删尾删、指定位置插入删除等等,这里需要注意的就是指针的改动,一些特殊情况,如首节点为空的情况;其他的如删除需要判断是否为空等和顺序表一致,下面是详细代码:
SLNode* SLNodeCreate(SLDataType data)
{
SLNode* pNode = (SLNode*)malloc(sizeof(SLNode));
pNode->data = data;
pNode->next = NULL;
return pNode;
}
void SLNodePushBack(SLNode** pphead, SLDataType data)
{
SLNode* pNewNode= SLNodeCreate(data);
//处理首节点为空的情况
if (*pphead==NULL)
{
(*pphead) = pNewNode;
return;
}
SLNode* pcur = *pphead;
while (pcur->next!=NULL)
{
pcur = pcur->next;
}
pcur->next = pNewNode;
}
void SLNodePushFront(SLNode** pphead, SLDataType data)
{
SLNode* pNewNode = SLNodeCreate(data);
//处理首节点为空的情况
if (*pphead == NULL)
{
(*pphead) = pNewNode;
return;
}
pNewNode->next = (*pphead);
*pphead = pNewNode;
}
void SLNodePrint(SLNode** pphead)
{
SLNode* pcur = (*pphead);
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
void SLNodeDeleteBack(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
//需要找最后一个节点之前的那个节点
SLNode* pcur = (*pphead)->next;
SLNode* pfor = *pphead;
if (!pcur)
{
//如果pcur为空,说明只有一个节点,直接删除就好了
free(*pphead);
*pphead = NULL;
return;
}
while (pcur->next)
{
pfor = pcur;
pcur = pcur->next;
}
free(pcur);
pcur = NULL;
pfor->next = NULL;
}
void SLNodeDeleteFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
SLNode* pcur = (*pphead);
*pphead = (*pphead)->next;
free(pcur);
pcur = NULL;
}
SLNode* SLNodeFind(SLNode** pphead, SLDataType data)
{
assert(pphead);
assert(*pphead);
SLNode* pcur = *pphead;
while (pcur)
{
if (pcur->data ==data)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
void SLInsertForward(SLNode** pphead, SLNode* pos, SLDataType data)
{
assert(pphead);
assert(*pphead);
if (pos==NULL)
{
SLNodePushBack(pphead,data);
// 就是尾插
return;
}
SLNode* pcur = *pphead;
SLNode* pnewnode = SLNodeCreate(data);
if (pcur==pos)
{
SLNodePushFront(pphead, data);
return;
}
while (pcur->next != pos)
{
pcur = pcur->next;
}
pnewnode->next = pos;
pcur->next = pnewnode;
}
void SLInsertBehind(SLNode** pphead, SLNode* pos, SLDataType data)
{
assert(pphead);
assert(*pphead);
assert(pos);//pos为空,要在空后面添加节点,不合理
/*SLNode* pcur = *pphead;
while (pcur!=pos)
{
pcur = pcur->next;
}*/
SLNode* pnewnode = SLNodeCreate(data);
pnewnode->next = pos->next;
pos->next = pnewnode;
}
void SLTErase(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
SLNode* pcur = *pphead;
if (pcur== pos)
{
SLNodeDeleteFront(pphead);
//第一个元素便是pos所在的元素时,直接头删
return;
}
while (pcur->next!=pos)
{
pcur = pcur->next;
}
pcur->next = pos->next;
free(pos);
pos = NULL;
}
void SLTEraseAfter(SLNode** pphead, SLNode* pos)
{
assert(pphead);
assert(*pphead);
assert(pos);
if (pos->next==NULL)
{
//什么也不做
return;
}
SLNode* pcur = pos->next;
pos->next = pcur->next;
free(pcur);
pcur = NULL;
}
void SLDestory(SLNode** pphead)
{
SLNode* pcur = *pphead;
while (pcur!=NULL)
{
SLNode* ptmp = pcur;
pcur = pcur->next;
free(ptmp);
ptmp = NULL;
}
*pphead = NULL;
}