接上文顺序表的缺点有什么办法解决呢?
前面讲到顺序表的存储结构。顺序表最大的缺点是插入和删除需要移动大量的元素,很耗时间,有什么有好的办法解决呢?
答案是有的,让我们来看看链表的存储结构。
链表的概念及结构
概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序来实现的。
是什么意思呢?元素之间不考虑是否相邻位置了,哪有空位就到哪里,只要让每个元素都知道自己下一个元素的地址在哪就可以了,这样,我们在访问第一个元素的时候就能知道第二个元素的地址,访问第二个元素的时候就能知道第三个元素的地址。以此类推,所有的元素我们都能遍历找到。
各个接口的实现:
typedef int SLTDatatype;
typedef struct SListNode
{
SLTDatatype data;
struct SListNode *next;
}SListNode;
动态申请一个节点
SListNode* BuySListNode(SLTDatatype x)
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
if (node == NULL)
{
printf("malloc fail \n");
exit(-1);
}
node->data = x;
node->next = NULL;
return node;
}
单链表尾插
在链表的最后面插入一个结点,需要考虑2种情况:该链表如果为空怎么处理,如果不为空呢那该如何处理呢?
void SListPushBack(SListNode** plist, SLTDatatype x)
{
assert(plist);
//申请一个结点
SListNode* node = BuySListNode(x);
//链表为空
if (*plist == NULL)
{
*plist= node;
}
else //链表不为空遍历找到最后一个
{
SListNode* cur = *plist;
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = node;
}
}
单链表尾删
删除最后一个结点,有3种情况要考虑:1、链表为空 2、链表只有一个结点 3、链表有多个结点
void SListPopBack(SListNode**plist)
{
assert(plist);
//处理三种情况:1、链表为空 2、 链表中只有一个结点 3、链表有多个结点
if (*plist == NULL)
{
return;
}
else if ((*plist)->next==NULL)
{
free(*plist);
*plist = NULL;
}
else
{
SListNode* tail = *plist;
SListNode*prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
单链表头插
单链表头插相对比较简单,申请一个新结点newNode,newNode->next指向*plist,再把newNode的地址给*plist。
void SListPushFront(SListNode**plist, SLTDatatype x)
{
assert(plist);
//申请新结点
SListNode* newNode=BuySListNode(x);
newNode->next = *plist;
*plist = newNode;
}
单链表头删
头删链表:定义一个指针指向该链表的第二个结点的地址,再free(*plist)。如果先free(*plist)就找不到链表其他的结点了。
void SListPopFront(SListNode**p)
{
assert(p);
//链表为空返回
if (*p == NULL)
{
return;
}
else
{
//记录链表的下一个结点
SListNode* next = (*p)->next;
free(*p);
*p = next;
}
}
单链表查找
简单粗暴遍历查找,找到该结点直接返回其地址,没找到则返回NULL。
SListNode* SListFind(SListNode*p, SLTDatatype x)
{
assert(p);
SListNode* cur = p;
while (cur != NULL)
{
if (cur->data == x)
{
//找到返回其地址
return cur;
}
cur = cur->next;
}
return NULL;
}
单链表pos位置之后插入
申请一个新的结点让这个新的结点指向pos的下一个结点,再让pos指向新结点。这里要注意先后顺序,如果先让pos指向新的结点就找不到下一个结点的地址了。
void SListInsertAfter(SListNode*pos, SLTDatatype x)
{
assert(pos);
//申请新结点
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
单链表pos位置删除
删除pos位置的结点:需要考虑到如果要删除的结点是头结点的话?就相当于是头删了,可以直接调用上面讲到的头删接口。如果要删除的不是头结点,用一个指针cur遍历查找每个结点指向的下一个结点是不是pos结点,是的话就退出循环。退出循环后,cur就是pos结点的前一个结点,因此cur指向pos的下一个结点地址即可,cur->next=pos->next。最后一步free(pos),pos=NULL。
void SListErase(SListNode**p,SListNode* pos)
{
assert(pos);
//pos是头结点相当于头删
if (*p == pos)
{
SListPopFront(p);
}
else
{
SListNode* cur = *p;
while (cur->next != pos)
{
cur = cur->next;
}
//让cur->next指向pos位置的下一个结点。
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
打印单链表
void SListPrint(SListNode* plist)
{
SListNode* cur = p;
while (cur!= NULL)
{
printf("%d ->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
销毁单链表
销毁链表需要逐步把每个结点删除,需要用2个指针来完成。一个cur来遍历链表,另一个指针next来记录cur的下一个结点。这样就不会出现free一个结点就找不到下一个结点的情况。
void SListDestory(SListNode**plist)
{
assert(plist);
SListNode*cur = *plist;
while (cur != NULL)
{
//记录下一个结点的地址
SListNode*next = cur->next;
free(cur);
//把下一个结点地址给cur
cur = next;
}
}
顺序表和链表的区别