链表和顺序表不同,它的存储是随机的,它的每个结点之间用指针链接,每个节点都存着下一个结点的地址,因此插入删除也比较简单,只需要修改指针就可以完成。
这里我们比较一下顺序表和链表
顺序表:1.插入删除比较麻烦(除了尾插和尾删)
2.可以根据下标来随便访问内容
链表: 1.元素的增删复杂度都是O(1)(双向的情况)
2.不支持随机访问,因为指针只能向前指。
链表的定义
链表的结点
typedef struct ListNode{
int value; //保存的值
struct ListNode *next; //保存下一个结点的地址
} Node;
//表示链表(只需要保存链表的第一个地址)
typedef struct SList{
Node *first; //Node *head;
}SList;//当first==NULL 表示是个空链表
1.链表的初始化
void SListInit(SList * s){
assert(s!=NULL);
s->first = NULL;
}
2.头插
对于链表来说头插是比较简单的,开辟一段空间,然后将空间的value变为要插入的值,然后将它指向原来的第一个结点,将头节点定义成新节点
void SListPushFront(SList *s,SListDataType v)
{
Node *node = (Node *)malloc(sizeof(Node));//先在堆上申请空间
node -> value = v; //新空间的值变成V,指针指向s的头结点
node -> next = s->first;
s->first = node; //头结点变成node的地址
}
3.尾插
链表中的尾插复杂度为O(n),因为要首先找到最后一个结点,才能将它的下一个结点换成新值
void SListPushBack(SList *s SListDataType v)
{
Node *node = (Node *)malloc(sizeof(Node));
node ->value = v;
node->next = NULL;
if(s->first == NULL){
//链表为空
s->first = node;
return;
}
else{
//如何找到最后一个结点(链表中一定有结点)
Node *cur = s->first;
while(cur->next!=NULL){
cur = cur->next
}
cur->next = node;
}
}
4.头删
和头插类似,也是复杂度只有O(1)
//头删
void SListPopFront(SList *s){
assert(s!=NULL); //不能没有链表
assert(s->first !=NULL); //不能没有节点
Node *next = s->first->next;
free(s->first);
s->first=next;
}
5.尾删
//尾删
void SListPopBack(SList *s){
assert(s!=NULL); //不能没有链表
assert(s->first !=NULL); //不能没有节点
if(s->first->next == NULL)
{ //链表中只有一个节点
free(s->first);
s->first = NULL;
return ;
}
Node *cur;
for(cur = s->first;cur->next->next!=NULL;cur = cur->next){
free(cur->next);
cur->next = NULL;
}
}
这里我只总结了单链表,双向链表的话稍微简单一点,就先不总结了。