相比于顺序表,我们知道顺序表的优点为
1.物理空间连续
2.下表随机访问
但是其缺点也很明显
1.空间不够需要扩容。而扩容有一定的性能消耗,其次一般扩容2倍,存在一定空间浪费。
2.头部或者中间插入效率低下。
故以此缺点上我们提出改进方案
1.按需申请释放空间
因此我们引入单链表来实现其优化
定义:“单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。”
同时利用单链表我们实现头部或者尾部的插入删除则不需要挪动数据,单独开辟一部分内存即可,可以说,链表是由顺序表的缺点而衍生出来的。
接下来就让我们一起进行代码的实现:
1.头文件变量的定义
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SM;
typedef struct SL
{
int data;
SL* next;
}SL;
next为指向下一个节点的指针
2.打印链表
void Slistprint(SL* first)
{
assert(first);
SL* cur=first;
while(*cur='\0')
{
printf("%d",cur->data);
cur=cur->next;
}
printf("NULL\n");
}
(额外补充):
因为后续要用到许多扩容的代码,在这里我们统一写做一个函数来封装,具体代码如下:
SL* CreatListnode(SM x)
{
SL* newnode=(SL*)malloc(sizeof(SL));
assert(newnode);
newnode->data=x;
newnode->next=NULL:
return newnode;
}
3.单链表尾部插入
void SlistPushback(SL* first,SM x)
{
assert(first);
SL* newnode=(SM*)malloc(sizeof(SM));
SL* tail=frist;
newnode->data=x;
newnode->next=NULL;
while(tail!=NULL)
{
tail=tail->next;
}
tail->next=newnode;
}
如果我们测试代码,会发现如果链表已经存在 ,代码没有问题,执行代码如下:
SL* plist=n1;
Slistprint(plist);
Slistpushback(plist,4);
Slistpushback(plist,5);
但是如果链表一开始是空链表,这是我们会发现程序运行会崩溃。
故我们因添加如下代码:
if(first=NULL)
{
first=newnode;
}
这里我们要思考一下我们是否写的正确,在我们运行完程序后,我们发现程序仍然会崩溃,这又是为什么呢?
在这里我们将newnode的地址給了first之后,如果我们进行调试会发现,plist仍未接收到地址,其仍然为空指针 ,原因是因为在这里plist是实参,而first是形参,我们在学C语言时知道,形参的改变不会影响实参的值,那我们传地址即可。
但我们又发现,在这里我们传的就是指针,那为什么不行呢?
我们在这里要注意,我们解引用的是指针变量,传递的是指针变量的地址,这里我们举一个形象的例子:
void fun(int a)
{
a=1;
}
int main()
{
int x=0;
fun(x);
printf("%d",x);
return 0;
}
在这里a的改变不会影响x的值,因为形参的改变不会影响实参的值,故我们应将x的地址传递过去,修改如下:
void fun(int* a)
{
*a=1;
}
int main()
{
int x=0;
fun(&x);
printf("%d",x);
return 0;
}
这样才可以修改x的值,接着看下面这个例子:
void fun(int* p)
{
int* ptr=(int*)malloc(sizeof(int));
p=ptr;
}
int main()
{
int* px=NULL;
fun(px);
printf("%u\n",px);
return 0;
}
在这里我们发现我们对ptr的改变不会影响px的地址,要想改变px的地址,需要传递指针变量的地址,所以我们可以整合如下规律:要改变int,传递int的地址;要改变int*的地址,要传递int*的地址,这是我们需要使用二级指针来传递一级指针变量的地址,修改代码如下:
void fun(int** p)
{
int* ptr=(int*)malloc(sizeof(int));
*p=ptr;
}
int main()
{
int* px=NULL;
fun(&px);
printf("%u\n",px);
return 0;
}
返回到单链表的尾插,修改代码如下:
void SlistPushback(SL** ffirst,SM x)
{
assert(ffirst);
SL* newnode=(SM*)malloc(sizeof(SM));
SL* tail=*ffrist;
newnode->data=x;
newnode->next=NULL;
while(tail!=NULL)
{
tail=tail->next;
}
tail->next=newnode;
}
传递值代码修改如下:
SL* plist=n1;
Slistprint(plist);
Slistpushback(&plist,4);
Slistpushback(&plist,5);
注意打印函数不需要传递地址,不要弄混。
4.单链表头部插入
void Slistpushfront(SL** ffirst,SM x)
{
SL* newnode=CreatSlistnode(x);
newnode->next=*ffirst;
*ffirst=newnode;
}
5.单链表尾部删除
在这里有两种写法:
法一(利用二级指针):
void SlistpopBack(SL** ffirst)
{
assert(*ffirst);
if(*ffirst=NULL)//只有一个节点的情况
{
free(*ffirst);
*ffirst=NULL;
}
else
{
SL* newtail=NULL;
SL* tail=*first;
while(tail)
{
newtail=tail;
tail=tail->next;
}
free(tail);
newtail->next=NULL;
}
}
法二(只使用一级指针):
while(tail->next->next!=NULL)//找的是倒数第二个
{
tail=tail->next;
}
free(tail->next);
tail->next=NULL;
6.单链表头部删除
void Slistpopfront(SL* ffirst)
{
assert(*ffirst);
SL* next=(*ffirst)->next;
free(*ffirst);
*ffirst=next;
}
此处只能用二级指针,因为要改变plist。所以一旦改变plist的值,就需要调用二级指针。
7.查找函数
SL* Slistfind(SL* first,SM* x)
{
SL* cur=first;
while(cur)
{
if(cur->data==x)
{
return cur;
}
cur=cur->next;
}
return NULL;
}
这里为什么要返回节点的指针,因为查找既可以充当查找又可以充当修改,代码如下:
SL* ret=Slistfind(plist,3);
if(ret)
{
printf("找到了\n");
ret->data=10;
}
8.任意位置插入和删除
具体代码如下:
void SListInsertAfter(SL* pos, SM x)
{
assert(pos);
SL* next = pos->next;
SL* newnode = CreatSlistnode(x);
pos->next = newnode;
newnode->next = next;
}
void SListEraseAfter(SL* pos)
{
assert(pos);
SL* next = pos->next;
if (next != NULL)
{
SL* nextnext = next->next;
free(next);
pos->next = nextnext;
}
}
最后希望这篇文章对您有帮助!