单向链表从生成到销毁小结:
1.定义一个结构体用来生成结点:
typedef struct node
{
int date;
struct node *next; //这里只能用struct node 不能用Node;因为Node在这还没生效;
}Node;
即定义一个struct node型的结构体,在以后的程序中用Node来代替struct node出现(方便更改和操作);
2.定义一个结构体用来存放链表的头结点和尾结点:
typedef struct node
{
Node *head;
Node *tail;
}LNode;
注意:该结构体中存放的是两个结点,(是地址!!!!)不可以直接对他进行取值!!!
3.生成一个元原始链表(空链表,没有指向任何结点)
LNode *createlist()
{
LNode *list=(LNode *)malloc(sizeof(*list));
list->head=NULL;
list->tail=NULL;
return list;
}
注意:新生成的原始链表要返回给主调函数用,注意返回值类型和指针函数类型;
4.给原始链表赋值:
void crea(LNode *list)
{
while(1)
{
int i;
scanf("%d",&i);
if(i==100)
{
break;
}
//生成孤立结点
Node *pnew=(Node *)malloc(sizeof(*pnew)); //注意这里要在while()里面定义和分配空间!!!
pnew->next=NULL; //因为每添加一个新结点就需要多增加一块存储空间!!!
pnew->date=i; //这里错 1 次!!
//判断链表是否为空链表,是空链表就让链表的头和尾都指向孤立结点
if(list->head==NULL)//注意不能直接用head!!! //这里错 1 次!!
{
list->head=pnew;
list->tail=pnew;
}
else //不是孤立结点就插入结点生成链表
{
list->tail->next=pnew; //尾插法;1.让原链表的next指向新结点,
//使新结点成为原链表尾结点的下一个结点;
list->tail=pnew; //2.让原链表的尾结点指向新结点,更新尾结点的位置;
// pnew->next=list->head; //头插法:1.让新结点的next指向原链表的头结点;
// list->head=pnew; //2.让原链表的头结点指向新结点,更新头结点的位置;
}
}
}
注意:lise->head才是链表的头结点!!!不是head。
插入和判断都是对链表的 (结点) 进行的,注意自己操作的是不是结点!!!
5.打印链表:
1).只打印
void print(LNode *list)
{
if(list->head==NULL) //若链表为空,直接退出
{
return ;
}
Node *p=list->head; //注意这里p指向的是链表的头结点,p的类型是结点型的!!不是链表型的。
while(p)
{
printf("%d ",p->date);
p=p->next; //对结点进行操作!!
}
printf("\n");
}
2).打印并销毁链表
void print(LNode *list)
{
if(list->head==NULL) //若链表为空,直接退出
{
return ;
}
Node *p=list->head; //用来指向要打印的结点
Node *pre=NULL; //用来指向打印完的结点
while(p)
{
printf("%d ",p->date);
pre=p; //指向打印完的结点
p=p->next;
free(pre); //销毁打印完的结点(释放其所占空间),即逐渐销毁链表所有的结点。
}
printf("\n");
free(list); //把空链表也销毁
}
注意这里是对结点进行操作
6.销毁链表
void destrylist(DNode *list)
{
if(list->head==NULL)
{
return ;
}
Node *p=list->head; //用来指向被销毁结点的下一个结点
Node *pre=NULL; //用来指向被销毁的结点
while(p) //p与pre指向的是链表的结点,p与pre的类型是结点型的!!不是链表型的。
{
pre=p; //指向要被销毁的结点
p=p->next; //指向被销毁结点的下一个结点
free(pre); //销毁结点
}
free(list); //销毁空链表
}
注意这里是对结点进行操作
7.在链表中插入结点(顺序链表中插入后仍顺序)或者生成有序链表
void insertlist(LNode *list,int date)
{
//1.生成孤立结点;
Node *pnew=(Node *)malloc(sizeof(*pnew)); //为新结点分配空间
pnew->date=date; //为新结点赋值
pnew->next=NULL; //让新结点成为孤立结点
//2.做插入前准备;
Node *p=list->head; //指向链表的头结点,用来充当游标
Node *pre=NULL; //用来指向游标的上一个结点
//3.找位置
while(p)
{
if(p->date > date)
{
break;
}
pre=p; //指向游标的上一个结点
p=p->next; //充当游标,最终指向要插入的位置
}
//4.插入 分4种情况
if(pre==p) //若游标与游标的上一结点相等,即没有进while循环,链表为空
{
list->head=pnew;
list->tail=pnew; //直接让链表的头尾结点都指向孤立结点;
}
/***************
特别注意:
虽然定义了 (p=list->head),
但是绝对不能用(p=pnew)代替(list->head=pnew);
因为我要改变的是list这个链表,
而不是p这个副本!!
****************/
else{
if(p==NULL) //若链表不为空但游标为空,即链表中找不到比date大的,插在尾
{
pre->next=pnew; //让游标的上一结点(即尾结点)的next指向孤立结点
}
//注意这里不能用(p->next=pnew;) 因为此时p==NULL;会导致段错误!!
else if(pre==NULL) //若链表不为空但游标的上一结点为空,即第一个数就比date大,插在头
{
pnew->next=p; //让孤立结点的next指向头结点;
list->head=pnew; //更新头结点的位置,使孤立结点成为新的头结点(很重要且不能用p=pnew;)!!
}
/***************
为什么这里不能用p!!!用p最终改变的是p而不是链表list!!
注意使孤立结点成为新的头结点很重要且不能用(p=pnew;)!!
明明已经定义了(p=list->head) 为什么不能用p=pnew???
因为我传进来的是list->head,用p只是改变了p,list->head还是指向原来的位置
****************/
else //否则 插到链表中两个结点中间
{
pnew->next=p; //孤立结点的next指向游标
pre->next=pnew; //游标的上一结点的next指向孤立结点
}
}
}
8.删除链表中的某个结点
void deletelist(LNode *list,int date)
{
//1.做删除前准备
Node *p=list->head; //定义一个结点型结构体指针充当游标用来指向要删除的结点 (是结点型的)
Node *pre=NULL; //定义一个结点型结构体指针用来指向游标的上一个结点
//2.找位置
while(p)
{
if(p->date==date) //若该结点要删除
{
break; //直接跳出
}
pre=p; //指向游标的上一个结点
p=p->next; //充当游标,最终指向要插入的位置
}
//3.删除, 分4种情况
if(p==NULL) //若链表为空,或者链表中找不到要删除的结点
{
return ; //直接退出
}
else
{
if(pre==NULL) //若链表不为空但是要删除结点的上一个为空,即要删除头结点
{
/******************
明明已经定义了(p=list->head) 为什么不能用p=p->next???
因为我传进来的是list->head,用p只是改变了p,list->head还是指向原来的位置,
并且该位置会为空,输出是会将该位置打印成 0 并且显示 已放弃(核心已转储)
******************/
list->head=list->head->next; //让头结点后移(特别注意:不是让游标后移!!!)
free(p) //释放游标指向的结点的空间
}
else if(p->next==NULL) //若游标的下一结点为空,即要删除的是尾结点
{
pre->next=NULL; //让游标的上一结点指向NULL;(本来是指向要删除的结点的)
free(p); //释放游标指向的结点的空间
}
else //否则上两个结点中间的结点
{
pre->next=p->next;//游标的上一结点的next指向游标的next(原来是指向游标的,现在跳过游标使之孤立)
free(p); //释放游标指向的结点的空间
}
}
}
9.将链表逆置
LNode *inversion(LNode *list)
{
//1.生成新的原始链表(空链表)
LNode *lnew=(LNode *)malloc(sizeof(*lnew));
lnew->head=NULL;
lnew->tail=NULL;
//2.做拆除老链表的准备工作
Node *p=list->head; //用来指向下一个要被拆除的结点
Node *pre=NULL; //充当游标指向要拆除并重装的结点
//3.拆除老链表并生成新链表
while(p)
{
pre=p; //指向要拆除并重装的结点
p=p->next; //注意要先让pre指向p的下一个在把p的指向断掉!!!
// pre=p->next; 不能把两步并到一起写成这样,否则会死循环!!!
pre->next=NULL; //把pre(要拆除重装的结点)的指向断掉
if(lnew->head==NULL)
{
lnew->head=pre; //若新链表为空,让新链表的头和尾都指向要拆除重装的结点
lnew->tail=pre;
}
else
{
pre->next=lnew->head; //用头插法把要拆除重装的结点放进新链表
lnew->head=pre;
}
}
return lnew; //返回新链表;
}
10.创建一个链表(尾插法),这个链表有正有负数,乱序输入,最后输出的结果负数在,正数在后。
LNode *separate(LNode *list)
{ //1.生成原始链表(空链表)
LNode *lnew=(LNode *)malloc(sizeof(*lnew));
lnew->head=NULL;
lnew->tail=NULL;
//2.做拆除老链表的准备工作
Node *p=list->head;
Node *pre=NULL;
//3.拆除老链表并生成新链表
while(p)
{ //注意控制循环不要成为死循环或则只运行一次!! 错 2 次
pre=p; //指向要拆除并重装的结点
p=p->next; //注意要先让pre指向p的下一个在把p的指向断掉!!!
pre->next=NULL; //把pre(要拆除重装的结点)的指向断掉
if(lnew->head==NULL)
{
lnew->head=pre; //若新链表为空,让新链表的头和尾都指向要拆除重装的结点
lnew->tail=pre;
} else {
if(pre->date > 0) //若要插入的为正数
{
lnew->tail->next=pre; //用尾插法向后插入孤立结点
lnew->tail=pre;
} else {
pre->next=lnew->head; //否则用头插法向前插入孤立结点
lnew->head=pre;
}
}
}
return lnew; //返回新链表
}
11.归并有序链表
LNode *merger(LNode *list1,LNode *list2)
{
//1.定义新链表
LNode *lnew=(LNode *)malloc(sizeof(*lnew));
lnew->head=NULL;
lnew->tail=NULL;
//2.为老链表的拆除做准备
Node *p1=list1->head;
Node *pre1=NULL;
Node *p2=list2->head;
Node *pre2=NULL;
//3.进行归并
while(p1&&p2) //若有一个链表为空,不再归并
{
pre1=p1; //用来指向链表1中被拆除后孤立的结点
pre2=p2; //用来指向链表2中被拆除后孤立的结点
if(pre1->date > pre2->date)
{ //若链表1中的孤立结点大于链表2中的
if(lnew->head==NULL)
{
lnew->head=pre2;
lnew->tail=pre2;
}
else
{ //用尾插法把小的孤立结点插到新链表中
lnew->tail->next=pre2;
lnew->tail=pre2;
}
p2=p2->next; //孤立结点被使用过的链表的游标后移
pre2->next=NULL; //链表中被使用过的的孤立结点结点指向NULL
// free(pre2); //不能释放结点,因为有别的链表指向了他
}
else if(pre1->date < pre2->date)
{ //若链表2中的孤立结点大于链表1中的
if(lnew->head==NULL)
{
lnew->head=pre1;
lnew->tail=pre1;
}
else
{ //用尾插法把小的孤立结点插到新链表中
lnew->tail->next=pre1;
lnew->tail=pre1;
}
p1=p1->next; //孤立结点被使用过的链表的游标后移
pre1->next=NULL; //链表中被使用过的的孤立结点结点指向NULL
} else {
if(lnew->head==NULL)
{
lnew->head=pre1;
lnew->tail=pre1;
}
else
{ //用尾插法把链表1的孤立结点插到新链表中
lnew->tail->next=pre1;
lnew->tail=pre1;
}
p1=p1->next;
p2=p2->next; //孤立结点被使用过的链表的游标后移(这里两个都被使用过)
pre1->next=NULL;
pre2->next=NULL; //链表中被使用过的的孤立结点结点指向NULL(这里两个都被使用过)
free(pre2); //释放没有进入新链表的孤立结点
}
}
lnew->tail->next=(p1?p1:p2); //用尾插法把非空的链表的剩余部分整个接到新链表中
return lnew; //返回新链表
}
12.逆序存储一个整数,实现加法
整数1:1 2 3 4
整数2:3 4 5 6
得到的链表:
4 6 9 0
代码运行结果:
// 7 8 9 1 5 9
+ 2 1 1 + 3 5 7
= 1 0 0 0 = 5 1 6
int getdate(Node *head) //取出结点中的数
{ //1.写函数专门取出结点中的数
if(head==NULL)
{
return 0;
}
return head->date; //函数返回结点中的数
}
LNode *ADD(LNode *list1,LNode *list2)
{ //2.新建一个链表
LNode *add=(LNode *)malloc(sizeof(*add)); //给新链表分配空间
add->head=NULL;
add->tail=NULL;
//3.为计数做准备
int x=0,c=0,sum=0;
Node *p1=list1->head;
Node *p2=list2->head;
Node *pre1=p1; //为了让循环多进行一次
Node *pre2=p2; //为了让循环多进行一次
//4.计数
while(pre1||pre2) //想办法让循环多进行一次
{
sum=(getdate(p1)+getdate(p2)); //得到两个结点中数字之和
x=(sum+c)%10; //得到两个结点中数字之和的低位
if((x==0)&&((p1==NULL)&&(p2==NULL))) //若p1和p2都为空 且 x为0;
{
return add; //直接返回新链表;
}
//if((x==0)&&(p1||p2)) 不等于 if((x==0)&&((p1==NULL)||(p2==NULL)))!!!
c=(sum+c)/10; //存储两个结点中数字之和的高位(进位)
Node *a=(Node *)malloc(sizeof(*a)); //生成新的结点
a->next=NULL; //让新结点孤立
a->date=x; //把低位放进新生成的结点
if(add->head==NULL)
{
add->head=a;
add->tail=a;
}
else
{ //用头插法生成新链表
a->next=add->head;
add->head=a;
}
pre1=p1; //想办法让循环多进行一次
pre2=p2; //想办法让循环多进行一次
if(p1)
{
p1=p1->next; //若p1已经为NULL,再让p1=p1->next就会段错误!!很危险
}
if(p2)
{
p2=p2->next; //若p2已经为NULL,再让p2=p2->next就会段错误!!很危险
}
}
return add; //返回新生成的链表
}