定义一个单链表
单链表采用结构体的方式定义,元素组成是一个data+一个同类型指针。
最后将结构体重命名,这里的两个重命名基本意思相同,做这样的命名是为了更好的区分链表的节点。
?为什么链表是指针类型,节点是普通类型呢
typedef struct LNode{
int data;
struct LNode *next;
}*linklist,LNode;
创建一个单链表
初始化
给头节点分配内存空间,并且让L指针指向头节点
1.为了代码的健壮性,检查分配是否成功
2.置空链表:因为其他表都是以单链表作为基础,因此这里实现的是单链表带头结点的置空。
bool Initlist(linklist *L){
*L=(LNode*)malloc(sizeof(LNode));//创建头节点
//L=(linklist)malloc(sizeof(linklist));
if (L==NULL)
return false;//分配内存失败
(*L)->next=NULL;//置空链表
return true;
}
头插法建立单链表
基本思路:每次来一个新节点都插入头节点和第一个节点之间。
基本方法:给新节点分配存储空间,读入新节点,数据赋值;新节点先连第一个节点,头节点连接新节点
//头插法建立单链表
linklist List_headInsert(linklist *L){
Initlist(L);
int a;
linklist s;
scanf("%d",&a);//输入一个数值
//可不可以没有这一句?
(*L)->next=NULL;
while(a!=9999)
{
s=(LNode*)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=a;//存放数据
s->next=(*L)->next;
(*L)->next=s;
scanf("%d",&a);
}
return *L;
}
尾插法建立单链表
基本思路:创建新节点,尾部节点指向新节点;最后,新节点next置空;得有一个p节点始终指向链表尾部,每次循环过后也要移动
//尾插法建立单链表
linklist List_TailInsert(linklist *L){//建立一个正向链表
Initlist(L);
int a;
scanf("%d",&a);//输入一个数值
LNode *r=*L;//设定两个指针指向头节点
linklist s;
while(a!=9999)//终止条件
{
s=(LNode *)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=a;//存放数据
r->next=s;//连接两个节点
r=s;//移动r指向新节点
scanf("%d",&a);
}
r->next=NULL;//让链表末尾指向空
return *L;//返回头指针
}
基本操作
创销增删改查六种操作,现在才实现了一种。销毁是删除的循环版,增加和修改的基础是查找,所以我们先讲这两个。
查找
按位查找
基本思路:从头节点开始查找,头节点记为第0位,找到第i位为止;然后如果要后插,就用尾插法,前插就找到第i-1位为止(具体表现在代码里就是j和p的错位,如果找的是i位,就j和p同步;如果找到的是i-1位,就让j比p大1)(前插还有一个骚操作,就是照旧后插,但是交换两个节点的值)(这里带上新节点p主要是为了方便后续插入,如果单纯头插法建立单链表,不需要指针p;p在这里初始化为头节点,在尾插法里初始化为也是头节点,但是概念里它要始终指向尾部节点)
代码是插入位序为i的节点
bool Insert_order(linklist *L,int i,int e){
if (i>len(L)||i<1)
return false;
linklist p=*L;
//int j=0;
int j=1;
while(p&&j<i){//把握要找的节点和插入的地方的区别
j++;
p=p->next;
}
LNode *s;
s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
按值查找
基本思路:按照上面的索引,不过终止条件要修改为p->data==a这种,最后返回一个true或者索引
删除
基本思路:删除任意一个节点,需要把他前面的节点和后面的节点连起来,所以就需要两个指针,找到任意节点的前驱节点,这里要注意找一个临时变量temp来代替被删除的节点,不能直接p=p->next->next(这一步是连接被删除节点的前后两个节点)
bool delete_list(linklist *L,int i)
{
if (i>len(L)||i<1)
return false;
linklist p=*L;
int j=1;
while(p&&j<i){//把握要找的节点和插入的地方的区别
j++;
p=p->next;
}
linklist x=p->next;//有点像temp操作,不能直接对本体操作
p->next=x->next;//让p的next变成另一个,而不是p=p—>next->next
free(x);
return true;
}
销毁
上面那个函数是删除第i个节点,重复上面操作,直到删除所有节点即可
链表逆置
基本思路:从头结点开始遍历链表并取出,然后新建一个链表,以尾插法方式
linklist List_Invert(linklist L,linklist *L2){
//linklist L2;
linklist s,p=L->next;//提前声明是s,p的类型
*L2=(LNode *)malloc(sizeof(LNode));
(*L2)->next=NULL;
while(p)
{
s=(LNode *)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=p->data;//存放数据
s->next=(*L2)->next;
(*L2)->next=s;
p=p->next;
}
return *L2;
}
完整代码
//定义单链表
//建立一个单链表:头插法,尾插法;有头节点,无头结点
//删除某个元素:按值删除,按位删除
//查找某个元素:按值查找,按位查找
//插入某个元素
//修改某个元素
#include<stdio.h>
#include<stdbool.h>//c语言引用bool类型
#include <stdlib.h>
#include <malloc.h>
#include<assert.h>
//定义一个单链表节点数据结构
typedef struct LNode{
int data;
struct LNode *next;
}*linklist,LNode;
//带头节点初始化
//linklist *L实际意思就是取L地址,c语言中的特殊表示
bool Initlist(linklist *L){
*L=(LNode*)malloc(sizeof(LNode));//创建头节点
//L=(linklist)malloc(sizeof(linklist));
if (L==NULL)
return false;//分配内存失败
(*L)->next=NULL;//置空链表
return true;
}
//判断是否空链表
bool Empty(linklist L){
return (L->next==NULL);
}
//尾插法建立单链表
linklist List_TailInsert(linklist *L){//建立一个正向链表
Initlist(L);
int a;
scanf("%d",&a);//输入一个数值
LNode *r=*L;//设定两个指针指向头节点
linklist s;
while(a!=9999)//终止条件
{
s=(LNode *)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=a;//存放数据
r->next=s;//连接两个节点
r=s;//移动r指向新节点
scanf("%d",&a);
}
r->next=NULL;//让链表末尾指向空
return *L;//返回头指针
}
//头插法建立单链表
linklist List_headInsert(linklist *L){
Initlist(L);
int a;
linklist s;
scanf("%d",&a);//输入一个数值
//可不可以没有这一句?
(*L)->next=NULL;
while(a!=9999)
{
s=(LNode*)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=a;//存放数据
s->next=(*L)->next;
(*L)->next=s;
scanf("%d",&a);
}
return *L;
}
//链表逆置思路:从前往后取链表的数,取出来的数以头插法的形式插入另一个链表
linklist List_Invert(linklist L,linklist *L2){
//linklist L2;
linklist s,p=L->next;//提前声明是s,p的类型
*L2=(LNode *)malloc(sizeof(LNode));
(*L2)->next=NULL;
while(p)
{
s=(LNode *)malloc(sizeof(LNode));//创建一个新的节点来存放数据
s->data=p->data;//存放数据
s->next=(*L2)->next;
(*L2)->next=s;
p=p->next;
}
return *L2;
}
int len(linklist *L){
linklist p=(*L)->next;
int length=0;
while(p){
length++;
p=p->next;
}
return length;
}
//按位序后插入
bool Insert_order(linklist *L,int i,int e){
if (i>len(L)||i<1)
return false;
linklist p=*L;
//int j=0;
int j=1;
while(p&&j<i){//把握要找的节点和插入的地方的区别
j++;
p=p->next;
}
LNode *s;
s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
//指定节点的后插和删除十分好处理,因为直接通过next就可以定位;前插还需要循环查找到指定节点,因此我实现一下指定节点的前驱查找,笑死不用了,他直接后插了这个节点,然后交换两个的值,厉害了
//getelem不写了,能删除就能找到
//bool Getelem(linklist L,)
bool delete_list(linklist *L,int i)
{
if (i>len(L)||i<1)
return false;
linklist p=*L;
int j=1;
while(p&&j<i){//把握要找的节点和插入的地方的区别
j++;
p=p->next;
}
linklist x=p->next;//有点像temp操作,不能直接对本体操作
p->next=x->next;//让p的next变成另一个,而不是p=p—>next->next
free(x);
return true;
}
//前插的思路就是1.照旧后插,然后交换两个数值;2.始终保持p比j慢一步(j=1开始)
bool printlist(linklist L){
if (L->next==NULL)
return false;
LNode *p=L->next;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->next;
}
return true;
}
int main()
{
linklist L=List_headInsert(&L);
printf("头插法的结果是");
printlist(L);
printf("\n");
// linklist L=List_TailInsert(&L);
// printf("尾插法的结果是");
// printlist(L);
linklist L2;
List_Invert(L,&L2);
printf("链表逆置的结果是");
printlist(L2);
printf("\n");
//在第二个位置后/前插一个888
printf("链表插入的结果是");
Insert_order(&L,2, 888);
printlist(L);
printf("\n");
//删除第三个位置的元素
printf("链表删除的结果是");
delete_list(&L,3);
printlist(L);
printf("\n");
return 0;
}
后记
代码都是我一个一个实现,并且测试过的,当时还参考了一些大佬的文章,但是忘了加参考,现在也很难找了,就算了。本来没写说明,但是发现不到一周,就把该忘的都忘了,所以又回来写说明啦,应该写的很清楚了,祝我和各位数据结构自由,互联网精神万岁
——---------------------------------------有IDP的人像星星,没有的人像落叶-------------------------------------