数据结构之线性结构(单链表)【二】

单链表

链接存储方法

链接方式存储的线性表称为链表(Linked  List)

链表的具体存储表示为:

1.用一组任意的存储单元来存放线性表的结点(这组存储单元可以是连续的也可以是不连续的)

2.链表中的逻辑次序和物理次序不一定相同。为了能够正确表示结点间的逻辑关系,在存储每个结点的同时,还必须存储指示其后继结点的地址信息或链

注:链式存储是最常用的存储方式之一,它不仅可以用来表示线性表,而且可用来表示各种非线性的数据结构。

链表的节点结构

data    next

data域----存放结点值的数据域

next域----存放节点的直接后继的地址(位置)的指针域(链域)

注:每个结点只有一个链域的链表称为单链表(Single  Linked  List)


头指针head和终端节点指针域的表示

单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点(链表由头指针唯一确定,单链表可以用头指  针的名字命名)

终端结点无后继,故终端节点的指针域为空,即NULL。

单链表的图示法


单链表的类型描述

typedef  char   DataType;//假设结点的数据域类型为char型

typedef struct  node {//结点类型定义

DataType data;//结点的数据域

struct  node  *next;//结点的指针域

} ListNode;

typedef   ListNode  *LinkList;

ListNode  *p;

LinkList  head;

注:LinkList和ListNode *是不同名字的同一个指针类型(命名的不同是为了概念上更明确)

       LinkList类型的指针变量head表示他是单链表的头指针

       ListNode   * 类型的指针变量p表示它是指向某一结点的指针

指针变量p和结点变量*p的关系

指针变量p的值-------结点地址

结点变量*p的值-----结点内容

(*p).data的值-----p指针所指结点的data域的值

(*p).next的值-------*p后继结点的地址

*((*p).next) -------*p后继结点



单链表的运算

建立单链表

动态的建立单链表有如下两种方法:

1.头插法建表

算法思路:从一个空表开始,重复读入数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新节点插入到当前链表的表头上,直到读入结束标志为止(该方法生成的链表结点次序与输入顺序相反)

具体算法实现:

LinkList  CreatListF(void)

{//返回单链表的头指针

char  ch;

LinkList  head;//头指针

ListNode  *s;//工作指针

head  = NULL;//链表开始为空

ch  =getchar;//读入第一个字符

while (ch !='\n') {

s= (ListNode *)malloc(sizeof(ListNode));//生成新节点

s->data =ch;//将读入的数据放入新节点的数据域中

s->next= head;

head = s;

ch = getchar();//读入下一个字符

}

            return head;

}

2.尾插法建表

算法思路:从一个空表开始,重复读入数据,生成新节点,将读入数据存放在新节点的数据域中,然后将新结点插入到当前链表的表尾上,直到读入结束标志为止。

具体算法实现:

LinkList  CreatListR(void)

{//返回单链表的头指针

char ch;

LinkList  head;//头指针

ListNode  *s ,*r;//工作指针,r为尾部指针

head = NULL;//链表开始为空

r = NULL;//尾指针初值为空

ch = getchar();//读入第一个字符

while(ch!='\n') {

s = (ListNode *) malloc (sizeof(ListNode));//生成新结点

s->data = ch;//将读入的数据放入新节点的数据域中

if (head == NULL)

head = s;//新结点插入空表

else

r ->next =s;

r = s;// 尾指针指向新表尾

ch =getchar();//读入下一个字符

}//endwhile

if(r! = NULL)

r->next = NULL;//对于非空表,将尾结点指针域置空head = s;

return head;

}

单链表的查找运算

1.按序号查找:在链表中,即使知道被访问结点的序号i,也不能象顺序表中那样直接按序号i访问结点,而只能从链表的头指针出发,顺链域next逐个节点往下搜索,直 至搜索 到第i个结点为止。因此,链表不是随机存取结构。

查找的思想方法:计数器j置为0后,扫描指针从链表的头结点开始顺着链扫描。当p扫描下一个结点时,计数器相应的加1,当j=i时,指针p所指的结点就是要找的第i个结点。而  当p指针指向null&& !=i 时表示找不到i结点。(头结点可看作第0个结点)

算法实现:

ListNode* GetNode(LinkList  head,int i)

{

int   j;

ListNode  *p;

p = head; j=0;

while(p->next&&j<i) {

p = p->next;

j++;

}

if(i==j)

return  p;

else  return NULL;

}

算法分析:算法中,while语句终止的条件是搜索到表尾或找到j==i,所以时间复杂度为O(n)

2.按值查找

思想方法:从开始结点出发,顺着链逐个将结点的值和给定值key作比较,若有结点的值与key相等,则返回首次找到的其值为节点的存储位置,否则返回NULL。

算法实现:

ListNode*  LocateNode  (LinkList  head,DataType  key)

{

ListNode    *p  = head->next;

while(p&&p->data!=key)

p = p->next;

return p;

}

其时间复杂度为O(n)


插入算法

思想方法:插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间

  具体步骤:

           (1)找到ai-1存储位置p
     (2)生成一个数据域为x的新结点*s
     (3)令结点*p的指针域指向新结点
     (4)新结点的指针域指向结点ai

具体实现:

void  InsertList(LinkList   head,DataType  x,int   i)

{

ListNode  *p;

p = GetNode(head,i-1);

if (p == NULL)

Error( " ");

s = (ListNode  *)malloc (sizeof(ListNode));

s->data= x;s->next=p->next;p->next =s;

}

算法的时间主要耗费在查找操作GetNode上,故时间复杂度为O(n)


删除运算

思想方法:删除运算是将表的第i个结点删去。

具体步骤:(1)找到ai-1的存储位置p(因为在单链表中结点ai的存储地址是在其直接前趋结点ai-1的指针域next中)
         (2)令p->next指向ai的直接后继结点(即把ai从链上摘下)
         (3)释放结点ai的空间,将其归还给"存储池"。

具体算法实现:

void   DeleteList(LinkList   head,int  i)

{//删除带头结点的单链表head上的第i个结点

ListNode  *p,*r;

p = GetNode(head,i-1);//找到第i-1个结点

if (p == NULL  || p->next == NULL)当i<1或i>n时,删除位置出错

Error(" ");//退出程序运行

r = p->next;//使r指向被删除的结点ai

p ->next =r->next;//将ai从链上摘下

free(r);//释放结点ai的空间给存储池

}

算法分析:算法的时间复杂度也是O(n)

链表上实现的插入和删除运算,无需移动结点,只需修改指针即可,所以对于改动比较频繁的数据,适合用链表存储,如果改动不频繁,可用顺序表(因链表需要额外的为指针开辟的存储空间,所以存储占用量大)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值