链表是一种常见的重要的数据结构,它是动态地进行存储分配的一种结构,它可以根据需要开辟内存单元,链表有一个头指针变量,以head
表示,它存放一个地址,该地址指向一个元素,链表中每一个元素称为结点,每个结点都应该包括两部分:一为用户需要的实际数据,
二为下一个结点的地址,因此head指向第一个元素,第一个元素又指向第二个......知道最后一个,该元素不再指向其它元素,它称之为表尾,
它的地址部分存放一个NULL,链表到此结束。
链表的各类操作包括:学习单向链表的创建,删除,插入,输入,排序等
[p1]-->[p2]-->.......[pn]-->[NULL];
#include "stdio.h"
#define NULL 0
#define LEN sizeof(struct student)
struct student{
int num;
float score;
struct student *next;
};
int n;
struct student *Create()
{
struct student *head;
struct student *p1=NULL;//p1保存创建的新节点的地址
struct student *p2=NUL;//p2保存原链表最后一个节点的地址
n=0;
p1=(struct studen*)malloc(LEN);//开辟一个新节点
p2=p1;//如果节点开辟成功,则p2先把它的指针保存下来以备后用
if(p1==NULL)
{
return NULL;
}
else
{
head=NULL;
scanf("%d %f",&(p1->num),&(p1->score));//输入数据
}
while(p1->num!=0)
{
n+=1;
if(n==1)
{
head=p1;
p2->next=NULL;
}
else
{
p2->next=p1;//把p1指向上次下面刚刚开辟的新节点
}
}
p2=p1;//把p1的地址给p2保留,然后p1产生新的节点
p1=(struct student *)malloc(LEN);
scanf("%d %f",&(p1->num),&(p1->score));
}
p2->next=NULL;//此句就是根据单向链表的最后一个节点要指向NULL
free(p1);//p1->NULL为0的时候跳出了while循环,并排释放p1
p1=NULL;//特别不要忘了吧释放的变量清空置为NULL,否则就变成野指针,
return head;//返回创建链表的头指针
}
输出链表中节点的函数为:
void Printf(struct student *head)
{
struct student *p;
p=head;
if(head!=NULL)
{
printf("head is %0\n",head);
do{
printf("%0 %d %5.1f",p,p->num,p->sore,p->next);
p=p->next;
}while(p!=NULL);
}
}
单向链表的删除图:
图3:空链表
head 1->next 2->next n->next
--[2]....->[n]--->[NULL]
head 2->next n->next;
图4:有N个节点的链表,删除第一个节点,
结合原链表和删除后的链表,就很容易写出相应的代码,操作方法如下:
有N个节点的链表,删除第一个节点
结合原链表和删除后的链表,就很容易写出相应的代码,操作方法如下:
1.你要明白head就第一个节点,head->next就是第二个节点
2.删除后head指向第二个节点,就射让head=head->next,OK这样就行了
-->[1]-->[2]-->[3].......--->[n]--->[NULL]
head 1->next 2-->next n->next
--->[2]....--->[n].....[NULL]
strtuct student *Del (Struct student *head,int num){
struct student *p1;
struct student *p2
if(head==NULL){
return head;
}
while(p1->num!=num&&p1->next!=null){
p2=p1;
p1=p1->next;
}
if(p1->num==num){
if(p1==head)
{
head=p1->next;
}
else
{
p2->next=p1->next;
}
free(p1);
p1=NULL;
n-=1;
}
else
{
}
return head;
}
链表插入一个节点
结合原链表和插入后的链表,就很容易写出相应的代码,操作方法:
1.你要明白空链表head指向NULL就是head=NULL;
2.插入后head指向第1个节点,就是让head=1,1->next=NULL,ok这样就行了
struct student *Insert(struct student *head,int num,struct student *node)
{
struct student *p1;
if(head==NULL){
head=node;
node->next=NULL;
n+=1;
return head;
}
p1=head;
while(p1->num!=num&&p1->next!=NULL)
{
p1=p1->next;
}
if(p1->num==num)
{
node->next=p1->next;
p1->next=node;
n+=1;
}
else
{
}
return head;
}
单向链表的反省图:
--->[1]---->[2].......---->[n]----->[NULL](原链表)
[NULL]<---[1]<---[2]<----[3]<----[n]<---(反序后的链表)
结合原链表和插入后的链表,就很容易写出相应的代码。操作方法如下:
1.我们需要一个读原链表的指针p2,存在反序链表的p1=NULL(刚好最后一个节点的next为NULL),还有一个临时存储变量p
2.p2在原链表中读出一个节点,我们就把它放到p1中,p就是用来处理节点放置顺序的问题;
3.比如,现在我们取得一个2,为了我们继续往下取节点,我们必须保持它的next值,由原链表可知 p=2->next;
4.然后由反序的链表可知,反序后2->next 要指向1,则2-->next=1;
5.好了,现在已经反序一个节点,接着处理下一个节点就需要保存此时信息:
p1变成刚刚加入的2.即p2=2;p2要变成他的下一节点,就是上面我们保存的p,即p2=p;
struct student *Reverse(struct student *head)
{
struct student *p;
struct student *p1;
struct student *p2;
p1=NULL;
p2=head;
while(p2!=NULL){
p=p2->next;
p2->next=p1;
p1=p2;
p2=p;
}
head=p1;
return head;
}
对链表进行选择排序的基本思想就是反复从还未排好的那些节点中,选出键值最小的节点,一次重新组合成一个链表
我认为写链表这类程序,关键是理解:head存储的第一个节点的地址,head->next存储的是第二个节点的地址,任意一个节点
p的地址,只能通过它前一个节点的next来求得
单向链表的选择排序图示:
---[1]---->[3]-----[2]---....--[NULL](原链表)
head 1->next 3--next 2->next n->next-->[NULL](空链表)
--->[1]--->[2]----[3]----[n]--->[NULL](排序后链表)
1.先在原链表中找最小的,找到一个后就把它放到另外一个空链表中;
2.空链表汇总安放第一个进来的节点,产生一个有序链表,并且让它在原链表中分离出来,
3.继续在原链表中找一个最小的,找到后把它放入有序链表的尾指针的next,然后它变成尾指针;
struct student *SelectSort(struct student *head)
{
struct student *first;//排列后有序链表头指针
struct student *tail;
struct student *p_min;
struct student *min;
struct student *p;
first =NULL;
while(head!=NULL)
{
for(p=head,min=head;p->next!=NULL;p=p->next){
if(p->next->num<min->num)
{
p_min=p; //保存找到的前驱节点,显然p->next
min=p->next; //保存键值更小的节点
}
}
///上面的for语句结束后,就要做两件事;一是把它放在有序链表中,二是根据相应的条件判断,安排它离开原来链表
if(first==NULL)
{
first=min; //第一次找到键值最小的节点
tail=min; //z注意:尾指针指向最后的一个节点
}
else
{
tail->next=min; //把刚找到的最小节点放到最后,即让尾指针的next指向它
tail=min; //尾指针也要指向它
}
if(min==head){ //如果找到的最小节点就是第一个节点
head=head->next; //显然让head指向原head->next,即第二个节点就OK
}
else
{
p_min->next=min-->next;//前次最小节点的next指向当前min的next,这样就让min离开了原链表
}
if(first!=NULL) //循环结束得到有序链表first
{
tail->next=NULL;//单向链表的最后一个节点的next应该指向NULL
}
head=first;
return head;
}
}
对链表进行直接插入排序的基本思想就是结束链表的前面n-1节点是已经按键值,排好的,对于节点n在这个系列镇南关插入位置,使得n插入后新系列依然
按照这种思想,依次对链表从头到尾执行一笔那,就可以是变为有序链表
单向链表的直接插入排序图:
--->[1]----->[3]----[2].......----[n]----[NULL](原链表)
head 1->next 3->next 2->next n->next
图13:有n个节点的链表直接插入排序
1.现在原链表中易第一个节点为一个有序链表,其余节点为待定节点
2.从图12链表中取节点,到图11链表中定位插入
3.上面图示虽说画两条表,其实只有一条链表,在排序汇总,实质只增加了一个拥有指向剩余需要的排序节点的头指针first 罢了,
功能:直接插入排序(由小到大)
struct student *InsertSort(struct student *head)
{
struct student *first; //为原链表剩余下用于直接插入的节点头指针
struct student *t; //临时指针变量:插入节点
struct student *p, //临时指针变量
first =head->next;//原链表剩余下用于直接插入排序节点链表
head->next=NULL;
while(first!=NULL)
{
//注意:这里for语句就是体现指针插入排序思想的地方,无系节点在有序链表中找插入的位置
//退出for循环,就是找到了插入的位置,应该讲t节点插入到p节点之后,q节点之前
//注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能,原因:
你若理解了上面的第三条,就知道了
//下面的插入就是将t节点即是first节点插入到q节点之后,已经改变了first节点,所以first节点应该在被修改之前往后
移动,不能放到下面注释的位置上去
for(t=first,q=head;((q!=NULL)&&(q->num<t->num);p=q,q=q->next))
{
frist=first->next;
if(q==head) //差在第一个节点之前
{
head=t;
}
else //p是q的前驱
{
p->next=t;
}
t->next=q;
}
return head;
}
对链表进行冒泡排序的基本思想就是对的丧气还未排好的范围内的全部节点,自上而下
对相邻的两个结点一次进行比较调整,让键值较大的节点往下沉,键值较小的往上冒
单向链表的冒泡排序:
--->[1]----->[3]------>[2].......------[n]----->[null](原链表)
head 1-->next 3->next 2->next n->next
---->[1]--->[2]----->[3]......---->[n]---->[NULL](排序后链表)
有N个节点的链表冒泡排序
1.排序后q节点指向p节点,在调整指向之前,我们要保存原p的指向节点地址,即:p2=p1->next->next;
2.顺着这一步一步往下推,排序后p1->next->next 要指向的是p2->next ,所以p1->next->next=p2->next;
3.在p2->next 原来q发出来的指向,排序后q的指向要变为指向p的,而原来p1->next是指向p的,所以p2->next=p1->next;
4.在图15中p1->next 原来是指向p的,排序后图16中的p1->next 要指向q,原来p1->next->next是指向q的,所以p1->next=p2;
5.至此,我们完成了相邻两节点的顺序交换。
6.下面的程序描述改进了一点就记录了每次最后一次节点下沉的位置,这样我们不必每次都从头到尾扫描,只要扫描到记录点为止,因为后面的都已经排好系了;
功能:冒泡排序(由小到大)
struct student *BubbleSort(struct student *head)
{
struct student *endpt;
struct student *p;
struct student *p1,*p2;
p1=(struct student *)malloc(LEN);
p1->next=head;
head=p1;
for(endpt=NULL;endpt!=head;endpt=p)
{
for(p=p1=head;p1->next->next!=endpt;p1=p1->next)
{
if(p1->next->num>p1->next->next->num)
{
p2=p1->next->next;
p1->next->next=p2->next;
p2->next=p1->next;
p1->next=p2;
p=p1->next->next;
}
}
}
p1=head;
head=head->next;
free(p1);
p1=NULL;
return head;
}
有N个节点的有序链表
插入node节点的位置有两种情况:一是第一个节点前,二是其它节点前后
---->[node]----->[1]------>[2]----->[3].....---->[n]----->[NULL]
head node ->next 1->next 2->next 3->next n->next;
node 节点插在第一个节点前
--->[1]---->[2]----->[3].......---->[node].....------>[n]---->[NULL]
head 1->next 2->next 3->next node->next n->next;
插入有序链表的函数为:
功能:插入有序链表的某个节点的后面(从小到大)
返回:指向链表表头的指针
struct student *SortInSert(struct student *head,struct student *node)
{
struct student *p; //p保存当前需要检查的节点的地址
struct student *t ;//临时指针变量
if(head==NULL)
{
head=node;
node->next=NULL;
n+=1;
return head;
}
p=head;
while(p->num<node->num&&p!=NULL)
{
t=p;
p=p->next;
}
if(p==head)
{
node->next=p;
head=node;
}
else
{
t->next=node;
node->next=p;
}
n+=1;
return head;
}