赞
踩
虽然顺序具有方便下标随机访问(因为是连续物理空间)的优点,但是也具有一定的缺陷,
如:
1. 插入数据,空间不足时要扩容,但是扩容有性能消耗
2. 头部或者中间位置插入删除数据,需要挪动数据,效率比较低
3. 空间扩容太大,可能存在一定空间占用,浪费空间,不能按需申请和释放空间
由于这些缺陷,链表诞生了
那么链表是什么?
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。
从上图可看出:
1. 链式结构在逻辑上是连续的,但是在物理上不一定连续
2. 现实中的结点一般都是从堆上申请出来的
3. 从堆上申请的空间,是按照一定的此略来分配的,两次申请的空间可能连续,也可能不连续
链表分类:
单向或者双向
带头或者不带头
循环或者非循环
常用的结构:
无头单向非循环链表
结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在面试笔试中出现很多。
带头双向循环链表
结构最复杂,一般用在单独处处数据,实际中使用的链表数据结构,都是带头双向循环链表。另外整个结构虽然结构复杂,但是使用代码实现以后会发现会带来很多优势,实现反而简单了
这里实现的是无头单向非循环链表
将要完成接口有:
- //创建结点
- SListNode* BuySListNode(SListDateType x);
-
- //打印链表
- void PrintSList(SListNode* phead);
-
- //尾插
- void SListPushBack(SListNode** pphead, SListDateType x);
-
- //头插
- void SListPushFornt(SListNode** pphead, SListDateType x);
-
- //尾删
- void SListPopBack(SListNode** pphead);
-
- //头删
- void SListPopFront(SListNode** pphead);
-
- //查找指定值
- SListNode* SListFind(SListNode* phead, SListDateType x);
-
- //在指定位置前插入
- void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDateType x);
-
- //在指定位置后插入
- void SListInsertAfter(SListNode* pos, SListDateType x);
-
- //删除指定位置
- void SListErase(SListNode** pphead, SListNode* pos);
-
- //删除指定位置后结点
- void SListEraseAfter(SListNode* pos);
-
- //销毁链表
- void SListDestroy(SListNode** pphead);
注:无头单向非循环链表需要理解二级指针传参,以及链表为空和不存在与顺序表的为空和不存在
定义的结构体结点:
- typedef int SListDateType;//方便类型灵活变换
-
- typedef struct SListNode
- {
- int date;//数据
- struct SListNode* next;//存放下一个结点的地址
- }SListNode;
创建新结点:
这里需要注意,由于链表的特点,我们操作结构体指针,而不是操作结构体,这一点与顺序表不同,所以我们要做的不是初始化,而是要写一个创建新结点的接口函数,当结点创建好就把数据放入
- //创建新结点
- SListNode* BuySListNode(SListDateType x)
- {
- //开辟一块动态内存
- SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
- //开辟失败终止程序
- if (newnode == NULL)
- {
- printf("Fail\n");
- exit(-1);
- }
- else
- {
- newnode->date = x;//将x放入新开辟date
- newnode->next = NULL;//开辟空间后不知道指向哪,先置空
- }
- //将开辟空间返回
- return newnode;
- }
尾部插入结点:
这里需要注意
由于尾插可能会改变第一个结点(链表为空),既然第一个结点会被改变,就应该传地址,而传过来的应该是一个一级指针指向链表的第一个结点,所以这里应该用二级指针接收
尽管传过来的一级指针指向的链表可能为空(即这个一级指针被赋为空),这也只是说明链表为空,但是存在这个链表,但是pphead作为应该二级指针指向传过来的一级指针,所以pphead里存放了这个一级指针的地址,既然有这个一级指针,那么这个一级指针便有地址存放一级指针,所以pphead不可能为空,否则就不是链表为空,那是链表不存在了,这是非法的情况
当链表为空需要分开讨论,直接将新创建的结点赋值
其他情况如下:
- void SListPushBack(SListNode** pphead, SListDateType x)
- {
- assert(pphead);//pphead不能为空
- //传x并接收创建的结点
- SListNode* newnode = BuySListNode(x);
- //如果传的是NULL,说明是空链表,将开辟的空间直接赋给空的链表
- if (*pphead == NULL)
- {
- //当链表尾空,将第一次新开的结点赋值,则*pphead存放的则始终是第一个结点
- *pphead = newnode;
- }
- //如果传的不是NULL,说明不是空链表,原来的链表有结点
- else
- {
- //找尾巴,将头结点给找尾结点
- SListNode* tail = *pphead;
- //当tail中的next等于NULL,说明其就是结尾的结点
- while (tail->next != NULL)
- {
- //找尾结点,不断的将下一个结点的地址给tail,直到下一个结点的地址是NULL
- tail = tail->next;
- }
- //当tail->next==NULL,将新开的结点赋值,替换NULL
- tail->next = newnode;
- }
- }
打印链表:
由于是打印,第一个结点也不会被改变,所以采用传值,即传一级指针的值,就用一级指针接收
- void PrintSList(SListNode* phead)
- {
- SListNode* cur = phead;//将头结点赋值给现结点
- //使用现结点遍历,cur->next==NULL是链表结束的标志
- while (cur != NULL)
- {
- printf("%d->", cur->date);
- cur = cur->next;
- }
- printf("NULL\n");
- }
头部插入结点:
这里头部插入和尾部插入同理,
插入情况如下:
- void SListPushFornt(SListNode** pphead, SListDateType x)
- {
- assert(pphead);//pphead不能为空
- //接收创建的新结点
- SListNode* newnode = BuySListNode(x);
- //将首结点地址给新建结点的next
- newnode->next = *pphead;
- //将新建结点的地址放首结点
- *pphead = newnode;
- }
尾部删除结点:
尾删和前面的尾插同理,还要注意链表为空以及链表只有一个结点的两种特殊情况,得分开情况讨论,因为prev不能为空,所以分情况讨论
没有结点无法删除
只有一个结点则直接删除
其他情况如下:
- void SListPopBack(SListNode** pphead)
- {
- assert(pphead);//链表必须存在
-
- //空链表直接返回
- if (*pphead == NULL)
- {
- return;
- }
- //链表只有一个结点,直接释放
- else if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- //链表大于一个结点
- else
- {
- //采用双指针做法
- SListNode* tail = *pphead;
- SListNode* prev = NULL;
- //找尾
- while (tail->next != NULL)
- {
- //保留前一个结点的地址
- prev = tail;
- tail = tail->next;
- }
- //释放尾结点并置空
- free(tail);
- tail = NULL;
- //前一个结点的next置空
- prev->next = NULL;
- /*
- 两次解引用的做法
- while (tail->next->next != NULL)
- {
- tail = tail->next;
- }
- free(tail->next);
- tail->next = NULL;
- */
- }
-
- }
头部删除结点:
与尾插同理,还要链表不能为空,为空无法删除
- void SListPopFront(SListNode** pphead)
- {
- assert(pphead);//链表必须存在
-
- //链表为空
- if (*pphead == NULL)
- {
- return;
- }
- //链表不为空
- else
- {
- //存放第二个结点的地址
- SListNode* next = (*pphead)->next;
- //释放第一个结点
- free(*pphead);
- //指向第二个结点
- *pphead = next;
- }
- }
查找指定的值:
由于是查找,不改第一个结点,所以传值,用一级指针接收
- SListNode* SListFind(SListNode* phead, SListDateType x)
- {
- //遍历链表的现指针
- SListNode* cur = phead;
-
- //查找x
- while (cur)
- {
- if (cur->date == x)
- {
- return cur;
- }
- cur = cur->next;
- }
-
- //找不到
- return NULL;
- }
在指定位置前插入:
与尾插同理,第一个结点可能改变,而且待插位置不能为空,这里需要分情况讨论,
当插入的结点是第一个结点时,正好是头插,因为prev不能为空,所以单独讨论
当插入的结点不是第一个时:
- void SListInsertBefore(SListNode** pphead, SListNode* pos, SListDateType x)
- {
- assert(pphead);//链表必须存在
- assert(pos);//插入的位置不能为空
-
- //当插入的结点是第一个时
- if (pos == *pphead)
- {
- SListPushFornt(pphead, x);
- }
- //当插入的结点不是第一个时
- else
- {
- //从第一个结点开始遍历
- SListNode* prev = *pphead;
-
- //查找pos位置
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- //创建新的结点
- SListNode* newnode = BuySListNode(x);
-
- //插入
- prev->next = newnode;
- newnode->next = pos;
- }
- }
在指定位置后插入:
这种情况第一个不可能被改变,所以传值就行,使用一级指针接收,以及待插位置不能为空
- void SListInsertAfter(SListNode* pos, SListDateType x)
- {
- assert(pos);//插入的位置不能为空
-
- //创建结点
- SListNode* newnode = BuySListNode(x);
-
- //插入
- newnode->next = pos->next;
- pos->next = newnode;
- }
删除指定位置的结点:
有可能删除第一个结点,二级指针接收,位置不能为空
需要分情况讨论:
当删除的结点是第一个结点时,也就是头删,需要单独讨论,因为prev不能为空
其他情况如下:
- void SListErase(SListNode** pphead, SListNode* pos)
- {
- assert(pphead);//链表必须存在
- assert(pos);//查找的位置不能为空
-
- //当删除的位置是第一个结点时
- if (*pphead == pos)
- {
- SListPopFront(pphead);
- }
- //当删除的位置是不是第一个结点时
- else
- {
- //从第一个结点开始遍历
- SListNode* prev = *pphead;
-
- //查找pos
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- //删除结点
- prev->next = pos->next;
- free(pos);
- pos = NULL;
- }
-
- }
删除指定位置后的结点:
不可能删除第一个结点,所以传值,一级指针接收,位置不能为空
- void SListEraseAfter(SListNode* pos)
- {
- assert(pos);//pos不能为空
-
- //存放待删除的结点
- SListNode* next = pos->next;
-
- //pos不是最后一个结点,也就是next不能为空
- if (next)
- {
- //删除结点
- pos->next = next->next;
- free(next);
- next = NULL;
- }
- }
销毁链表:
第一个结点会被改变,传地址,二级指针接收
- void SListDestroy(SListNode** pphead)
- {
- assert(pphead);//链表得存在
-
- SListNode* cur = *pphead;
-
- while (cur)
- {
- //指向待删除的下一个结点
- SListNode* next = cur->next;
- //删除
- free(cur);
- //指向下一个结点,正好到了最后一个结点cur被置空
- cur = next;
- }
- }
由此可见,单链表结构:
适合头插头删
不适合尾部或者中间某个位置插入删除
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。