采用的时谭c上面的例题:
输入一系列学生的成绩,规定学生的学号不为0,按照学号 成绩的顺序输入数据 要求用函数完成传建链表和输出链表的操作
代码:(自闭了好一会二才看懂书上的代码)
(复制到DEV C++里面,主要看注释)
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
//#define NULL 0 程序中就被宏定义了
#define LEN sizeof(struct student)
struct student{//表明链表中的每个成员都应该长这个样子
long num;
float score;
struct student *next;//用来存放下一个节点的地址
};
int n;//计数器用来判断是否为头指针,也可以用来计数人数
//这是个用来创建链表的函数
struct student *create(void)//这是一个名叫create的操作型函数,它的返回值的类型是struct student的指针类型,其实就是链表的头指针
{
struct student *Head;
struct student *p1,*p2;
n = 0;
//void *malloc(unsigned int size)这是一个返回值为指针的函数,会返回所分配区域的第一个字节的地址即开头的位置
//但是返回的指针为不指向任何类型的数据的指针 (如果分配内存不成功就会返回NULL指针)
//强制将返回的指针转化为struct *student 类型的指针,即指向某个结构体的指针,才可以赋给相同类型的p1和p2
p1 = p2 = (struct student*)malloc(LEN);//LEN即为分配的内存的长度,且这部分的内存空间连续 ,p1和p2均有了地址,相当于此时的p1和p2就是两个结构体
//虽然p1和p2指向的那一块内存没有按照成员列表分割为一块一块的但是通过结构体指针的成员来分别指向这块内存的不同区域
//p1和p2均为指向struct studet类型的指针,且分配好了地址 ,且均指向这块区域
scanf("%ld %f",&p1 -> num,&p2 -> score);//因此可以这样直接向p1和p2里面读入数据
Head = NULL;//将Head指向空
//while外面的区域其实就相当于将头指针指向的区域孤立出来
while(p1 -> num != 0)//只要输入给无名区域的num的值不是0
{
n = n + 1;//则节点数加一
if(n == 1) Head = p1;//如果这是第一个节点,就把头指针指向这块区域
else p2 -> next = p1;//如果不是第一个节点,就将p2的next指向在上一个循环里面建立的p1所指向的新创建的结构体区域
p2 = p1;//p2再次指向和p1相同的区域,存放这块区域的地址,因为p1马上要指向另一块新创建的区域,对于头节点的情况,这句话其实是不起作用的
p1 = (struct student *)malloc(LEN);//p1指向一块新创建的长度为结构体长度的另一块无名区域,p1成为一个新的结构体
scanf("%d %f",&p1 -> num,&p1 -> score);//向以后的节点重复性读入数据
}
p2 -> next = NULL;//如果跳出循环,则该链表完成建立,将最后一个节点的next成员指向NULL
return (Head);//返回头指针Head
}
//现在来写输出链表的函数
void print(struct student * head)
{
struct student * p;
printf("\nNow These %d records are : \n",n);
p = head;
if(head != NULL)//只要头指针不指向空,即链表非空就执行输出链表的操作
{
do
{
printf("%ld %5.1f\n",p -> num,p -> score);
p = p -> next;//每次输出后就把p指向下个节点
}while(p != NULL);//一直进行到最后一个节点时,其next指向空,结束输出
}
}
int main(void)
{
struct student *pt;
pt = create();
print(pt);
}
用图来解释p1和p2的移动轨迹:
添加在链表内删除指定节点和插入节点的函数:
1.删除指定的节点
代码:
struct student *del(struct student *p1,long long int Num)//num就是待删除节点内的成员的数值
{
struct student *p1,*p2;
if(head == NULL){
printf("\n list null! WDNMD \n");
goto end ;
}
p1 = head ;
while(Num != p1 -> num && p1 -> next !=NULL)//遍历寻找待删除的节点
{
p2 = p1 ;//存放p1上一个节点的地址,因为p1马上就要指向此处p1所指向的下一个结构体
p1 = p1 -> next ;//同上的解释
}
if(Num == p1 -> num)//找到待删除的节点p1
{
if(p1 == head)
{
head = p1 -> next ;//如果待删除的节点就是头指针指向的节点,就把头指针向后移动一个节点即可
}else{
//如果待删除的节点不是头节点,就分开讨论
p2 -> next = p1 -> next ;
//p2为p1的上一个节点,把它指向p1的下一个节点,以后对该链表的操作就会跳过p1,其实并没有将p1在内存中删除
//但还是相当于达成删除的目的
printf("delete :%d\n",Num) ;
n = n - 1 ;//总的元素数目减一
}
}else{
printf("%ld has not been found\n",Num);
end : return (head) ;
}
}
2.在链表中插入节点的操作:
struct *insert(struct student *head,struct student *stud)//stud为待插入的节点 ,且要插入到一个比这个元素小的链表节点前
{
struct student *p1,*p2,*p0 ;
p1 = head ;
p0 = stud ;
if(head == NULL)//如果链表最开始就为空的状态
{
{
head = p0 ;//head就指向p0,相当于向链表中添加一个新的元素
p0 -> next = NULL;//head以后再无元素,则其next指向空 NULL
}
}
else//如果链表最开始就非空
{
while(((p0 -> num)>(p1 -> num))&&(p1 -> next != NULL))//即要求待插入节点的成员的值要大于其之前的节点的成员的值,
//小于其后节点的成员的值,且p1后面任然有链表元素存在
{
p2 = p1 ;//暂存p1上一个节点的地址,因为p1马上要指向其后的一个节点
p1 = p1 -> next;//将p1指向p1之后的一个节点
}
}
if(p0 -> num <= p1 -> num)//待插入节点的值小于等于其后一个节点的值
{
if(p1 == head)//如果p1是头指针,说明待插入节点应该在头指针之前插入
{
head = p0;//头指针改为p0
p0 -next = p1;//p0的下个节点指向老的头指针
}
else if(p1 -> next != NULL)//如果p1之后还有链表元素,即p1不是指向链表的末尾
{
p2 -> next = p0;//p2是p1上一个节点的地址,其寻找指向待插入节点
p0 -> next = p1;//待插入节点的下一个节点指向现在的p1,完成在p1之前插入一个节点
}
else//此时p1指向的就是链表的末尾,则待插入的节点应该在链表的末尾插入
{
p1 -> next = p0;//则p1指向待插入的节点
p0 -> next = NULL;//待插入节点的下一个节点就指向NULL,现在的待插入节点成为新的链表的末尾
}
}
n = n + 1;//链表元素的总数加一
return (head);//返回头指针的地址
}
删除和插入的这两个函数并没有实践,可能存在错误
实践代码:
为了确保链表的头节点可以被删掉,应该把删除节点的函数的类型定义为返回值为结构体指针的类型,否则头结点改变后,void类型不能返回新的头节点。
//rewrite the lianbiao
#include<bits/stdc++.h>
#define LEN sizeof(struct node)
using namespace std;
struct node{
int num;
float score;
struct node *next = NULL;
};
int n;
struct node *create_List(void)
{
struct node *newnode = NULL,*fomernode = NULL,*Head = NULL;
n = 0;
newnode = fomernode = (struct node*)malloc(LEN);
scanf("%d %f",&newnode->num,&newnode->score);
while(newnode->num!=0)
{
n = n + 1;
if(n == 1)
{
Head = newnode;
}
else
{
fomernode->next = newnode;
}
fomernode = newnode;
newnode = (struct node*)malloc(LEN);
scanf("%d %f",&newnode->num,&newnode->score);
}
fomernode->next =NULL;
return Head;
}
void print_List(struct node *head)
{
struct node *p;
printf("These records are as follows:\n");
p = head;
if(head == NULL)
{
printf("NULL List error\n");
}
else
{
while(p!=NULL)
{
printf("%d %f\n",p->num,p->score);
p = p->next;
}
}
}
struct node *delete_Node(struct node *head,int num)
{
int n=0;
struct node *p = head;
struct node *fomernode;
while(p!=NULL)
{
n++;
if(p->num == num)
{
if(n==1)
{
head = head->next;
goto end;
}
else
fomernode->next = p->next;
goto end;
}
else
{
fomernode = p;
p = p->next;
}
}
printf("NOT the number error \n");
end: return head;
}
struct node* add_Node(struct node *head,int num,struct node *add_one)
{
struct node *p=head;
struct node *former;
int N = 0;
while(p != NULL)
{
N++;
if(p->num==num)
{
if(n==1)
{
add_one->next = head;
head->next = add_one;
goto end;
}
else if(p->next==NULL)
{
p->next = add_one;
add_one->next =NULL;
goto end;
}
else
{
add_one->next = p->next;
p->next = add_one;
goto end;
}
}
else
{
former = p;
p = p->next;
}
}
printf("NOT this number error\n");
end: return head;
}
int main(void)
{
struct node *pt;
pt = create_List();
print_List(pt);
int num;
scanf("%d",&num);
pt = delete_Node(pt,num);
print_List(pt);
int Num;
scanf("%d",&Num);
struct node adding_one;
scanf("%d %f",&adding_one.num,&adding_one.score);
pt = add_Node(pt,Num,&adding_one);
print_List(pt);
return 0;
}
删除和添加的片段函数的确是存在一点问题,重在理解思想,光背住板子是没有益处的
其实按照正规的要求,头结点不应该存放数据,其只能存放下一个节点的地址,否则在删除节点的时候会有很多麻烦
#include<bits/stdc++.h>
#define LEN sizeof(struct node)
using namespace std;
struct node
{
int numbe;
float score;
struct node *next;
};
int n = 0;
struct node *create_list(void)
{
struct node *newnode = NULL,*formernode = NULL,*head = NULL;
newnode = (struct node*)malloc(LEN);
head = newnode;
newnode =(struct node*)malloc(LEN);
scanf("%d %f",&newnode->numbe,&newnode->score);
while(newnode->numbe!=0)
{
n++;
if(n==1)
{
head->next = newnode;
}
else
{
formernode->next = newnode;
}
formernode = newnode;
newnode = (struct node*)malloc(LEN);
scanf("%d %f",&newnode->numbe,&newnode->score);
}
formernode->next=NULL;
return head;
}
void print_list(struct node *head)
{
struct node *p = head->next;
if(p == NULL)
{
printf("The list is NULL\n");
}
else
{
while(p!=NULL)
{
printf("%d %f\n",p->numbe,p->score);
p=p->next;
}
}
}
int main(void)
{
struct node *pt;
pt = create_list();
//printf("%d %f\n",pt->next->next->numbe,pt->next->next->score);
printf("END PHASE 1\n");
print_list(pt);
}
尝试写了一下双向链表(不太清楚定义,就多加了一个节点指向前一个节点)
代码:
//try build a double head list
#include<bits/stdc++.h>
#define LEN sizeof(struct node)
using namespace std;
struct node//建立结构体类型和声明链表中成员的模板
{
int number;
float score;
struct node *next;
struct node *before;
};
int n = 0;
struct node *create_List(void)//建立链表的函数
{
struct node *newnode = NULL,*formernode = NULL;
struct node *head = NULL;
newnode = (struct node*)malloc(LEN);
formernode = newnode;
scanf("%d %f",&newnode->number,&newnode->score);
while(newnode->number!=0)
{
n++;
if(n==1)
{
head = newnode;
head->before = NULL;
}
else
{
formernode->next = newnode;
newnode->before = formernode;
}
formernode = newnode;
//newnode->before = formernode;指向前一个节点的指针如果在这里,formernode已经变成了现在输入的新节点,
//则before还是指向现在的新节点,没有意义
newnode = (struct node*)malloc(LEN);
scanf("%d %f",&newnode->number,&newnode->score);
}
formernode->next = NULL;
return head;
}
void print_List(struct node *head)
{
struct node *p = head;
if(p==NULL)
{
printf("The list is empty\n");
}
else
{
while( p!= NULL)
{
printf("%d %f ",p->number,p->score);
if(p->before!=NULL)
{
printf("and the date bofore is:%d %f\n",p->before->number,p->before->score);
}
else
{
printf("and the date bofore is NULL\n");
}
p = p->next;
}
}
return ;
}
int main(void)
{
struct node *pt;
pt = create_List();
print_List(pt);
return 0;
}
运行后的效果: