链表--单链表的创建与查找
1、链接存储方法
链接方式存储的线性表简称为链表(Linked List)。
链表的具体存储表示为:
① 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的)
② 链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址(或位置)信息(称为指针(pointer)或链(link))
注意:
链式存储是最常用的存储方式之一,它不仅可用来表示线性表,而且可用来表示各种非线性的数据结构。
2、链表的结点结构
┌──┬──┐
│data│next│
└──┴──┘
data域--存放结点值的数据域
next域--存放结点的直接后继的地址(位置)的指针域(链域)
注意:
①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。
②每个结点只有一个链域的链表称为单链表(Single Linked List)。
【例】线性表(bat,cat,eat,fat,hat,jat,lat,mat)的单链表示如示意图
3、头指针head和终端结点指针域的表示
单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。
注意:
链表由头指针唯一确定,单链表可以用头指针的名字来命名。
【例】头指针名是head的链表可称为表head。
终端结点无后继,故终端结点的指针域为空,即NULL。
4、单链表的一般图示法
由于我们常常只注重结点间的逻辑顺序,不关心每个结点的实际位置,可以用箭头来表示链域中的指针,线性表(bat,cat,fat,hat,jat,lat,mat)的单链表就可以表示为下图形式。
5、单链表类型描述
typedef char DataType; //假设结点的数据域类型为字符
typedef struct node{ //结点类型定义
DataType data; //结点的数据域
struct node *next;//结点的指针域
}ListNode;
typedef ListNode *LinkList;
ListNode *p;
LinkList head;
注意:
①LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确)
②LinkList类型的指针变量head表示它是单链表的头指针
③ListNode *类型的指针变量p表示它是指向某一结点的指针
6、指针变量和结点变量
┌────┬────────────┬─────────────┐
│ │ 指针变量 │ 结点变量 │
├────┼────────────┼─────────────┤
│ 定义 │在变量说明部分显式定义 │在程序执行时,通过标准 │
│ │ │函数malloc生成 │
├────┼────────────┼─────────────┤
│ 取值 │ 非空时,存放某类型结点 │实际存放结点各域内容 │
│ │的地址 │ │
├────┼────────────┼─────────────┤
│操作方式│ 通过指针变量名访问 │ 通过指针生成、访问和释放 │
└────┴────────────┴─────────────┘
①生成结点变量的标准函数
p=( ListNode *)malloc(sizeof(ListNode));
//函数malloc分配一个类型为ListNode的结点变量的空间,并将其首地址放入指针变量p中
②释放结点变量空间的标准函数
free(p);//释放p所指的结点变量空间
③结点分量的访问
利用结点变量的名字*p访问结点分量
方法一:(*p).data和(*p).next
方法二:p-﹥data和p-﹥next
④指针变量p和结点变量*p的关系
指针变量p的值——结点地址
结点变量*p的值——结点内容
(*p).data的值——p指针所指结点的data域的值
(*p).next的值——*p后继结点的地址
*((*p).next)——*p后继结点
注意:
① 若指针变量p的值为空(NULL),则它不指向任何结点。此时,若通过*p来访问结点就意味着访问一个不存在的变量,从而引起程序的错误。
② 有关指针类型的意义和说明方式的详细解释。
(1) 头插法建表
① 算法思路
从一个空表开始,重复读入数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志为止。
具体方法【参见动画演示】
注意:
该方法生成的链表的结点次序与输入顺序相反。
② 具体算法实现
LinkList CreatListF(void)
{//返回单链表的头指针
char ch;
LinkList head;//头指针
ListNode *s; //工作指针
head=NULL; //链表开始为空
ch=getchar(); //读入第1个字符
while(ch!='\n'){
s=(ListNode *)malloc(sizeof(ListNode));//生成新结点
s->data=ch; //将读入的数据放入新结点的数据域中
s->next=head;
head=s;
ch=getchar(); //读入下一字符
}
return head;
}
(2) 尾插法建表
① 算法思路
从一个空表开始,重复读入数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新结点插入到当前链表的表尾上,直到读入结束标志为止。
具体方法【参见动画演示】
注意:
⒈采用尾插法建表,生成的链表中结点的次序和输入顺序一致
⒉必须增加一个尾指针r,使其始终指向当前链表的尾结点
② 具体算法实现
LinkList CreatListR(void)
{//返回单链表的头指针
char ch;
LinkList head;//头指针
ListNode *s,*r; //工作指针
head=NULL; //链表开始为空
r=NULL;//尾指针初值为空
ch=getchar(); //读入第1个字符
while(ch!='\n'){
s=(ListNode *)malloc(sizeof(ListNode));//生成新结点
s->data=ch; //将读入的数据放入新结点的数据域中
if (head!=NULL)
head=s;//新结点插入空表
else
r->next=s;//将新结点插到*r之后
r=s;//尾指针指向新表尾
ch=getchar(); //读入下一字符
}//endwhile
if (r!=NULL)
r->next=NULL;//对于非空表,将尾结点指针域置空head=s;
return head;
}
注意:
⒈开始结点插入的特殊处理
由于开始结点的位置是存放在头指针(指针变量)中,而其余结点的位置是在其前趋结点的指针域中,插入开始结点时要将头指针指向开始结点。
⒉空表和非空表的不同处理
若读入的第一个字符就是结束标志符,则链表head是空表,尾指针r亦为空,结点*r不存在;否则链表head非空,最后一个尾结点*r是终端结点,应将其指针域置空。
2.单链表的查找运算
(1)按序号查找
① 链表不是随机存取结构
在链表中,即使知道被访问结点的序号i,也不能像顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构。
② 查找的思想方法
计数器j置为0后,扫描指针p指针从链表的头结点开始顺着链扫描。当p扫描下一个结点时,计数器j相应地加1。当j=i时,指针p所指的结点就是要找的第i个结点。而当p指针指为null且j≠i时,则表示找不到第i个结点。
注意:
头结点可看做是第0个结点。
③具体算法实现
ListNode* GetNode(LinkList head,int i)
{//在带头结点的单链表head中查找第i个结点,若找到(0≤i≤n),
//则返回该结点的存储位置,否则返回NULL。
int j;
ListNode *p;
p=head;j=0;//从头结点开始扫描
while(p->next&&j<i){//顺指针向后扫描,直到p->next为NULL或i=j为止
p=p->next;
j++;
}
if(i==j)
return p;//找到了第i个结点
else return NULL;//当i<0或i>0时,找不到第i个结点
}
④算法分析
算法中,while语句的终止条件是搜索到表尾或者满足j≥i,其频度最多为i,它和被寻找的位置有关。在等概率假设下,平均时间复杂度为:
(2) 按值查找
①思想方法
从开始结点出发,顺着链逐个将结点的值和给定值key作比较,若有结点的值与key相等,则返回首次找到的其值为key的结点的存储位置;否则返回NULL。
②具体算法实现
ListNode* LocateNode (LinkList head,DataType key)
{//在带头结点的单链表head中查找其值为key的结点
ListNode *p=head->next;//从开始结点比较。表非空,p初始值指向开始结点
while(p&&p->data!=key)//直到p为NULL或p->data为key为止
p=p->next;//扫描下一结点
return p;//若p=NULL,则查找失败,否则p指向值为key的结点
}
③算法分析
该算法的执行时间亦与输入实例中key的取值相关,其平均时间复杂度分析类似于按序号查找,为O(n)。
附源代码
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *next;
}ListNode;
typedef ListNode * LinkList;
//头插入法
LinkList creatListF(void)
{
LinkList head=NULL;
ListNode *s;
char ch;
ch=getchar();
while(ch!='\n')
{
s=(ListNode *)malloc(sizeof(ListNode));
if(s==NULL)
{
return NULL;
}
s->data=ch;
s->next=head;
head=s;//head要不断向前移动,一直指着最前线,这样存储的数据与输入的数据顺序相反
ch=getchar();
}
return head;
}
//尾插入法
LinkList creatListR(void)
{
LinkList head=NULL,r=NULL;
ListNode *s;
char ch;
ch=getchar();
while(ch!='\n')
{
s=(ListNode *)malloc(sizeof(ListNode));
s->data=ch;
if(head==NULL)
head=s;//新结点插入空表
else
r->next=s;//如果是头结点,不执行这一步,头结点特别处理
r=s;//一直指向最后一个,无论点,r肯定是指向最后一个
if(r!=NULL)
r->next=NULL;//对于非空表,将尾结点指针域置空,防止内存读写错误
ch=getchar();
}
return head;
}
//根据查找第i个结点
ListNode * getNodebyi(LinkList head,int i)
{
LinkList p=head;
int j=0;
while(p->next && j<i)//如果p->next为NULL,那么没有后结点,关键判断后继结点
{
p=p->next;
j++;
}
//循环结束后,要不p为NULL,要不j=i
if(j==i)
{
return p;
}
else
return NULL;
}
ListNode * getNodebykey(LinkList head,char key)
{
LinkList p=head;//本程序是不带头结点的
while(p && key!=p->data)//直到p为NULL或p->data为key为止
{
p=p->next;
}
return p;//若p=NULL,则查找失败,否则p指向值为key的结点
}
void print(LinkList head)
{
while(head)
{
printf("%c ",head->data);
head=head->next;//在创建时,要将head设置为NULL,否则内存出错;LinkList head这样声明只是指向一个未知的值
}
}
int main()
{
//LinkList head=creatListF();
LinkList head=creatListR();
print(head);
//LinkList node=getNode(head,2);
//printf("\n查找第2个\n");
LinkList node=getNodebykey(head,'c');
printf("\n查找第2个\n");
printf("%c",node->data);
getchar();
return 0;
}