从第10期破例插叙一期单链表的实现,这个东东相当重要!考研的同学也可以看:相较于王道考研的伪码不太相同,专注于可以运行。如果是笔试中的伪码,意思正确即可~
注:博主之前写过一个版本的顺序表和单链表的C++实现,和这篇的写法有所不同,不过内容也较全,大家可以先行阅读~C++数据结构笔记(2)线性表的顺序存储和链式存储_c++线性表的顺序存储和链式存储-CSDN博客文章浏览阅读348次。1.线性表是0个或者多个数据元素的有限序列,其中数据元素类型相同2.线性表可以逐项访问和顺序存储3.有顺序存储和链式存储两种存储方式。接下来,_c++线性表的顺序存储和链式存储https://jslhyh32.blog.csdn.net/article/details/131440870
一.理论精炼
线性表没什么可说的,比较简单,大家姑且把他理解为数组即可。相较于顺序表的物理结构上也连续存取,链表在内存中的存储位置是离散的。在实现的过程中,多数参考资料愿意以带头节点的链表为例,如下图:
二.分配内存空间
先来看一下链表节点的定义方式:
struct LNode{
int data;
LNode* next;
};
初学者接触的时候有人会蒙圈——因为这相当于是嵌套定义,即类型里面又直接定义了一个指向自己类型的指针。但这恰恰是链表的“几何意义”:顺序表的定义就很符合人类的思维惯式,对吧,一个表,或者说是一个有顺序的集合一样,物理上就应该是连续的一堆东西连在一起——计算机的内存中实际上也是这么存放的,因此在定义的时候直接定义成和数组“长得一样”的类型。
不过链表就不一样了,和他本身的逻辑意义一致——即各节点之间通过指针串起来,彼此之间是不连续的。因此在声明一个链表的时候,其实是声明了一个头结点——这样后面的元素根据指针就可以存在了!即声明的时候先声明一个,然后再根据指针域的值继续赋值。
1.malloc函数
malloc是用于申请分配动态内存的函数,这篇博客已经介绍过,这里不再赘述:
C语言malloc函数及数组初始长度的辨析-CSDN博客文章浏览阅读389次,点赞7次,收藏7次。知名的教材在编写中总是给出了很多伪代码,虽然说从意图上来说只要将代码的逻辑表达清楚就没什么问题,不过很多书中的伪码有些过于逆天,会误导许多基础不扎实的人;另一方面,毕竟每个人的编码习惯不同,可能有些高手喜欢写生僻的代码来让人云里雾里语法的规则。。。。https://jslhyh32.blog.csdn.net/article/details/140559079?spm=1001.2014.3001.5502如下,声明一个LNode类型的指针,再赋予空间:
LNode* head;
head=(LNode*)malloc(5*sizeof(LNode));
逻辑非常清晰明了,malloc返回的是地址,因此需要将这部分地址赋予一个指针~
2.new运算符
C++中,可以这样写:
LNode* p=new LNode;
没什么难度,这里主要想讲C,就不展开细说了。
3.内存泄漏
C语言的设计者认为,程序员完全右能力自己控制内存的分配与释放,因此把对内存的控制、操作都分配给了程序员。使用完malloc等之后,一定要使用free将其释放:
free(head);
三.创建链表
先来直观的感受一下如何创建链表:
LNode* node1=(LNode*)malloc(sizeof(LNode));
LNode* node2=(LNode*)malloc(sizeof(LNode));
LNode* node3=(LNode*)malloc(sizeof(LNode));
node1->data=1;
node1->next=node2;
node2->data=2;
node2->next=node3;
node3->data=3;
node3->next=NULL;
显然这样太死板了,没有人机交互,那么不妨我们改善一下,main函数中可以输入链表的长度,如下:
int main(int argc, char *argv[]) {
printf("请输入链表的长度:");
int num=0;
scanf("%d",&num);
LNode* List=createLinkList(num); //根据长度创建链表
List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
while(List!=NULL)
{
printf("%d ",List->data);
List=List->next;
}
return 0;
}
然后我们来编写createLinkList函数:
LNode* createLinkList(int x)
{
LNode* head;// 声明头结点
LNode* tNode;//当前结点
LNode* pre;//当前结点的前驱节点
head=(LNode*)malloc(sizeof(LNode));
head->next=NULL;//初始化指针域为0
pre=head;
int i=1;
for(;i<=x;i++)
{
tNode=(LNode*)malloc(sizeof(LNode));//创建一个新节点
int temp=0;
temp=i;
tNode->data=temp;
tNode->next=NULL;
pre->next=tNode;
pre=tNode;
}
return head;
}
(加前驱是为了方便链表的创建,也可以不这样做~)
如上图,没什么bug~
为了让大家直观感受一下所谓的【离散】,我们把链表中的节点地址也打印一下,修改main函数如下:
int main(int argc, char *argv[]) {
printf("请输入链表的长度:");
int num=0;
scanf("%d",&num);
LNode* List=createLinkList(num); //根据长度创建链表
LNode* other=List; //另外单独存放一个头结点
printf("地址依次如下:\n");
while(other!=NULL)
{
printf("%d ",&other);
other=other->next;
}
printf("\n");
printf("数值依次如下:\n");
List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
while(List!=NULL)
{
printf("%d ",List->data);
List=List->next;
}
return 0;
}
如下图:头结点和其他5个节点之间均不是连续的!
另外,每次执行的地址也不尽相同。相信这样直观感受一下,各位就能理解到链表的物理意义了~
四.查找元素
查找就非常简单了,这里我们直接写一个统计某元素个数的函数,如下:
int SearchByValue(LNode* target,int x)
{
int count=0;
LNode* temp=target->next;
while(temp!=NULL)
{
if(temp->data==x)
count++;
temp=temp->next;
}
return count;
}
main函数调用:
继续写别的~
五.插入元素
这个有点意思,因为需要交换两个指针,具体的逻辑这里不多说了,太基础了,这里放个图让大家看看,千万别被逻辑绕晕~
代码如下:
void InsertByPos(LNode* L,int pos,int x)
{
LNode* temp=L;//将头结点的值赋给临时的节点
int i=1;
for(;i<=pos-1;i++) //找到待插入位置的前一个指针
temp=temp->next;
LNode* tNode;
tNode=(LNode*)malloc(sizeof(LNode)); //创建一个新节点
tNode->next=temp->next;
temp->next=tNode;
tNode->data=x;
}
main函数测试:
//3.测试 InsertByPos
InsertByPos(List,4,32);
List=List->next;
while(List!=NULL)
{
printf("%d %d\n",List->data,&List->next);
List=List->next;
}
结果如下:
堪称完美~
六.删除元素
1.按值删除
简单,直接按照上面按值查询的代码写就好,只需要改变if条件的逻辑即可:
void DeleteByValue(LNode* L,int x)
{
LNode* temp=L->next;//忽略头结点的第一个
LNode* pre=L;//pre始终用来保存
while(temp!=NULL)
{
if(temp->data==x)
pre->next=temp->next;
pre=temp;
temp=temp->next;
}
}
main函数:
//4.测试DeleteByValue
DeleteByValue(List,4);
List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
while(List!=NULL)
{
printf("%d %d\n",List->data,&List->next);
List=List->next;
}
测试结果:
4成功被删掉~
2.按位删除
根据插入元素的代码即可修改成功。题外话,其实这里还可以写一个按位查找,大家自己试试~
void DeleteByPos(LNode* L,int pos)
{
LNode* temp=L->next;
LNode* pre=L;
int i=1;
for(;i<=pos-1;i++) //找到待插入位置的前一个指针
{
pre=temp;
temp=temp->next;
}
pre->next=temp->next;
}
老套路:
DeleteByPos(List,4);
List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
while(List!=NULL)
{
printf("%d %d\n",List->data,&List->next);
List=List->next;
}
free(List);
return 0;
还是删除掉了第4位的元素~
主打一个过五关斩六将~
七.静态链表
静态链表感觉没什么意思,感觉还不如直接用顺序表,大家自己看看就行,比较简单:
写在最后:无论408还是众多985名校的自命题,线性表都是算法题、大题考察的热门,大家一定要熟练掌握代码规范。至于二叉树乃至图论的具体编程实现,怎么说,要是你不是奔着140+去考,其实是可以允许自己不会的,参照二八定律——应该尽可能地从简易的20%里面获取占大头的80%分数。因此这期结束后也不优先更新各种数据结构的实现了,优先更新一些数学的问题,接着就是DFS、BFS、DP这些蓝桥杯等竞赛偏爱的内容。(说句题外话,蓝桥杯之所以被戏称圈钱杯、DP杯,就是因为近年来的考试题目逐渐以暴力和DP为主,在一个不是很发达的省份比如我们这里,即便是A组,如果你的DP异常熟练,拿个省一进国赛是没什么难度的。除了蓝桥杯还有CSP,如果代码基础非常扎实,会处理复杂的字符串,还能熟练掌握DP,考个300+似乎也不是很有难度。。由此可见DP的重要性!)
完整的.c文件源码,有需要的自提:
链接:https://pan.baidu.com/s/1uN0elyL2N25vNhF2bSewoA
提取码:hma8