线性表
线性表是有n个具有相同特性的数据元素的有限序列。线性表是数据结构,常见的线性表有顺序表、链表、栈、队列等等。
顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组的基础上,完成增删查改。顺序表可以分为静态顺序表和动态顺序表。
静态顺序表
静态顺序表使用的是定长数组存储数据元素。
上面的顺序表就是静态顺序表,最多只能存储X=7个数据
动态顺序表
动态顺序表使用动态内存开辟的数组存储数据。
顺序表的实现
静态顺序表适用于已知需要存放具体数据的情况。当我们不知道需要存放多少数据时,使用静态顺序表,空间可能会开辟的很小,空间也可能开辟的很大。现实中,我们通常使用动态的顺序表。下面我们来实现动态的顺序表。
代码如下:
void Seqlistinit(Seqlist* psl)
{
assert(psl);
psl->a = NULL;
psl->b = 0;
psl->capacity = 0;
}
void CheckCapacity(Seqlist* psl)
{
assert(psl);
if (psl->b == psl->capacity)
{
int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
int* tmp = (int*)realloc(psl->a,sizeof(int) * newcapacity);
if (tmp == NULL)
{
perror("malloc");
return;
}
psl->capacity = newcapacity;
psl->a = tmp;
}
}
void PushBack(Seqlist* psl,int x)
{
assert(psl);
CheckCapacity(psl);
psl->a[psl->b] = x;
psl->b++;
}
void PopBack(Seqlist* psl)
{
assert(psl);
psl->b--;
}
void PushFront(Seqlist* psl, int x)
{
assert(psl);
CheckCapacity(psl);
int end = psl->b - 1;
while (end >0&&end==0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->b++;
}
void PopFront(Seqlist* psl)
{
assert(psl);
for (int i = 0;i < psl->b - 1;i++)
{
psl->a[i] = psl->a[i + 1];
}
psl->b--;
}
//pos为下标
void SeqlistInsert(Seqlist* psl, int pos, int x)
{
assert(psl);
assert(pos >= 0 && pos <= psl->b - 1);
CheckCapacity(psl);
int end = psl->b;
while (end>pos)
{
psl->a[end] = psl->a[end - 1];
end--;
}
psl->a[pos] = x;
psl->b++;
}
void SeqlistErase(Seqlist* psl, int pos)
{
assert(psl);
for (int i = pos;i < psl->b - 1;i++)
{
psl->a[i] = psl->a[i + 1];
}
psl->b--;
}
void Seqlistprint(Seqlist* psl)
{
assert(psl);
for (int i = 0; i < psl->b; i++)
{
printf("%d ", psl->a[i]);
}
}
void Seqlistdestory(Seqlist* psl)
{
assert(psl);
free(psl->a);
}
从上面的代码我们可以看到,顺序表在尾插和尾删时比较方便,但是头插和头删时就比较麻烦了。
链表
链表是一种物理存储结构上的非连续、非顺序的存储结构,但是逻辑上是连续的。数据元素的逻辑顺序是通过链表中的指针实现的。链表有单向链表、双向链表、循环链表、非循环链表等等。我们实际中主要是无头单项非循环链表和带头双向循环链表。
无头单项非循环链表
这种链表结构简单,一般不会单独用来存储数据,更多的用于其他数据结构的子结构,比如:哈希桶、图的邻接表等等。
无头单项非循环链表的实现
代码如下:
SLNode* CreateNode(int x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc");
return;
}
newnode->val = x;
newnode->next = NULL;
return newnode;
}
void SLTPushBack(SLNode** pphead, int x)
{
SLNode* node = CreateNode(x);
if (*pphead == NULL)
{
*pphead = node;
}
else
{
SLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = node;
}
}
void SLTPushFront(SLNode** pphead, int x)
{
SLNode* node = CreateNode(x);
if (*pphead == NULL)
{
*pphead = node;
}
else
{
node->next = *pphead;
*pphead = node;
}
}
void SLTPopBack(SLNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
tail->next = NULL;
}
}
void SLTPopFront(SLNode** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLNode* tmp = (*pphead)->next;
free((*pphead));
*pphead =tmp->next;
}
}
void SLTPrint(SLNode** pphead)
{
assert(*pphead);
SLNode* tmp = *pphead;
while ((*pphead)!= NULL)
{
printf("%d->", (*pphead)->val);
(*pphead) = (*pphead)->next;
}
*pphead = tmp;
printf("NULL");
}
void SLTDestroy(SLNode** pphead)
{
assert((*pphead));
free((*pphead));
}
我们发现,对比顺序表,单链表的头插和头删非常的简单,而尾插和尾删因为需要找尾,变得很困难。
带头双向循环链表
结构很复杂,一般用在单独存储数据。实际中使用的数据结构,很多都是带头双向循环链表。虽然结构很复杂,但是使用起来很简单
带头双向循环链表的实现
代码如下:
LTNode* CreateLTNode(int x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return ;
}
newnode->val = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
LTNode* LTInit()
{
LTNode* phead = CreateLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPushBack(LTNode* phead, int x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = CreateLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
void LTPopBack(LTNode* phead)
{
ssert(phead);
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
void LTPrint(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
assert(phead);
printf("<->");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
}
我们发现双链表写起来确实有点难,但是用起来确实是非常好用的捏!