1.空间不够了需要扩容,增容是要付出代价
realloc的机制代价,与身具来的代价,1.后面内存足够开辟就原地扩容,2.后面内存不够就会异地扩容
2.避免频繁扩容,我们满了基本都是扩2倍,可能就会导致一定的空间浪费
3.顺序表要求数据从开始位置连续存储那么我们在头部或者中间位置插入删除数据就需要挪动数据,效率不高
针对顺序表的缺陷,就设计出了链表
链表是按需申请空间,不用了就释放空间(更合理的使用空间)
头部中间尾部插入删除数据,不需要挪动数据
当然他也是有缺陷的,例如每个数据后面都要存一个指针去链接后面的数据节点
不支持随机访问(也就是没有下标访问了)
**概念:**链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构
虽然有那么多的链表的结构,但是我们实际中最常用的还是两种结构
typedef int SLTDataType;
//单链表节点
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
有时候一步一步调试会不怎么方便所以我们先把打印函数写出来,然后再写增删改查也不迟
//单链表打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
只要是插入不管你如何如何反正是插入你都得扩容,当然单链表扩容的单位是节点
//单链表尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
//申请一个新节点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找到尾节点
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
这时我是想写头插,但是思考一番发现,创建节点这一步会和尾插里面有些步骤重复,因此我们可以把重复的步骤抽离出来写成一个函数就会方便一些
//获得单链表节点函数
SLTNode* BuySListNode(SLTDataType x)
{
//申请一个新节点
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)//实际上如果申请失败了,就说明堆没有多少空间了
{
printf("申请失败\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;//然后把节点指针返回出去
}
所以前面那个尾插就可以用这个复写了
//单链表头插函数
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
//申请一个新节点
SLTNode* newnode = BuySListNode(x);
//让新节点里面存plist头结点的地址
newnode->next = *pphead;
//然后再让newnode成为plist头节点
*pphead = newnode;
}
//单链表尾删
void SListPopBack(SLTNode** pphead)
{
assert(*pphead);
if (!(*pphead)->next)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* prev = NULL;//类似一个标记节点指针
SLTNode* tail = *pphead;
while (tail->next)//找到最后一个节点
{
prev = tail;//每次操作tail前让prev先存一下他
tail = tail->next;
}
free(tail);//把他空间释放了
tail = NULL;//然后指向空
//但是前一个next我们没有操作就是指向了原来已经释放掉了的空间
prev->next = NULL;
}
}
//单链表头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
(*pphead) = next;
}
//单链表查找函数
SLTNode* SListFind(SLTNode* phead,SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
主文件的写法
void test1()
{
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 1);
SListPushBack(&plist, 4);
SListPushFront(&plist, 2);
SListPushBack(&plist, 4);
SListPushFront(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 4);
SListPrint(plist);
SLTNode* pos = SListFind(plist,4);
int i = 1;
while (pos)
{
printf("第%d个pos节点,%p->%d\n",i++,pos,pos->data);
//找到后的下一个地址
pos = SListFind(pos->next, 4);
}
}
SLTNode* pos1 = SListFind(plist,2);
if (pos1)
{
//这里就可以看到查询返回节点指针的价值所在了
pos1->data = 20;
}
SListPrint(plist);
可以通过和查询函数配合来进行插入
//单链表插入函数
//在pos前面插入,这个pos在哪里来呢,就是从前面查找函数来
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
//首先创建节点
SLTNode* newnode = BuySListNode(x);
if (pos == *pphead)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
//找到pos的前一个位置
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
看到上面就是知道单链表不太适合在前面插入,而是适合在后面插入,因为前插你要知道当前位置前面的那一个节点位置就会有点复杂,而后插就没有这个prev的节点指针了
//单链表后插函数
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
//创建一个新的节点
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
单链表删除函数
这是我自己写的,我们看下面老师写的
//void SListErase(SLTNode** pphead, SLTNode* pos)
//{
// SLTNode* cur = *pphead;
// SLTNode* prev = NULL;
// while (cur)
// {
// if (cur == pos)
// {
// if (pos == *pphead)
// {
// cur = (*pphead)->next;
// free(*pphead);
// (*pphead) = NULL;
// *pphead = cur;
// }
// else
// {
// prev->next = cur->next;
// free(cur);
// cur = prev->next;
// }
// }
// else
// {
// prev = cur;
// cur = cur->next;
// }
// }
//}
//单链表删除函数
//老师写的
void SListErase(SLTNode** pphead, SLTNode* pos)
{
if (pos == (*pphead))
{
//头删函数
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
当然还有简洁的后删函数
//单链表后删函数
void SListEraseAfter(SLTNode* pos)
{
assert(pos->next);
SLTNode* nextnode = pos->next;
pos->next = nextnode->next;
free(nextnode);
}
//单链表销毁函数
void SListDestory(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* nextnode = cur->next;
free(cur);
cur = nextnode;
}
*pphead = NULL;
}
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* pos = head;
struct ListNode* prev = NULL;
while(pos)
{
if(pos->val == val)
{
if(pos == head)
{
pos = head->next;
free(head);
head = pos;
}
else
{
prev->next = pos->next;
free(pos);
pos = prev->next;
}
}
else
{
prev = pos;
pos = pos->next;
}
}
return head;
}
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL;
struct ListNode* cur = head;
if(!cur)
{
return head;
}
else
{
struct ListNode* next = head->next;
while(next)
{
cur->next = prev;
prev = cur;
cur = next;
next = next->next;
}
cur->next = prev;
return cur;
}
}
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* fast = head;
struct ListNode* slow = head;
while((fast != NULL)&&(fast->next != NULL))
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode* fast = pListHead;
struct ListNode* slow = pListHead;
while(k--)
{
if(!fast)
{
return NULL;
}
fast = fast->next;
}
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode* head = NULL;
struct ListNode* tail = NULL;
if(!l1)
return l2;
if(!l2)
return l1;
while(l1&&l2)
{
if(l1->val <= l2->val)
{
if(!head)
{
head = tail = l1;
}
else
{
tail->next = l1;
tail = l1;
}
l1 = l1->next;
}
else
{
if(!head)
{
head = tail = l2;
}
else
{
tail->next = l2;
tail = l2;
}
l2 = l2->next;
}
}
tail->next = (l1 == NULL) ? l2 : l1;
return head;
}