链表由结点组成,结点由数据域和指针域组成。其中,数据域存放的就是数据元素,指针域存放下一个结点的地址。数据元素可以只有一个,也可以有多个不同类型的数据元素,甚至是数组。下图和代码来自《C Primer Plus》,该链表每个节结点同时含char类型和int类型。
#define TSIZE 45
struct film{
char title[TSIZE];
int rating;
struct film *next;//指针域
};
struct film head;//头指针
在上述表示中,头指针存储第一个结构的地址。头指针指向链表中的第一项。在上述的表示方式中,结构体的名字是struct film,每次使用该类型的指针,都要写全这个名字。
struct film *p=(struct film*)malloc(sizeof(struct film));//调用malloc,为新结构和新指针分配空间
事实上, 在以后的数据结构学习中,我们常用的是下面这种形式:
tyoedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
LNode L;
LinkList p=(LinkList)malloc(sizeof(LNode));
在这里,L是单链表的名字,p是一个同类型指针,但还未定义它指向谁。
虽然LNode和*LinkList是等价的,但还是常用上述表示方式。在某些编译器里,不这样表示可能会报错。
在后续学习中,头结点,头指针,第一个结点等概念也略有变化。以往,我们设L为LinkList型的变量,则L为单链表的头指针,它指向表中的第一个结点。若L为空,则所表示的线性表为空表,长度为0 。此后,在单链表的第一个结点前设一个结点,称之为头结点。头结点的指针域存储第一个结点的存储位置(即指向第一个结点的指针)。单链表的头指针指向头结点。若头结点的指针域为空,则单链表为空表。若单链表名为L,L就是头指针head,在不同参考资料中可能用L,也可能用head。下图是博主用ppt画的,上为空表,下为非空表。
在正式开始之前,我们不妨先熟悉一下单链表的遍历本身。
void DisplayLink(LinkList L)
{
LinkList p;
p=L;//链表名L,L也是头指针的名字,指向头结点。头结点的指针域指向第一个结点(即p->next)
while(p!=NULL)//注意一下代码的简洁易懂。p!=NULL比!(p=NULL)常用
{
printf(%d\n",&p->data);
p=p->next;//不能用p++,因为链表不是连续存储的
}
}
注意,遍历本身是不需要分配空间的,不需要调用malloc函数。LinkList类型的指针p本身就是用来指向LinkList类型结构体的。p指向需要插入链表中的新结点时,需要给p分配空间。
顺序表按位序查找比较简单,直接引用L.elem[i]即可。但链表查找必须从头开始
Status ListLocate(LinkList L,int i,ElemType &e)
{
int j;
LinkList p;//同理遍历,不要给p分配动态存储空间
p=L->next;//p存储第一个结点的地址
j=1;//(j=0)
while(p&&jnext;//循环结束时,p存储第i个结点的地址
j++;
}
if(!p||j>i) return ERROR;//i>表长||空表
e=p->data;
printf("第%d位是元素%d\n", i, e);
return OK;
}
在本节学习中,Status型函数比较常用,因为可以对操作失败做出返回值。你也可以用bool代替。
Status QueryNode(LinkList L,ElemType x)
{
LinkList p;int i=0;
p=L->next;
while(p!=NULL)
{
if(p->data=x)
{
i++;
printf("元素%d在链表第%d位\n",x,i);
return OK;
}
p=p->next;
}
return ERROR;
}
链表的删除和插入原理相同,重点都在于不要丢失结点地址
Status DeletList(LinkList &L,int i,ElemType &e)//删除第i个结点
{
LinkList p,q;//q指向第i个结点。循环结束时,p指向第i-1个结点
p=L;//p存储头结点的地址
int j=0;
while(p->next&&jnext;
i++;
}
if(!p->next||j>i-1) return ERROR;
//*****************************
q=p->next;
p->next=q->next;//第i-1个结点的指针域指向第i+1个结点
//*****************************
e=q->data;//用e存放q的数据域
free(q);//释放第i个结点
return OK;
}
Status ListInsert(LinkList &L,int i,ELemType e)//在第i个结点前插入新结点
{
LinkList p=L;
LinkList q;
int j=0;
while(p&&jnext;
j++;//循环结束时,p指向第i-1个结点
}
if(!p||j>i) return ERROR;
else
{
q=(LinkList)malloc(sizeof(LNode));
q->next=p->next;
p->next=q;
p->data=e;
}
return OK;
}
注意在第i位后插入元素的while循环条件不是while(p->next&&j 每个新结点都是头结点的新直接后继。 由图知,头插法的输入顺序为倒序 关键步骤:1.初始化一个空表 2.for循环逐个插入结点 for(i=n;i>0;i--)和for(i=n;i>0;--i)都表示递减循环,即从n开始,每次递减1,直到i小于等于0为止。 区别在于循环体中对i的更新位置不同。在for(i=n;i>0;i--)中,i表示先使用i的当前值进行循环体操作,然后再将i减1;而在for(i=n;i>0;--i)中,--i表示先将i减1,然后再使用新值进行循环体操作。 每个新结点都是尾结点的新直接前驱 它的思路是遍历链表,将新的结点插入到链表的尾部,即在原有链表的最后一个结点之后插入新的结点。这种方法可以保持原链表的相对顺序不变。 具体步骤如下: 这样,每次插入新的结点时,都会保持链表的相对顺序不变,并且新的结点都会插入到链表的尾部。 不妨用下面这道题串联上述操作,顺便练习C语言菜单的建立。 主函数中实现下列操作(蓝色部分用函数实现) C语言菜单是一种通过控制台界面为用户提供选择功能的方式。通常使用switch语句和循环来实现菜单的选择和功能执行的过程。通过用户输入的选择执行对应的功能函数。在每个功能函数内部,你可以编写你想要执行的具体功能代码。若当用户输入某个特定值时,返回上一级菜单,则实现了多级菜单。 本题用到的菜单比较简单,博主采用了最基本的if...else if...语句。 源代码如下。编译器vs20 一点细节:博主认为输出查找结果的printf语句放在主函数里比放在查找函数里方便。创建
头插法
//关键步骤
p->next=L->next;
L->next=p;
void CreatList(LinkList &L,int n)//L为链表名,n为表长
{
L=(LinkList)malloc(sizeof(LNode));//定义指向L的头指针
L->next=NULL;//初始化L为空表
for(int i=n;i>0;--i)
{
LinkList p=(LinkList)malloc(sizeof(LNode));//随用随分配
scanf("%d",&p->data);
p->next=L->next;
L->next=p;
}
}
尾插法
void CreateList(LinkList& L, int n) {//尾插法
// 正序输入 n 个数据元素,建立带头结点的单链表
LinkList p, q; int i;
L = (LinkList)malloc(sizeof(LNode));
q = L; // 先建立一个带头结点和尾指针的单链表
for (i = 0; i < n; ++i) {
p = (LinkList)malloc(sizeof(LNode));
scanf_s("%d", &p->data); // 输入元素值
q->next = p;
q = p;
} //
q->next = NULL; //修改尾指针
}
一道题目
题干
菜单
源代码
#include
运行结果
其一
其二