主体:
// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
申请空间:
// 动态申请一个结点
SListNode* BuySListNode(SLTDateType x) {
SListNode* tmp = (SListNode*)malloc(sizeof(SListNode));
if (!tmp) {
perror("fail buy");
exit(-1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
思路:
申请一片空间,然后判断是否申请成功,没有成功就退出,退出码为-1;
申请成功,我们就填入数据,然后将我们节点的指向为NULL,这样方便后面操作;
遇见的问题:
我在实现顺序表的时候也遇见了,就是它警告NULL取消tmp->data的引用;
我当时没有反应过来,且没有报错就没改,我后面才想起来perror是一个打印错误信息的函数,本身不会结束进程;所以代码还是会向下执行,所以我们要用一个在子进程可以杀死进程的函数exit;
打印:
// 单链表打印
void SListPrint(SListNode* plist) {
assert(plist);
SListNode* tmp = plist;
while (tmp) {
printf("%d", tmp->data);
tmp=tmp->next;
}
printf("\n");
}
思路:
先判断给指针的是否为空;因为我们要访问它的成员;
其实这个tmp是没有必要的;我们这里的plist也是一个函数内部的临时变量;
实际上就是从前往后读取,边界条件为最后一个节点也要打印;于是要将指针移动到指向NULL;
优化后的代码:
// 单链表打印
void SListPrint(SListNode* plist) {
assert(plist);
while (plist) {
printf("%d", plist->data);
plist = plist->next;
}
printf("\n");
}
尾插:
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {
SListNode* tmp = BuySListNode(x);
SListNode* ptmp = *pplist;
if (ptmp == NULL) ptmp = tmp;
else {
while (ptmp->next) {
ptmp = ptmp->next;
}
ptmp->next = tmp;
}
}
思路:
第一,链表为空(就是一个节点都没有);我们直接指向申请的节点;
第二,不为空,链表找尾,边界条件为尾节点保留,就是移动指针到节点next为空的时候;
细节:我们不断言指针为空的情况,因为空链表也是可以的;
改bug:
代码第5行,这里会存在问题;因为我们要改的是外面的指针,而不是这个临时变量;
这里就算赋值了,也不会改变外面;要改变一个变量值就是直接给这个变量赋值;
或者这个变量的指针赋值;
这里的临时变量只是外面指针的拷贝;所以只是指向空间相同;但是他们还是属于同等级的指针;但是我们后面的代码是对的原因也是因为我们要操作的数据和指针的等级相匹配;
我们要改结构体,只需要结构体指针就可以了;对他进行解引用改值就行;
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {
SListNode* tmp = BuySListNode(x);
if (*pplist == NULL) *pplist= tmp;
else {
SListNode* ptmp = *pplist;
while (ptmp->next) {
ptmp = ptmp->next;
}
ptmp->next = tmp;
}
}
头插:
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x) {
SListNode* tmp = BuySListNode(x);
//这里用一个局部的一级指针;这里会有问题;
tmp->next=*pplist;
*pplist = tmp;
}
思路
将新节点的下一个指向现在的头节点;然后将我们指针指向新节点;
这里和尾插一样不能用临时指针变量;
尾删:
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
assert(*pplist);
SListNode* ptmp = *pplist;
if (ptmp->next == NULL)//一个节点;
{
free(ptmp);
ptmp = NULL;
}
else {
SListNode* tmp = ptmp;
while (ptmp->next) {
tmp = ptmp;
ptmp = ptmp->next;
}
free(ptmp);
tmp->next = NULL;
}
}
思路:
尾删,
空的时候就报错;不能访问;
只有一个节点的时候直接free()所指向的空间;然后指针置空;
多个节点就要求找尾,然后同一的操作;
优化:
我刚才发现了,我们可以将这里那个合并为一个;
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
assert(*pplist);
SListNode* ptmp = *pplist;
SListNode* tmp = ptmp;
while (ptmp->next) {
tmp = ptmp;
ptmp = ptmp->next;
}
free(ptmp);
tmp->next = NULL;
}
代码执行效果一样;
头删:
// 单链表头删
void SListPopFront(SListNode** pplist) {
assert(*pplist);
SListNode* ptmp = *pplist;
(*pplist) = (*pplist)->next;
free(ptmp);
}
这里也一样不能用临时指针变量;
就是将我们指针指向他的下一个位置;
所以我们要保证他有一个节点,不然我们都没办法删;
我们要避免内存泄漏,就要求有一个指针指向跳过的节点;然后释放空间;且这里是
栈区的指针变量它会自己释放;所以可以不指向空;
查找:
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);
while (plist) {
if (plist->data == x) return plist;
}
return NULL;
}
这个没什么要说的,就是一个循环找就行了;
任意位置插入与删除
/ 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x) {
SListNode* tmp = BuySListNode(x);
tmp->next = pos->next;
pos->next = tmp;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
assert(pos && pos->next);
SListNode* tmp = pos->next;
pos->next = pos->next->next;
free(tmp);
}
思路与注意点前面完全一样;
pos->next = pos->next->next;
因为这里我们要访问pos->next的成员,所以我们要断言判断这个pos->next是否存在;
如果为空我们访问它的成员就是非法的;