笔记中图片均来源课堂PPT
正在学习中,如果有错误欢迎指正!
一、链表的性质
链表也是线性表的一种,它与顺序表有着相似的性质,都是以线性的结构来储存数据,但是链表与顺序表不同之处在于:链表更借助于指针来储存元素。
详细来说,链表中的每个元素(习惯性地,我们把它叫做链点),都分为元素域和指针域,元素域用来存放元素的数据(比如int型的某个数据),而指针域用来指向下一个元素的位置。
所以,通过链表中每个链点的指针域,我们可以轻松的达到想去的任何一个元素,不必拘泥于顺序表必须一个一个按照前后顺序读取元素的泥沼中,方便了许多(好耶)
了解了组成链表的每个基本元素,我们来看构成整个链表还需要些什么。首先,我们想到的是,作为一个线性表,链表也应该有头有尾,指向头元素和尾元素指向指针是需要我们考虑的;其次,有了元素之后,还需要链表的长度,不然计算机会很迷茫(究竟需要多少个元素呢?我要一直循环下去吗?)
综上所述,链表的基本构成如下:
head:链表的头结点
tail:链表的尾结点(补充一下:其实我们定义链表的时候基本不定义尾结点,具体定义方法后面会说)
length:链表的长度
有了这些,我们就可以定义链表了。
二、链表的基本操作
1.链表的定义
用typedef先定义一个链点,然后在定义一个链表,最后将它们产生关系,结合起来,就成为了一个完整的链表。
链点:
typedef struct link{
int data; //data指该链点的元素域
struct link*next; //next指该链点的指针域
}mylink ;
链表:
typedef struct list{
link *head;
link *tail; //现在基本上大多数不定义尾指针
int length;
}mylist;
那么现在来解释下不定义尾指针也可以结束链表的方法:
其实很简单,只需使下一个元素为空元素即可,也就是说,令p->next=null,这样不仅减少一个指针的储存位置,还能更方便的使用链点的指针域,无需特殊处理。
2.链表的建立
根据前面的定义,建立链表时,我们首先需要的是:链表的长度,链表的头指针,以及将链表头指针和下一个数据连接起来的操作。
先引入一个malloc函数,这个函数的作用是分配数据所需的内存空间,并返回一个指向它的指针。听着有一些抽象,举个例子,假如我们有十个苹果,十个小朋友,那么平时我们分配空间就好像一口气给每个小朋友一个苹果,一次性发完;而malloc则是一个一个小朋友发放,如果小朋友需要,就给他苹果,这就是所谓的动态分配内存。
在动态分配内存中,每需要一个位置就要申请一次。没分配内存就写入数据一定是不可取的!
在使用malloc函数时,要加入一个头文件<stdlib.h>
创建链表的代码如下:
void createlist(mylink* p, mylist* q)
{
int i;
printf("How many numbers do you want to input:\n");
scanf("%d", &q->length);
q->head = (struct link*)malloc(sizeof(struct link));//创建头结点
p = (struct link*)malloc(sizeof(struct link));//申请第一个结点
q->head->next = p;//链接头结点和首结点
printf("Please input numbers:\n");
for (i = 0; i < q->length; i++)
{
scanf("%d", &p->num);
p->next = (struct link*)malloc(sizeof(struct link));//申请下一个节点
p = p->next;//将该节点和下一个节点连起来
}
}
3.链表的遍历
遍历操作比较简单。在遍历中,需要注意的有:
·首先需要重置链表,将链表回到头结点,然后进行遍历
·在利用循环遍历链表的时候,要记得每次都用指针指向下一个元素
void printlist(mylink* p,mylist*q)
{
int i;
printf("these numbers are:\n");
p = q->head->next;//由于第一个循环已经将链表移到末尾,所以这里要将链表移到首结点开始打印
for (i = 0; i < q->length; i++)
{
printf("%d ", p->num);
p = p->next;//指向下一个元素
}
}
4.链表的插入
问题描述:在位置为location的元素前插入新元素
问题分析:输入应该有链表list、link,位置location,新元素elem
插入一个新的元素时,首先要定位元素,其次要覆盖元素。定位元素比较简单,因为输入了location,所以直接用循环遍历就可以到达所插入元素的位置。接下来就是覆盖元素:
利用链表指针域的特性,可以容易地插入元素。先将前一个元素的next指针指向新元素,再将新元素的next指针指向后一个元素,就完成了插入的动作。
由于新元素也应该是链点,所以这里仍然需要定义一个链点以及链表,把新元素放到链表中,再插入到原来的链表之中,否则数据类型不统一,编译器会报错。
如果这个插入的元素要插到表首,因为location=1,它的前一个元素为list->head,所以不能用上述方法,应该单独列出这种情况,利用表首的next指针将此元素插进去。
代码如下:
void insertlist(mylink* p, mylist* q,int location,mylink *elem,mylist*elemlist)
{
int counter = 1;
p = q->head;//重置头指针
elem = elemlist->head->next;//插入表的第一个链点
if (location<0 || location>q->length)
{
printf("the location is wrong");//报错提示,插入位置错误
return;
}
if(location==1)//考虑边界条件:表首插入
{
elem = q->head->next;
q->head->next = elem;
}
while (counter <= location - 1 && p != NULL)
{
p = p->next;
counter++;
}
elem->next = p->next;//elem的指针指向p->next
p->next = elem;//把元素放入p->next中
q->length++;//长度加一
}
注意:当循环执行到表尾时,p 的值为NULL,p->next是悬空的值, 那么对于以下代码:
elem->next = p->next;
p->next = elem;
就是错误的,原因在于当元素是表尾插入时,p->next为null,这样写会导致系统崩溃。
对插入过程总结一下,可以更好的理解链表的特性:
①链表操作往往从表头开始,然后逐个找到所需要的节点
②链表指针小心使用,谨防覆盖丢失
③不能访问指针域为null的成员
④链表操作的有向性,以至于不能回退
5.链表的链点删除
问题描述:删除位置为location的链点 ;
问题分析:输入应有链表list、link,删除位置location
和插入元素类似,在删除元素时,首先应该找到该元素的位置(同样用循环遍历就可达到),问题在于如何删除元素。对比插入元素改变指针方向来达到目的的方法,删除元素同样可以用此方法。略加思索之后,可以想到,如果将删除位置之前元素的指针域直接指向删除位置之后元素,那么删除位置的元素就被孤立了,最后直接再释放该元素就成了!
很好,我们已经有了思路和想法,但是在敲代码之前,这里有一点需要注意:从链表上取下来的那个倒霉链点需要用一个临时指针保存起来,然后再释放,否则可能丢失。
void deletelist(mylink* p, mylist* q, int location,mylink*elem,mylist*elemlist)
{
int counter = 1;
p = q->head;
if (location<0 || location>q->length)
{
printf("\nthe location is wrong\n");//删除位置错误
return;
}
while (counter <= location - 1 && p != NULL)//找到该元素
{
p = p->next;
counter++;
}
elem = p->next;
p->next = p->next->next;//删除的核心语句
free(elem);//释放elem的空间
q->length--;
}
【思考】如果希望删除值为x的元素,如何实现?(思路见习题部分)
二、链表的练习题
1.1)首先创建一个单链表:从键盘读入五个整数,按输入顺序形成单链表。将创建好的链表元素依次输出到屏幕上。
2)在已创建好的链表中插入一个元素:从键盘读入元素值和插入位置,调用插入函数完成插入操作。然后将链表元素依次输出到屏幕上。
3)在已创建好的链表中删除一个元素:从键盘读入欲删除的元素位置(序号),调用删除函数完成删除操作。然后将链表元素依次输出到屏幕上。
问题分析:该问题分为四个部分,分别为创建、输入链表,插入链点,删除链点,遍历链表。以上几个部分在之前的内容都有介绍过,现在只需要把它们集合在一起,并配上一个main函数即可。不多说了,代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef struct Link
{
int num;
struct Link* next;
}mylink;
typedef struct list {
Link* head;
Link* tail;
int length;
}mylist;
void createlist(mylink* p, mylist* q)
{
int i;
q->length = 5;
q->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
p = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
q->head->next = p;//链接头结点和首结点
printf("Please input numbers:\n");
for (i = 0; i < q->length; i++)
{
scanf("%d", &p->num);
p->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
p = p->next;//将该节点和下一个节点连起来
}
}
void printlist(mylink* p, mylist* q)
{
int i;
printf("these numbers are:\n");
p = q->head->next;//由于第一个循环已经将链表移到末尾,所以这里要将链表移到首结点开始打印
for (i = 0; i < q->length; i++)
{
printf("%d ", p->num);
p = p->next;
}
}
void insertlist(mylink* p, mylist* q, int location, mylink* elem, mylist* elemlist)
{
int counter = 1;
p = q->head;
elem = elemlist->head->next;
if (location<0 || location>q->length)
{
printf("the location is wrong");
return;
}
while (counter <= location - 1 && p != NULL)
{
p = p->next;
counter++;
}
elem->next = p->next;
p->next = elem;
q->length++;
}
void temp(mylink* elem, mylist* elemlist)
{
printf("please input the elem\n");
elemlist->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
elemlist->length = 1;
elem = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
elemlist->head->next = elem;//链接头结点和首结点
for (int i = 0; i < elemlist->length; i++)
{
scanf("%d", &elem->num);
elem->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
elem = elem->next;//将该节点和下一个节点连起来
}
elem = elemlist->head->next;//重置
}
void deletelist(mylink* p, mylist* q, int location, mylink* elem, mylist* elemlist)
{
int counter = 1;
p = q->head;
if (location<0 || location>q->length)
{
printf("\nthe location is wrong\n");
return;
}
while (counter <= location - 1 && p != NULL)
{
p = p->next;
counter++;
}
elem = p->next;
p->next = p->next->next;
free(elem);
q->length--;
}
int main()
{
mylist list;
mylink link;
createlist(&link, &list);
printlist(&link, &list);
printf("\nplease input the insert location\n");
int t = 0;
mylink elem;
mylist elemlist;
scanf("%d", &t);
temp(&elem, &elemlist);
insertlist(&link, &list, t, &elem, &elemlist);
printlist(&link, &list);
printf("\nplesae input the delete location\n");
scanf("%d", &t);
deletelist(&link, &list, t, &elem, &elemlist);
printlist(&link, &list);
return 0;
}
2.对一个元素有序(升序)排列的顺序表,任意给出一个数,要求插入到顺序表的正确位置,让插入后的顺序表也是有序(升序)排列的。该题中没有提供插入元素的具体位置,要根据元素值大小寻找合适的位置。
问题分析:对于该题,要首先找到插入元素的位置,然后直接调用insert函数,就可以简单的将元素插入进去。找元素的位置时,可以利用循环遍历,if语句判断元素的值来寻找元素的位置。
#include <stdio.h>
#include <stdlib.h>
#define maxnum 20
typedef struct Link
{
int num;
struct Link* next;
}mylink;
typedef struct list {
Link* head;
Link* tail;
int length;
}mylist;
void createlist(mylink* p, mylist* q)
{
printf("How many numbers do you want to input:\n");
scanf("%d", &q->length);
int i;
q->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
p = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
q->head->next = p;//链接头结点和首结点
printf("Please input numbers:\n");
for (i = 0; i < q->length; i++)
{
scanf("%d", &p->num);
p->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
p = p->next;//将该节点和下一个节点连起来
}
}
void printlist(mylink* p, mylist* q)
{
int i;
printf("these numbers in the list are:\n");
p = q->head->next;//由于第一个循环已经将链表移到末尾,所以这里要将链表移到首结点开始打印
if (q->length < 0)
{
printf("no data in the list");
return;
}
for (i = 0; i < q->length; i++)
{
printf("%d ", p->num);
p = p->next;
}
}
int searchlist(mylink* p, mylist* q, int num)
{
int location, i;
i = 1;
p = q->head;
while (i <= q->length && p != NULL)
{
if (p->num <= num && p->num > num)
{
location = i;
return location + 1;
}
i++;
}
return q->length;
}
void temp(mylink* elem, mylist* elemlist, int num)
{
elemlist->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
elemlist->length = 1;
elem = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
elemlist->head->next = elem;//链接头结点和首结点
for (int i = 0; i < elemlist->length; i++)
{
elem->num = num;
elem->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
elem = elem->next;//将该节点和下一个节点连起来
}
elem = elemlist->head->next;//重置
}
void insertlist(mylink* p, mylist* q, int location, mylink* elem, mylist* elemlist)
{
location = q->length;
int counter = 1;
p = q->head;
elem = elemlist->head->next;
if (location<0 || location>q->length)
{
printf("the location is wrong");
return;
}
while (counter <= location && p != NULL)
{
p = p->next;
counter++;
}
elem->next = p->next;
p->next = elem;
q->length++;
}
int main()
{
mylink link;
mylist list;
createlist(&link, &list);//要求输入的表是升序的
printlist(&link, &list);
int num, location;
printf("\nplease input the searching number\n");
scanf("%d", &num);
location = searchlist(&link, &list, num);
mylink elem;
mylist elemlist;
temp(&elem, &elemlist, num);
insertlist(&link, &list, location, &elem, &elemlist);
printlist(&link, &list);
}
3.1)创建一个单链表,其数据元素为整数,从键盘输入,输入0结束(注意0不放到链表内);
2)从键盘任意输入一个整数,在单链表中查询该数,如果单链表中已经存在这个数,就调用删除函数,删除该元素所在结点,并将单链表在删除前后的数据元素依次输出到屏幕上;
如果单链表中不存在这个数,就调用插入函数,将这个数插入到单链表尾,并将单链表在插入前后的数据元素依次输出到屏幕上。
问题分析:首先是创建链表,和前一章的顺序表类似,在创建链表的时候利用if语句判断,如果元素为0,就停止输入。根据题意,之后要查找元素 ,和第二题用类似的方法即可,遍历循环寻找元素位置,并返回位置的值,如果元素不在链表中,就返回0,再用if语句判断接下来的操作。最后的插入和删除只需套入前面的模板就可以完成。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#define maxnum 20
typedef struct Link
{
int num;
struct Link* next;
}mylink;
typedef struct list {
Link* head;
int length;
}mylist;
void createlist(mylink* p, mylist* q)
{
q->length = 0;
int i;
int elem = 0;
q->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
p = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
q->head->next = p;//链接头结点和首结点
printf("Please input numbers:\n");
for (i = 0; i < maxnum; i++)
{
scanf("%d", &elem);
if (elem == 0)
{
p->num = NULL;
break;//问题在于我已经申请空间了,但是不想打印这个位置,这个位置就会随机数,用null解决
}
p->num = elem;
p->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
p = p->next;//将该节点和下一个节点连起来
q->length++;
}
}
void printlist(mylink* p, mylist* q)
{
int i;
printf("these numbers in the list are:\n");
p = q->head->next;//由于第一个循环已经将链表移到末尾,所以这里要将链表移到首结点开始打印
if (q->length < 0)
{
printf("no data in the list");
return;
}
for (i = 0; i < maxnum; i++)
{
if (p->num == 0)
{
break;
}
printf("%d ", p->num);
p = p->next;
}
}
int searchlist(mylink* p, mylist* q, int num)
{
int location, i;
i = 1;
p = q->head;
while (i <= q->length && p != NULL)
{
if (num == p->num)
{
location = i;
return location-1;
}
p = p->next;
i++;
}
return 0;
}
void deletelist(mylink* p, mylist* q, int location, mylink* elem, mylist* elemlist)
{
int counter = 1;
p = q->head;
if (location<0 || location>q->length)
{
printf("\nthe location is wrong\n");
return;
}
while (counter <= location - 1 && p != NULL)
{
p = p->next;
counter++;
}
elem = p->next;
p->next = p->next->next;
free(elem);
q->length--;
}
void insertlist(mylink* p, mylist* q, int location, mylink* elem, mylist* elemlist)
{
location = q->length;
int counter = 1;
p = q->head;
elem = elemlist->head->next;
if (location<0 || location>q->length)
{
printf("the location is wrong");
return;
}
while (counter <= location && p != NULL)
{
p = p->next;
counter++;
}
elem->next = p->next;
p->next = elem;
q->length++;
}
void temp(mylink* elem, mylist* elemlist,int num)
{
elemlist->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
elemlist->length = 1;
elem = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
elemlist->head->next = elem;//链接头结点和首结点
elem->num = num;
elem->next = NULL;//申请下一个节点
elem = elemlist->head->next;//重置
}
int main()
{
mylink link;
mylist list;
createlist(&link, &list);
printlist(&link, &list);
int num,location;
printf("\nplease input the searching number\n");
scanf("%d", &num);
mylink elem;
mylist elemlist;
temp(&elem, &elemlist,num);
location = searchlist(&link, &list, num);
if (location != 0)
{
deletelist(&link, &list, location, &elem, &elemlist);
}
else
{
insertlist(&link, &list,location, &elem, &elemlist);
}
printlist(&link, &list);
}
4.找出链表中所有的负数并删除它们
问题分析:题中说所有的负数,在负数不止一个的情况下,需要每找到一个负数,就返回一个location值并调用一次delete函数。所以需要创建一个search函数,完成找负数->返回location值,再调用delete函数完成任务。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#define maxnum 30
typedef struct Link
{
int num;
struct Link* next;
}mylink;
typedef struct list {
Link* head;
Link* tail;
int length;
}mylist;
void createlist(mylink* p, mylist* q)
{
printf("How many numbers do you want to input:\n");
scanf("%d", &q->length);
int i;
q->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
p = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
q->head->next = p;//链接头结点和首结点
printf("Please input numbers:\n");
for (i = 0; i < q->length; i++)
{
scanf("%d", &p->num);
p->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
p = p->next;//将该节点和下一个节点连起来
}
}
void printlist(mylink* p, mylist* q)
{
int i;
printf("these numbers in the list are:\n");
p = q->head->next;//由于第一个循环已经将链表移到末尾,所以这里要将链表移到首结点开始打印
if (q->length < 0)
{
printf("no data in the list");
return;
}
for (i = 0; i < q->length; i++)
{
printf("%d ", p->num);
p = p->next;
}
}
void temp(mylink* elem, mylist* elemlist)
{
elemlist->head = (struct Link*)malloc(sizeof(struct Link));//创建头结点
elemlist->length = 1;
elem = (struct Link*)malloc(sizeof(struct Link));//申请第一个结点
elemlist->head->next = elem;//链接头结点和首结点
for (int i = 0; i < elemlist->length; i++)
{
// elem->num = num;
elem->next = (struct Link*)malloc(sizeof(struct Link));//申请下一个节点
elem = elem->next;//将该节点和下一个节点连起来
}
elem = elemlist->head->next;//重置
}
void deletelist(mylink* p, mylist* q,mylink* elem, mylist* elemlist)
{
int counter = 1;
p = q->head->next;
for (counter = 1; counter <= q->length; counter++)
{
if (p->next->num < 0 && p->next != NULL)
{
elem = p->next;
p->next = p->next->next;
free(elem);
q->length--;
}
else
{
if (p->next == NULL)
return;
p = p->next;
}
}
}
int main()
{
mylink link;
mylist list;
createlist(&link, &list);
mylink elem;
mylist elemlist;
temp(&elem, &elemlist);
deletelist(&link, &list, &elem, &elemlist);
printf("删除负数后,表为:");
printlist(&link, &list);
}
(这些代码中location=1的情况我好像没有写,这里要注意一下)
5.思考题
有了前面的铺垫,思考题也就变得简单了,我们只需要首先定位元素的位置,然后进行删除操作即可。定位根据循环遍历并返回location值,再调用delete函数,就可以完成任务。