数组是常用的数据结构,但是有其局限性,以下来比较数组和链表的区别
- 数组的元素个数是固定的,而组成链表的结点个数可按需要增减;
- 数组元素的存诸单元在数组定义时分配,链表结点的存储单元在程序执行时动态向系统申请;
- 数组中的元素顺序关系由元素在数组中的位置(即下标)确定,链表中的结点顺序关系由结点所包含的指针来体现。
- 对于不是固定长度的列表,用可能最大长度的数组来描述,会浪费许多内存空间。
- 对于元素的插入、删除操作非常频繁的列表处理场合,用数组表示是不适宜的。若用链表实现,会使程序结构清晰,处理的方法也较为简便。
链表通常是比可变数组要好用的。
链表图示:
结合上图我们可以很清楚地看到,对于一个链表,它一共需要两种类型地变量,一个数据项,一个指向下一项的指针。
- 在结构体对象中通过指针域与下一个对象链接,从而形成链表;
代码如下:
struct Node {
int num;
float score; //数据域
struct Node* next; //指针域
};
创建链表:
struct Node* createList() //创建链表
{
n = 0; //初始化节点
struct Node* head =NULL; //创建结构体头指针变量
struct Node* p1, * p2;
p1 = p2 = (struct Node*)malloc(LEN);
printf("输入学生的学号\n");
scanf_s("%d", &p1->num);
printf("输入学生的成绩\n");
scanf_s("%f", &p1->score);
while (p1->num!=NULL)
{
n++; //记录节点
if (n == 1) //当输入的是第一个节点时
{
head = p1; //则让表头直接赋值为p1
}
else
{
p2->next = p1; //通过p2将p1开辟的新节点链接起来
}
p2 = p1; //链接完p2得跟上p1的位置
p1= (struct Node*)malloc(LEN); //p1重新开辟新节点(新的对象)
printf("输入学生的学号\n");
scanf_s("%d", &p1->num);
printf("输入学生的成绩\n");
scanf_s("%f", &p1->score);
}
p2->next = NULL; //添加结束后p1已经消失,p2也不在继续链接p1
free(p1);
return head;
}
定义一个变量n来记录节点,定义两个结构体指针p1、p2,p1负责开辟新节点,p2是为了方便p1不在开辟新节点后销毁p1所指向的内存(指针p1还在),最后由p2来代表链表尾。由结构体中的num来判断是否继续建立新节点,当num为0或NULL时,p1指针不在开辟新节点。
在这要补充一点,必须得创建结构体指针函数去返回一个指针,来让main函数中的结构体指针变量去接收函数所返回的指针,才能保证链表得到创建。
打印链表:
void print(struct Node* head) //打印链表函数
{
struct Node* p=head;
if (head!=NULL) //如果传入链表不为空
{
do
{
printf("第%d学生的成绩为:%f\n", p->num, p->score);
//判断最后p->next是不是最后的链接指针(即链表里的p2->next=NULL)
p = p->next;
} while (p!=NULL);
}
else
{
printf("链表为空表\n");
}
}
把链表头传入函数由p来接收,head不为空则遍历链表,打印当前节点的值。
删除链表:
struct Node* Del (struct Node* head,int num)
{
struct Node* p1, * p2=NULL;
if (head == NULL)
{
printf("该链表为空链表\n");
return NULL;
}
p1 = head;
//当p1->num不是所需要删除的序号并且不是p1->next不是末尾节点
while (p1->num != num && p1->next != NULL)
{
p2 = p1; //给p2赋值等于p1
p1 = p1->next; //p1向下查找
}
if (p1->num == num) //查询出需要删除的序号
{
if (p1 == head) //若是所需要删除的p1查到是头节点时,给头节点赋值p1->next覆盖头节点
{
head = p1->next;
}
else
{
p2->next = p1->next; //若需要删除的不是头节点时,p2中的next直接赋值p1中的next
}
printf("已将需要删除的%d链表删除\n",num);
free(p1);
n--; //记录节点的变量也跟着减少
}
else
{
printf("未找到需要删除的链表");
}
return head;
}
创建p1、p2,让p1=head,通过传入的num去与当前p1->num去比较,直到寻找到所需要删除的那个节点中的num,p2是用来与p1->next链接的,所以p2与p1是相邻的。
删除链表
链表插入:
struct Node* insert(struct Node* head, struct Node* str) //链表插入
{
struct Node* p0, * p1, * p2=NULL;
p0 = str;
p1 = head;
if (head == NULL) //假链表直接替代
{
head = p0;
p0->next = NULL;
}
else //真链表
{
//判定条件(p0的序号是否大于p1的)并且(p1还不是最后的节点)
while ((p0->num > p1->num) &&(p1->next!=NULL))
{
p2 = p1;
p1 = p1->next;
}
if (p0->num <= p1->num) //查找到可以插入的地址后,即p0序号小于p1的序号
{
if (p1 == head) //当p1就是头指针时,即p0序号比谁都小
{
head = p0; //p0->next = head; 然后return p0;
}
else //普通情况
{
p2->next = p0; //头插法
}
p0->next = p1;
}
else //没找到插入点时,即p0是最大的
{
p1->next = p0;
p0->next = NULL;
}
}
n++;
return head;
}
这里用到的是头插法
完整代码:
#include<stdio.h>
#include<stdlib.h>
#define LEN sizeof(struct Node)
static int n; //记录链表节点
struct Node {
int num;
float score; //数据域
struct Node* next; //指针域
};
/*这里用一个结构体来存放head节点和tail节点,直接通过结构体变量去访问
链表头会更方便。
struct _List {
struct Node *head;
struct Node *tail;
};
*/
struct Node* createList() //创建链表
{
n = 0; //初始化节点
struct Node* head =NULL; //创建结构体头指针变量
struct Node* p1, * p2;
p1 = p2 = (struct Node*)malloc(LEN);
printf("输入学生的学号\n");
scanf_s("%d", &p1->num);
printf("输入学生的成绩\n");
scanf_s("%f", &p1->score);
while (p1->num!=NULL)
{
n++; //记录节点
if (n == 1) //当输入的是第一个节点时
{
head = p1; //则让表头直接赋值为p1
}
else
{
p2->next = p1; //通过p2将p1开辟的新节点链接起来
}
p2 = p1; //链接完p2得跟上p1的位置
p1= (struct Node*)malloc(LEN); //p1重新开辟新节点(新的对象)
printf("输入学生的学号\n");
scanf_s("%d", &p1->num);
printf("输入学生的成绩\n");
scanf_s("%f", &p1->score);
}
p2->next = NULL; //添加结束后p1已经消失,p2也不在继续链接p1
free(p1);
return head;
}
void print(struct Node* head) //打印链表函数
{
struct Node* p=head;
if (head!=NULL) //如果传入链表不为空
{
do
{
printf("第%d学生的成绩为:%f\n", p->num, p->score);
p = p->next;
} while (p!=NULL); //判断最后p->next是不是最后的链接指针(即链表里的p2->next=NULLS)
}
else
{
printf("链表为空表\n");
}
}
//删除链表,用二级指针来接收(&head头指针)的地址,否则最后return出的head不是原本传入的head
struct Node* Del (struct Node* head,int num)
{
struct Node* p1, * p2=NULL;
if (head == NULL)
{
printf("该链表为空链表\n");
return NULL;
}
p1 = head;
while (p1->num != num && p1->next != NULL) //当p1->num不是所需要删除的序号并且不是p1->next不是末尾节点
{
p2 = p1; //给p2赋值等于p1
p1 = p1->next; //p1向下查找
}
if (p1->num == num) //查询出需要删除的序号
{
if (p1 == head) //若是所需要删除的p1查到是头节点时,给头节点赋值p1->next覆盖头节点
{
head = p1->next;
}
else
{
p2->next = p1->next; //若需要删除的不是头节点时,p2中的next直接赋值p1中的next
}
printf("已将需要删除的%d链表删除\n",num);
free(p1);
n--; //记录节点的变量也跟着减少
}
else
{
printf("未找到需要删除的链表");
}
return head;
}
struct Node* insert(struct Node* head, struct Node* str) //链表插入
{
struct Node* p0, * p1, * p2=NULL;
p0 = str;
p1 = head;
if (head == NULL) //假链表直接替代
{
head = p0;
p0->next = NULL;
}
else //真链表
{
while ((p0->num > p1->num) &&(p1->next!=NULL)) //判定条件(p0的序号是否大于p1的)并且(p1还不是最后的节点)
{
p2 = p1;
p1 = p1->next;
}
if (p0->num <= p1->num) //查找到可以插入的地址后,即p0序号小于p1的序号
{
if (p1 == head) //当p1就是头指针时,即p0序号比谁都小
{
head = p0; //p0->next = head; 然后return p0;
}
else //普通情况
{
p2->next = p0; //头插法
}
p0->next = p1;
}
else //没找到插入点时,即p0是最大的
{
p1->next = p0;
p0->next = NULL;
}
}
n++;
return head;
}
int insetps(struct Node* head, int date, struct Node* newp) //静态链表指定插入
{
struct Node* p = head;
while (head)
{
if (p->num == date) //头插法是判断(p->next->num==date)才进行
{
newp->next = p->next; //尾插法
p->next = newp; //若是要修改的话则用 p->date=newd; (newd为需要修改的传参)
return 1; //成功输入1
}
p = p->next;
}
return 0; //没找到输入0
}
int main()
{
struct Node* stu, str;
stu = createList(); //接收表头
print(stu);
printf("输入需要删除的链表号\n");
scanf("%d", &n);
stu=Del(&stu, n); //删除,传入指针类型的地址,接收新表
print(stu);
//重新添加
printf("请输入要添加的信息\n");
printf("输入学生的学号\n");
scanf("%d", &str.num);
printf("输入学生的成绩\n");
scanf("%f", &str.score);
print(insert(stu, &str)); //插入和打印
return 0;
}