【线性表】链式存储结构之:单链表

链表  ,采用链式存储结构存储数据元素的线性表

链式存储方法

用一组任意的存储单元来存放线性表的数据元素(这组存储单元既可以是连续的,也可以是不连续的)

链表的逻辑关系

(1)为了能正确表示数据元素间的逻辑关系,对于每个数据元素来说, 除了储存本身的信息外,还必须存储指示其后继结点的位置信息这两部分信息组成数据的一个结点。它包括两个域:数据域(data)和指针域(next)( 每个结点只有一个指针域的称为单链表
(2)单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。
(3)由于单链表中最后数据元素没有直接后继,所以将最后一个结点的指针指向NULL。
(4)只要确定了第一个数据元素,就可以确定所有数据元素,由此可见,单链表可以由头指针唯一确定

单链表类型定义

typedef struct LNode
{
    ElemType  data;
    struct LNode * next;
}LNode, * LinkList;
【例】假设p是指向单链表中的第i个数据元素的指针,则
p->data是第i个数据元素的值。
p->next是指向第i+1个数据元素的指针
p->next-data是第i+1个数据元素的值


单链表的算法实现

1.建立单链表

(1) 头插法建表

从一个空表开始,重复读入n个数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新结点插入到当前链表的表头上。

具体方法【参见动画演示

LinkList CreateList_L(int n)
{//头插法建表,返回单链表的头指针
	LinkList head;	//头指针
	LinkList p;		//临时保存新结点地址
	head = NULL;	//先建立一个空表
	for (; n > 0; n--)
	{//在表头插入n元素
		p = (LinkList)malloc(sizeof(LNode));	//生成新结点
		scanf(&p->data);	//输入结点数据的值
		p->next = head;		//将新结点插入到表头
		head->next = p;		//修改head指向新的表头
	}
	return head;
}


(2) 尾插法建表 

从一个空表开始,重复读入n个数据,生成新结点,将读入数据存放在新结点的数据域中,然后将新结点插入到当前链表的表尾上。

具体方法【参见动画演示

LinkList GreateList_R(int n)
{
	LinkList head;	//头指针
	LinkList r;		//尾指针
	LinkList p;		//临时保存新结点地址
	head = NULL;	//建立一个空表
	r = NULL;		//尾指针初始化
	for (; n > 0; n--)
	{
		p = (LinkList)malloc(sizeof(LNode));	//生成新结点
		scanf(&p->data);	//输入结点数据的值
		if (NULL == head)	//如果是空表
		{
			r = head = p;		//新结点插入表头
		}
		else
		{//插入表尾
			r->next = p;
			r = p;
		}		
	}
	return head;
}

注意:

  • 由于开始结点的位置是存放在头指针(指针变量)中,而其余结点的位置是在其前趋结点的指针域中,插入开始结点时要将头指针指向开始结点。
  • 因为对尾部进行插入,所以增加了一个尾指针


(3) 尾插法建带头结点的单链表 
①头结点及作用
头结点是在链表的开始结点之前附加一个结点。它具有两个优点:
⒈由于开始结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作就和在表的其它位置上操作一致,无须进行特殊处理;
⒉无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域空),因此空表和非空表的处理也就统一了。

②带头结点的单链表


 
注意:
  
头结点数据域的阴影表示该部分不存储信息。在有的应用中可用于存放表长等附加信息。
LinkList CreatListR1(int n)
{//用尾插法建立带头结点的单链表
	
	LinkList head=(LinkList)malloc(sizeof(LNode));	//生成头结点
	LinkList r = head;	//初始化尾指针指向头结点
	LinkList p;			//临时保存新结点地址
	
	for (; n > 0; n--)
	{//在表头插入n元素
		p = (LinkList)malloc(sizeof(LNode));	//生成新结点
		scanf(&p->data);		//输入元素的值
		r->next = p;			//将新结点链接到尾部
		r = p;					//将尾指针指向新的尾部
	}
	r->next=NULL;				//终端结点的指针域置空,或空表的头结点指针域置空
	return head;
} 
注意:
     
上述算法里,动态申请新结点空间时未加错误处理,这对申请空间极少的程序而言不会出问题。但在实用程序里,尤其是对空间需求较大的程序,凡是涉及动态申请空间,一定要加入错误处理以防系统无空间可供分配。



2.单链表的查找运算 

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

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

③具体算法实现
 
    LinkList 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个结点
     } 
(2) 按值查找
①思想方法 
    
 从开始结点出发,顺着链逐个将结点的值和给定值key作比较,若有结点的值与key相等,则返回首次找到的其值为key的结点的存储位置;否则返回NULL。

②具体算法实现

    LinkList  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的结点
       }



3.插入运算

(1)思想方法
     插入运算是将值为x的新结点插入到表的第i个结点的位置上,即插入到ai-1与ai之间。
具体步骤: 
     (1)找到ai-1存储位置p
     (2)生成一个数据域为x的新结点*s
     (3)令结点*p的指针域指向新结点
     (4)新结点的指针域指向结点ai。

(2)具体算法实现
    void InsertList(LinkList head,  DataType x,  int i)

      {//将值为x的新结点插入到带头结点的单链表head的第i个结点的位置上

        LinkList  p;

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

        if (p==NULL)                      //i<1或i>n+1时插入位置i有错

             Error("position error");

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

        s->data=x;

        s->next=p->next;

        p->next=s;

      }    

(3)算法分析

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


4.删除运算

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

(2)具体算法实现
   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("position error");//退出程序运行
         r=p->next;//使r指向被删除的结点ai
         p->next=r->next;                            //将ai从链上摘下
         free(r);                                              //释放结点ai的空间给存储池
       } 
注意:
     设单链表的长度为n,则删去第i个结点仅当1≤i≤n时是合法的。
     当i=n+1时,虽然被删结点不存在,但其前趋结点却存在,它是终端结点。因此被删结点的直接前趋*p存在并不意味着被删结点就一定存在,仅当*p存在(即p!=NULL)且*p不是终端结点(即p->next!=NULL)时,才能确定被删结点存在。

(3)算法分析

     算法的时间复杂度也是O(n)。
     链表上实现的插入和删除运算,无须移动结点,仅需修改指针。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值