您的位置:首页 > 其它

单链表的创建与查找

2015-02-03 19:18 218 查看


链表--单链表的创建与查找

[转自:http://www.cnblogs.com/foreverking/articles/2341399.html]

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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐