目录
问题:1. 中间 / 头部的插入删除,时间复杂度为 O(N)2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。3. 增容一般是呈 2 倍的增长,势必会有一定的空间浪费。例如当前容量为 100 ,满了以后增容到200,我们再继续插入了 5 个数据,后面没有数据插入了,那么就浪费了 95 个数据空间。
因此为了弥补这一缺点引入了单链表的形式 -- 可以一定程度上解决顺序表的问题
下面给出了链表的结构

实际上是地址指向 -- 这里的箭头是为了方便理解,实际并不存在

添加一个数据就开一点空间

- #pragma once
- #include
- #include
- #include
-
- typedef int SLTDataType;
-
- typedef struct SListNode
- {
- SLTDataType data;
- struct SListNode* next; //利用 next 来指向下一个节点
- }SLTNode;
-
- //创立一个节点(开辟新空间) -- 返回开辟节点的地址
- SLTNode* BuySLTNode(SLTDataType x);
-
- //打印
- void SListPrint(SLTNode *phead);
-
- //头插
- void SListPushFront(SLTNode **phead,SLTDataType x);
-
- //尾插
- void SListPushBack(SLTNode** phead, SLTDataType x);
-
- //头删
- void SListPopFront(SLTNode** pphead);
-
- //尾删
- void SListPopBack(SLTNode** pphead);
-
- //链表的查找
- SLTNode* SListFind(SLTNode* phead, SLTDataType x);
-
- //某个位置插入 -- 这里就不再用下标了 -- 默认在pos之前插入 -- 因为库函数一般都是如此,当然后插要简单的多
- void SListInsert(SLTNode** pphead, SLTNode *pos, SLTDataType x);
-
- //某个位置插入(在节点后面插入) -- 这个容易实现
- void SListInsertAfter(SLTNode* pos, SLTDataType x);
-
- //某个位置删除pos -- pos当前位置删除
- void SListErase(SLTNode** pphead, SLTNode* pos);
-
- //某个位置删除pos -- pos后面位置删除
- void SListEraseAfter(SLTNode** pphead, SLTNode* pos);
-
- //销毁
- void SListDestory(SLTNode** pphead);
- #include "SList.h"
-
- //打印
- void SListPrint(SLTNode* phead)
- {
- SLTNode* cur = phead;
-
- while (cur != NULL)
- {
- printf("%d->", cur->data);
- cur = cur->next; //把下一个节点的地址给cur -- 往下一个节点走
- }
- printf("NULL\n");
- }
-
- //创立一个节点(开辟新空间)
- SLTNode* BuySLTNode(SLTDataType x)
- {
- SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
- newnode->data = x;
- newnode->next = NULL;
-
- return newnode;
-
- }
-
-
- //头插
- void SListPushFront(SLTNode** pphead, SLTDataType x)
- {
- assert(pphead); //pphead 是plist的地址是不可能为空的,所以如果pphead是空说明有问题出现了
-
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead; //这里得使用二级指针,因为改的是phead的指向,而不是改它所指向的值
- *pphead = newnode; //更新头
- }
-
-
- //尾插 -- 先要找到尾部
- void SListPushBack(SLTNode** phead, SLTDataType x)
- {
- assert(phead);
-
- SLTNode* newnode = BuySLTNode(x);
- //有两种情况:第一种为空、第二种不为空
- //第一种情况要改变plist,所以要使用二级指针
- //第二种情况只要找到next就行了,一级指针可以实现
- //链表为空:插入第一个节点,要改变SListNode*
- //链表不为空:尾插,要改变结构体SListNode(里面的next)
- //1、空
- if (*phead == NULL)
- {
- *phead = newnode;
- }
- else//2、不为空
- {
- SLTNode* tail = *phead;
- while (tail->next != NULL) //找到尾部
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
-
- }
-
-
- //头删
- void SListPopFront(SLTNode** pphead)
- {
- assert(pphead); //二级指针一定不为空,因为是一级指针的地址,如果为空就报错了
-
- //温柔的检查
- //if (*pphead == NULL)
- //{
- // return;
- //}
-
- //正常删除不会有问题,但是当删多了就会崩溃掉,所以要检查*pphead(就是plist)是不是空,是空就不可以删除了
- //暴力的检查 -- 库里面常用
- assert(*pphead != NULL);
-
- SLTNode* del = *pphead;
- *pphead = (*pphead)->next; //这里要括号,因为优先级是一样的
- free(del);
- del = NULL;
-
-
- }
-
-
- //尾删
- //如果直接找尾,free掉空间,就相当与野指针了,上一个节点还是指向这个空间,但是里面的值都变成随机值了 -- 不合适
- //正确方式应该找到前一个节点
- //两种写法
- //第一种:创立两个指针,一前一后走
- void SListPopBack(SLTNode** pphead)
- {
- assert(pphead);
-
- //暴力的检查 -- 同样的也需要检查 -- 不可以删多
- assert(*pphead != NULL);
-
- //只有一共节点的时候
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else//多个节点的时候
- {
- //第一种:创立两个指针,一前一后走
- //assert(pphead);
- //SLTNode* prev = *pphead;
- //SLTNode* tail = *pphead;
- //while (tail->next != NULL)
- //{
- // prev = tail;
- // tail = tail->next;
- //}
- //prev->next = NULL;
- //free(tail); //要改变结构体就要用结构体的指针
- //tail = NULL;
-
-
- //第二种:直接找
- SLTNode* tail = *pphead;
- while (tail->next->next != NULL)//但是当只有一个节点的时候就有问题了,上面的方法一样,所以要单独拿出来
- {
- tail = tail->next;
- }
-
- free(tail->next);
- tail->next = NULL;
-
- }
-
- }
-
- //找到某一个节点
- SLTNode* SListFind(SLTNode* phead, SLTDataType x)
- {
- SLTNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
- //不等于就找下一个节点
- cur = cur->next;
- }
- //没找到就返回NULL
- return NULL;
- }
-
- //某个位置插入(前插) -- 这里就不再用下标了 -- 默认在pos之前插入
- void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
- {
- assert(pphead);
- assert(pos);
-
- //当只有一个节点的时候就会出问题 -- 因为会找不到
- //这种情况相当于头插了
- if (pos == *pphead)
- {
- SListPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
-
- //暴力的检查 -- 同样的也需要检查
- assert(prev);
- }
- SLTNode* newnode = BuySLTNode(x);
- prev->next = newnode;
- newnode->next = pos;
- }
-
- }
-
- //某个位置插入(后插) -- 这个容易实现
- void SListInsertAfter(SLTNode* pos, SLTDataType x)
- {
- assert(pos);
-
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next; //注意顺序,不然就找不到下一个了
- pos->next = newnode;
-
- }
-
- //某个位置删除pos -- pos当前位置删除
- void SListErase(SLTNode** pphead, SLTNode* pos)
- {
- assert(pphead);
- assert(pos); //理论上pos不应该为空
-
- if (*pphead == pos) //只有一个节点,相当于头删
- {
- SListPopFront(pphead); //直接调用头删
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos )
- {
- prev = prev->next;
- //检查pos是不是链表中的节点
- assert(prev);
- }
- prev->next = pos->next;
- free(pos);
- //pos = NULL; -- 这里置空并没有用,因为出去的时候就会销毁掉,要使用二级指针,不过不要这样做,要改变很多,而且没必要
- //这种就和free一样 -- free并不会置空指针,需要使用者置空
- }
-
-
- }
-
-
-
- //某个位置删除pos -- pos后面位置删除
- void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
- {
- assert(pos);
-
- if (pos->next == NULL) //如果没有就直接返回就可以了
- {
- return;
- }
- else
- {
- SLTNode* next = pos->next;
- pos->next = next->next;
- free(next);
- }
-
- }
-
-
- //销毁
- //销毁的时候先要保存下一个节点的地址,不然free掉节点就找不到下一个节点了
- void SListDestory(SLTNode** pphead) //里面致空不影响外面的plist,应该使用二级指针
- {
- assert(pphead);
-
- SLTNode* cur = *pphead;
-
- while (cur)
- {
- SLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
-
- *pphead = NULL; //置空
- }
- #include "SList.h"
-
- //头插、头删测试
- void TestSList1()
- {
- SLTNode* plist = NULL; //改什么值就要传什么的地址(局部是不行的)
- SListPushFront(&plist, 1); //得传地址,因为要改结构体指针,所以要传结构体指针的地址
- SListPushFront(&plist, 2);
- SListPushFront(&plist, 3);
- SListPushFront(&plist, 4);
- SListPrint(plist);
-
- SListPopFront(&plist); //删四次
- SListPrint(plist);
- SListPopFront(&plist);
- SListPrint(plist);
- SListPopFront(&plist);
- SListPrint(plist);
- SListPopFront(&plist);
- SListPrint(plist);
-
- //销毁
- SListDestory(&plist);
- }
-
-
- //尾插、尾删测试
- void TestSList2()
- {
-
- SLTNode* plist = NULL;
- SListPushBack(&plist, 1);
- SListPushBack(&plist, 2);
- SListPushBack(&plist, 3);
- SListPushBack(&plist, 4);
- SListPrint(plist);
-
-
- SListPopBack(&plist); //删四次
- SListPrint(plist);
- SListPopBack(&plist);
- SListPrint(plist);
- SListPopBack(&plist);
- SListPrint(plist);
- SListPopBack(&plist);
- SListPrint(plist);
-
- //SListPopBack(&plist); //多删一次
- //SListPrint(plist);
- //销毁
- SListDestory(&plist);
-
- }
-
-
- //销毁、查找测试
- TestSList3()
- {
- SLTNode* plist = NULL;
- SListPushBack(&plist, 1);
- SListPushBack(&plist, 2);
- SListPushBack(&plist, 3);
- SListPushBack(&plist, 4);
- SListPrint(plist);
-
- SLTNode* pos = SListFind(plist, 3);
-
- if (pos)
- {
- printf("找到了\n");
- //查找到就可以修改了,因为返回的是指针,可以直接修改
- pos->data *= 10;
- }
- else
- {
- printf("没找到\n");
- }
- SListPrint(plist);
-
- pos = SListFind(plist, 1); //在1前面插就相当与头插了
- if (pos)
- {
- SListInsert(&plist, pos, 30);
- }
- SListPrint(plist);
-
- SListDestory(&plist);
-
- }
-
- //某个位置的删除测试
- void TestSList4()
- {
-
- SLTNode* plist = NULL;
- SListPushBack(&plist, 1);
- SListPushBack(&plist, 2);
- SListPushBack(&plist, 3);
- SListPushBack(&plist, 4);
- SListPrint(plist);
-
- //删除某一个
- SLTNode* pos = SListFind(plist, 4);
- if (pos)
- {
- SListErase(&plist, pos);
- //这里要注意pos的野指针问题 -- pos节点的地址是不变的,但是里面的值变成随机值了,所以要置空
- pos = NULL;
- }
- SListPrint(plist);
-
- //删头
- pos = SListFind(plist, 1);
- if (pos)
- {
- SListErase(&plist, pos);
- }
- SListPrint(plist);
-
- }
-
-
- //单链表并不会很好的解决顺序表的所以问题,单链表适合头部的操作,删除、插入之类的 -- 时间复杂度达到了O(1)
- //如果要任意高效插入删除需要 -- 用到双向链表
- //单链表开始是不需要初始化的 -- 开始置空就行了
- int main()
- {
- //TestSList1();
- //TestSList2();
- //TestSList3();
- TestSList4();
-
-
- return 0;
- }
数据结构是不需要写什么菜单之类的,不过这个单链表还是有些问题无法得到很好的解决,于是就又有了进一步的链表,双向链表
枝上柳绵吹又少,天涯何处无芳草。
宋·苏轼 《蝶恋花·春景》