一、无头非循环单链表
1.链表功能
无头非循环单链表主要包括增、删、插、改、显示的功能。每个链表的节点包含要存储的数据以及下一个数据存储的地址,最后一个节点的地址存储为空。特点:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等。
2.学习过程
作为初学者,在刚开始学的时候还是遇到很多麻烦的:指针,二级指针,断言的使用等等。(虽然老师课上讲的很详细,但有时候确实自己没能很好地理解)。后续跟着老师刷了一些力扣的题,也很卡(捂脸)。
2.1在指针上遇到的麻烦
什么时候用二级指针,什么时候用一级指针,想了好久。当需要使节点的地址发生变化时,就要用到二级指针。当需要对节点内的成员进行访问时,只需用到一级指针。
执行链表的插入函数
这里通过传递节点地址的方式改变了原链表的构成,节点地址本身为一级指针,改变一级指针就要用二级指针(我自己这么理解的(捂脸))。
链表中执行显示功能的函数
这里仅需要对每个节点的成员进行访问,一级指针就够了。
2.2在assert上遇到的麻烦
assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行。此为原文地址(c语言之assert()函数用法总结_assert函数c语言作用-CSDN博客)
我个人的理解为:判断断言内容是否为空。开始学的时候喜欢凡是用到指针的函数,开头都进行断言,但是在单链表的学习中明显不行了,
单链表为空时可以打印,所以不可以对显示函数中传入的指针进行断言。
执行删除功能时,要避免空链表,要保证节点不为空:assert(*head)。
2.3刷题很慢
做了几道老师课上提到过的题,总结:想不到,写不对(捂脸)。
自己写了半天,总是出错。
代码质量不行
链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)
做了上面的题,学到正确的方法。
自己没写出来,跟着老师做的。
3.总结
学习速度太慢了,这周学到的东西太少了,下周要提高效率,继续努力。
单链表头文件:
#pragma once
#include <stdio.h>
#include <assert.h>
#include <malloc.h>
typedef int SListDataType;
typedef struct SList {
SListDataType data;
//int size;
//int Capacity;
struct SList* next;
}ST;
void SListPrint(ST* phead);
void SListPushBack(ST** phead, SListDataType x);
void SListPopBack(ST** phead);
void SListPopFront(ST** phead);
void SListPushFront(ST** phead, SListDataType x);
ST* SListFind(ST* phead, SListDataType x);
void SListInsertFront(ST* pos, ST** phead,SListDataType x);//前插insert不能尾插
void SListInsertBack(ST* pos, ST** phead,SListDataType x);//后插
void SListEraseFront(ST** phead, ST* pos);//前删
void SListEraseBack(ST** phead, ST* pos);//后删
ST* BuyNote( SListDataType x);
void menu();
源文件(菜单功能没实现):
#pragma once
#include <stdio.h>
//#include <iostream>
//using namespace std;
#include "List.h"
void SListPrint(ST* phead)
{
//assert(phead);断言只能解决空指针的问题,某些封装函数指针不可为空,所以匹配assert
//但是,有些函数即便指针为空也是合理情况,这时就不能assert。
ST* cur = phead;
//顺序表对phead断言是为了保证顺序表不为空
while(cur)
{
printf("%d-> ",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void menu()
{
printf("**********单链表**********\n");
printf("********1.数据展示********\n");
printf("********2.数据插入********\n");
printf("********3.数据删除********\n");
printf("********4.数据清除********\n");
printf("********5.数据查找********\n");
//printf("********5.退出系统********\n");
printf("**********单链表**********\n");
}
void SListPushBack(ST** phead, SListDataType x)
{
assert(phead);//(别和空表弄混*phead是判断表是否为空)
//assert(*phead); //空链表可以尾插所以*phead可以为空
ST* Tmp = BuyNote(x);
if (*phead==NULL)
{
*phead = Tmp;//这里的Tmp是节点的地址,实质就是一个结构体的指针
}
else
{
ST* tail = *phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = Tmp;
}
}
ST * BuyNote(SListDataType x)
{
ST* Tmp = (ST*)malloc(sizeof(ST));
if (Tmp == NULL)
{
perror("Tmp NULL!");
return NULL;
}
Tmp->data = x;
Tmp->next = NULL;
return Tmp;
}
void SListPushFront(ST** phead, SListDataType x)
{
assert(phead);
ST *Tmp=BuyNote(x);
Tmp->next = *phead;
*phead = Tmp;
}
void SListPopBack(ST** phead)
{
assert(phead);//这里phead就是**phead,因为ST**是类型啊,int a,后续直接就a了,没有int。
assert(*phead);//这里是加*是解引用,指针的地址解引用就是指针(节点的地址,这样就可以直接对原链表进行操作,避免了值传递问题)
//在此基础上,要进行*phead断言是因为要对节点进行删除,每个地址就是一个成员,没成员就不能删除,所以要保证解出的地址不为空
if (*phead==NULL)
{
perror("Phead NULL! ");
return;
}
if ((*phead)->next == NULL)
{
free(*phead);
*phead = NULL;
}
else
{
ST* Tmp = (ST*)malloc(sizeof(ST));
Tmp = *phead;
ST* Pre = NULL;
while (Tmp->next != NULL)
{
Pre = Tmp;
Tmp = Tmp->next;
}
free(Tmp);
Tmp = NULL;
Pre->next = NULL;
}
/*ST* Tmp = (ST*)malloc(sizeof(ST));
Tmp = *phead;
while(Tmp->next->next!=NULL)
{
Tmp=Tmp->next;
}
free(Tmp->next);
Tmp->next=NULL;(这种方式明显理解的更深了)
*/
}
void SListPopFront(ST** phead)
{
assert(phead);
assert(*phead);
if (*phead == NULL)
{
perror("Phead NULL! ");
return;
}
else
{
ST*Tmp=(*phead);
*phead = (*phead)->next;
free(Tmp);
Tmp = NULL;
}
}
ST* SListFind(ST* phead, SListDataType x)
{
//assert(phead);
ST* Tmp = phead;
while (Tmp)
{
if (Tmp->data == x)
{
printf("找到的地址为:%p\n",Tmp);
return Tmp;
}
Tmp = Tmp->next;
}
printf("数据不包含在链表内!\n");
return NULL;
}
void SListInsertFront(ST* pos, ST**phead, SListDataType x)//前插
{
assert(phead);
assert(pos);
if (pos == *phead)
{
SListPushFront(phead,x);
}
else
{
ST* Tmp = BuyNote(x);
ST* Tmp2 = *phead;
//前插,关键是找到插入点的前一个位置
while (Tmp2->next != pos)
{
Tmp2 = Tmp2->next;
}
Tmp2->next=Tmp;
Tmp->next=pos;
}
}
void SListInsertBack(ST* pos, ST** phead, SListDataType x)//后插
{
assert(phead);
assert(pos);
if (pos == *phead)
{
SListPushFront(phead, x);
}
else
{
ST* Tmp = BuyNote(x);
ST* Tmp2 = *phead;
//后插,关键是找到插入点的下一个位置
while (Tmp2!= pos)
{
Tmp2 = Tmp2->next;
}
Tmp->next = Tmp2->next;
Tmp2->next = Tmp;
}
}
void SListEraseFront(ST** phead, ST* pos)//前删
{
assert(phead);
assert(*phead);
assert(pos);
ST* cur = *phead;
if (cur->next == NULL)
{
free(cur);
cur = NULL;
}
else if (cur==pos)
{
perror("前删操作不能完成,请选择后删!");
}
else
{
while (cur->next != pos)
{
cur=cur->next;
}
/*ST* Tmp = *phead;
while (Tmp != pos)
{
Tmp = Tmp->next;
}
cur->next = Tmp->next;
free(Tmp);
Tmp = NULL;*///自己写得,下面是人家写的,看看差距
cur->next = pos->next;
free(pos);
//pos = NULL;//这里没用,写了就是基础不扎实,pos是指针,想把
//pos的地址置为空,就要用指向pos地址的指针(二级),再解引用。
//可以外部置空
}
}
void SListEraseBack(ST** phead,ST* pos)//后删,删的pos位置后面的值,和前删值不一样,离谱。
{
assert(phead);
assert(*phead);
assert(pos->next);
//自己写的:
ST* Tmp = *phead;
while (Tmp->next != pos)
{
Tmp = Tmp->next;
}
Tmp->next = Tmp->next->next;
free(pos);
//课上讲的:
//Tmp=pos->next;
//pos->next=pos->next->next;
//free(Tmp);
//Tmp=NULL;
}
测试文件:
#pragma once
#include "List.h"
void test()
{
menu();
ST* p = NULL;
ST* l = NULL;
SListPushBack(&p,1);
SListPushBack(&p,2);
SListPushBack(&p,3);
SListPushBack(&p,4);
SListPushFront(&p, 0);
SListPushFront(&p, 9);
SListPushFront(&p, 8);
SListPopBack(&p);
SListPopFront(&p);
SListPopFront(&p);
ST* pp=SListFind(p,3);
printf("%p\n",pp);
SListInsertFront(pp,&p,3);
//SListEraseFront(&p,pp);为什么放在这里会出问题?因为pp指向的内容已经被删了。
SListInsertBack(pp,&p, 11);
SListEraseFront(&p,pp);
pp = NULL;
ST* pp2 = SListFind(p, 3);
SListInsertBack(pp2, &p, 11);
//SListEraseBack(&p, pp);
//pp=NULL;这里是后删,要执行需要重新调用一次Find函数。
/*SListPopFront(&p);
SListPopFront(&p);
SListPopFront(&p);
SListPopFront(&p);*/
//SListPopFront(&p);
SListPrint(p);
}
int main()
{
test();
return 0;
}