声明:本文仅为个人的学习过程记录,如有问题欢迎友好交流
单链表及其应用
单链表
学完顺序表之后,咱们可以发现一些顺序表的缺点:
- 对于中间/头部 的插入与删除操作,时间复杂度为O(n),需要挪动数据,会降低程序运行效率
- 增容需要 申请新空间 拷贝数据到新空间 释放旧空间 这仨操作,会造成不小的空间损耗 增容会降低程序运行效率!
- 动态顺序表中增容 一般是2-3倍的增长,即使这一操作已经减少了空间的损耗,但仍然会造成浪费
如何解决以上问题?
这时候就需要新的数据结构-----链表
链表:
链表也是线性表的一种,
链表的物理结构不一定是线性的,但是逻辑结构一定是线性的,如下图
链表结构和火车有点类似
火车由火车头和车厢组成,车头后面连着一定数量的车厢
如图,原本是X和A这俩车厢连着,如果不要A车厢了,可以把X车厢连到D车厢上去
像火车一样,链表是由一个一个的节点(结点)组成,如下图
搞清楚了链表的大体结构,接下来咱们了解一下节点的组成部分
如上图,节点由俩部分组成,
一部分是存储的数据
另一部分存储着下一个节点的地址 即指向下一个节点的指针
既然节点里都有俩不一样的数据类型了,那节点的定义自然是由结构体来完成
实现链表的过程中,我们会用到一个头文件和一个源文件(其实全放在一个源文件里面也行,但是这样显得有点乱)
singlechainklist.h和singlechainklist.cpp
头文件里面放各种声明,源文件里面进行单链表功能函数的实现
//存储的数据类型
typedef int DataType; //方便后续应用时换成结构体类型
//结构体的定义
typedef struct SingleChainListNode
{
DataType data; //存储的数据
struct SingleChainListNode* next; //z指向下一个节点的指针,存储着下一个节点的地址
}Node;
思考一下,是否真的需要二级指针呢?
应该是有两种写法的
- 不需要二级指针的
在菜单函数开头就创建头节点并实际给头节点分配了空间,让他做了链表的第一个元素
如:
node* head = (node*)malloc(sizeof(node)); //头节点
head->next = NULL;
且插入函数里面是自动增添新节点的
这种就不需要二级指针,head是指针变量,其传进函数,被指针变量接收
- 需要二级指针的
在菜单函数开头创建了头节点,但是只是置空,没给头节点分配空间
如:
node *head=NULL;
这种就需要用到二级指针。用在啥地方呢?
head要以&head的形式传入插入函数,接收指针的地址,那自然是要二级指针
这种需要判断头节点为空or非空
第一种我写学生信息管理系统的时候写过了,所以我这里写的是第二种 =v=
新节点的申请
代码如下:
//新节点的申请
Node* SCapplynode()
{
Node* newnode = (Node*)malloc(sizeof(Node));
if (newnode == NULL)
{
perror("newnode");
exit(1);
}
return newnode;
}
链表信息的插入:
头插,尾插 (不太需要中插)
头插
思路:头节点里的next指向插入的节点,插入的节点的next指向原本的头节点的next指向的节点
即 0-》1-》2 变为 0-》插入-》1-》2
代码如下:
//头插
void SCPushHead(Node** phead,DataType x)
{
if (*phead == NULL)
{
*phead = SCapplynode();
//DataType x = 0;
//scanf("%d", &x);
(*phead)->data = x;
(*phead)->next = NULL;
}
else
{
Node* newnode = SCapplynode();
//DataType x = 0;
//scanf("%d", &x);
newnode->data = x;
Node* tem = (*phead);
*phead = newnode;
(*phead)->next = tem;
}
}
尾插
思路:用tail指针找到最后一个节点,然后最后一个节点的next指向插入的节点
即 0-》1-》2 变为 0-》1-》2-》插入
代码如下:
//尾插
void SCPushBack(Node** phead,DataType x)
{
if (*phead == NULL)
{
*phead = SCapplynode();
//DataType x = 0;
//scanf("%d", &x);
(*phead)->data = x;
(*phead)->next = NULL;
}
else
{
Node* newnode = SCapplynode();
//DataType x = 0;
//scanf("%d", &x);
newnode->data = x;
Node* tail = (*phead);
Node* beforetail = (*phead);
while (tail!=NULL) //通过这个循环找到尾节点
{
beforetail = tail;
tail = tail->next;
}
beforetail->next = newnode;
newnode->next = NULL;
}
}
链表信息的删除:头删,尾删,指定删 (指定删在应用中用)
头删
思路:用一个临时变量保存一下传进来的指针指向的头节点的下一个节点的地址,然后free头节点,再把指向头节点的指针指向临时变量保存的第二个节点
代码如下:
//头删
void SCDelHead(Node** phead)
{
if ((*phead) == NULL)
{
printf("NULL List! Delete Error\n");
return;
}
Node* newhead = (*phead)->next;
free(*phead);
*phead = newhead;
}
尾删
思路:找到尾节点,以及为节点的前一个节点,尾节点置为空,尾节点的前一个节点里面的next指向NULL
代码如下:
//尾删
void SCDelBack(Node** phead)
{
if ((*phead) == NULL)
{
printf("NULL List! Delete Error\n");
return;
}
if ((*phead)->next == NULL) //注意特殊情况:链表里只有个头
{
free((*phead));
*phead = NULL;
return;
}
Node* tail = (*phead);
Node* beforetail = (*phead);
while (tail->next != NULL) //通过这个循环找到尾节点的前一个节点和尾节点
{
beforetail = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
beforetail->next = NULL;
}
指定删
思路:以数据为标志,通过while循环找到想要删除的节点(在后面应用的时候可以用人名这类具有辨识度的来当标志),找到想要删除的节点之后,找到其前后节点,free掉想要删除的节点,然后把删除的节点的前一个节点里的next指向删除的节点的后一个节点
代码如下:
//指定删
void SCDelSearch(Node** phead,DataType x)
{
if ((*phead) == NULL)
{
printf("Empty List! Delete Error\n");
return;
}
Node* pphead = (*phead);
Node* pointer = (*phead);
Node* beforepointer = (*phead);
assert(pphead&&pointer&&beforepointer);
while (pointer->data != x && pointer->next!=NULL)
{
beforepointer = pointer;
pointer = pointer->next;
}
if (pointer->next == NULL&&pointer->data!=x)
{
printf("Not Found! Delete Error\n");
return;
}
if (pointer == (*phead))
{
SCDelHead(phead);
return;
}
Node* afterpointer = pointer->next;
free(pointer);
pointer = NULL;
beforepointer->next = afterpointer;
}
查找
思路:以数据为标志,通过while循环找到节点(在后面应用的时候可以用人名这类具有辨识度的来当标志)
代码如下:
//数据的查找
void SCSearch(Node* phead ,DataType x)
{
Node* tail = phead;
if (tail == NULL)
{
printf("Empty LIst! Found Error\n");
return;
}
while (tail->data != x && tail->next != NULL)
{
tail = tail->next;
}
if (tail->next == NULL&&tail->data != x)
{
printf("Not Found!\n");
return;
}
printf("找到了!,数据是 %d\n", tail->data);
}
更改
思路:以数据为标志,通过while循环找到想要更改数据的节点(在后面应用的时候可以用人名这类具有辨识度的来当标志),找到之后改就完事了
代码如下:
//数据的更改
void SCChange(Node* phead,DataType x)
{
Node* tail = phead;
if (tail == NULL)
{
printf("Empty LIst! Change Error\n");
return;
}
while (tail->data != x && tail->next != NULL)
{
tail = tail->next;
}
if (tail->next == NULL&&tail->data != x)
{
printf("Not Found!\n");
return;
}
printf("请输入:");
scanf("%d", &tail->data);
printf("\n");
printf("更改成功!\n");
}
打印整个链表里储存的所有数据
代码:
//全体数据的打印
void SCPrint(Node* phead)
{
if (phead == NULL)
{
printf("NULL!\n");
return;
}
while (phead)
{
测试用
//if (phead->next == NULL)
//{
// printf("NULL");
//}
printf("%d ", phead->data);
phead = phead->next;
}
printf("\n");
}