链表基本操作01

等把一本书全部看完后,再做调整,此处为整理部分。
两种链表类型:
在这里插入图片描述
头结点有无有什么区别呢?

在表示空表时:有头结点直接head->next=null即可;没有头结点头指针指向为空。
在操作时需要判断是否在开始结点,那么在插入、删除等操作同样要判断当前位置。
所以加入头结点后,发现无需对开始结点特殊处理。

1.链表结构描述:

  1. List item

    在这里插入图片描述
    存数据存联系:data存储数据,next指向下一个节点。

    那么这种结构有什么优点呢?
    它的特点是(存储地址不相邻可以在逻辑上相邻)   
    

关于链表结点分配

  1. 静态分配
  2. 动态分配(在程序运行中申请得到的)
(1)结点的申请   
p=(LinkListNode *)malloc(sizeof(LinkListNode));
函数malloc()分配一个大小为LinkListNode字节的空间,并将其首地址
放入指针变量p中。
(2)节点的释放      
free(p);
释放p所指的结点变量空间。
(3)结点数据项的访问
利用结点指针p访问结点分量。
方法一:(*p).data 和 (*p).next
方法二:  p->data 和  p->next

2.单链表的运算:

基本操作
1初始化
2建链表
3查找
4删除
5插入
  • 1.单链表初始化
    构造一个只有头结点的单链表。
/*==========================================
函数功能:单链表运算——初始化
函数输入:无
函数输出:链表头指针
============================================*/
LinkListNode *initialize_LkList(void)
{
	LinkListNode *head;
	head=(LinkListNode *)malloc(sizeof(LinkListNode));  //申请一个结点
	if(head==NULL) exit(1);  //存储空间分配失败
	head->next=NULL;  //指针域置空 
 }
注意:一般对申请空间极少的程序而言,动态申请新结点空间是不会出现问题。
但在实用程序里,尤其是对空间需求极大的程序,凡是涉及动态申请空间,一定要判断
空间是否申请成功,以防系统无空间可供分配。

思考:一般c语言书上会建议,不要返回局部量的地址,在InitList函数中head是局部量,这样的设计是否可靠呢?(下面介绍我也没弄明白,可以不看)

暂时我用不到,这一段也没看懂,在此展示一段测试代码及相关内容。
功能描述输入输出
创建一结构节点空间CreateNode结构结点空间地址LinkListNode *
函数名形参函数类型

创建结点方式1:通过malloc函数申请,此种方式得到的内存空间在“堆”中。
创建结点方式2:通过定义结构变量,此种方式得到的内存空间在“栈”中。

测试程序如下:

//栈与对空间地址传递的测试
#include<stdio.h>
#include<malloc.h>

typedef struct node
{
	int data;
	struct node *next;
 } LinkListNode;
 
 LinkListNode *CreateNode(void)
 {
 	LinkListNode *p,*Heap,Stack;
 	
 	//Heap指向空间通过malloc函数申请,在堆中分配
	 Heap=(LinkListNode *)malloc(sizeof(LinkListNode));
	 Heap->data=6;
	 Heap->next=NULL;
	 
	 //Stack变量空间在栈中分配
	 Stack=*Heap;//将Heap结点的内容复制给Stack结点
	 p=&Stack;
	 p=Heap;
	 return p; 
 }
 
 int main()
 {
 	LinkListNode *head,*x;
 	int y;
 	head=CreateNode();
 	x=head;
 	printf("%x:%d%d\n",x,x->data,x->next);
 	y=x->data;
 	printf("%x:%d%d\n",x,y,x->next);
 	return 0;
 }
程序结果一:
在程序21行关闭,22行有效,即返回Heap空间地址时,程序执行的结果
程序结果二:
在程序22行关闭,21行有效,即返回Stack空间地址时,程序执行的结果

在这里插入图片描述
测试结果跟预想不一样,程序二跟预想出现差异。

思考结论:

 在栈中分配的局部量地址,传递给主函数后,由于局部量空间被系统释放,
 其间的内容随即失效——“将指针值作为函数的返回值时,不要返回一个局部量的地址。”
 
 在堆中分配的空间虽然也是子函数局部量,但传递给主函数时,
 由于这部分空间是由当前运行程序控制的,并未被释放,故而内容依然保留。 
  • 2.建立单链表
    将线性表n个元素存放在一个单链表中,head为头指针。

1 ) 尾插法建立单链表

通过不断在单链表的尾部插入结点的方法来建立链表。

在这里插入图片描述在这里插入图片描述在这里插入图片描述

(1)申请头结点head                          head=malloc(sizeof(LinkListNode)); 
(2)前趋结点q=head                          q=head;
(3)申请当前结点p;                          p=malloc(sizeof(LinkListNode));
    向p结点中添入结点值;                     p->data=a[i];
    p的地址填入前趋q指针域;                  q->next=p;
(4)q=p                                    q=p;    
(5)尾结点指针域置NULL                       p->next=NULL;  
                       重复步骤3~4直到结点数目满足要求为止

------------------------表2.1 尾插法建链表函数结构
在这里插入图片描述

在这里插入图片描述
——数据结构:头指针——>结点-------->结点——>尾结点
之前的数据结构中定义了struct node类型,包含data和next;initialize_LkList函数中分配了head指针。


  • 尾插法创建单链表代码
 /*===========================================
函数功能:单链表操作——尾插法建立链表
函数输入:结点值数组,结点个数
函数输出:链表的头指针
=============================================*/
LinkListNode *Create_Rear_LkList(ElemType a[],int n ) 
{   
      LinkListNode *head,*p, *q;
      int i;

      head=(LinkListNode *)malloc(sizeof(LinkListNode)); 
      q=head;                    
      for(i=0;i<n;i++) 
     {  
          p=(LinkListNode *)malloc(sizeof(LinkListNode)); 
          p->data=a[i];             
          q->next=p;                
          q=p;                      
     }  
     p->next=NULL;
     return head; 

2 ) 头插法建立单链表
——从一个空表开始,通过不断在单链表头部插入结点的方法来建立链表。

  1. 方法一
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    思考:头结点与p相连 与 p结点与q相连顺序颠倒一下可不可以?
  • 前插法方法一c代码
/*===========================================
函数功能:单链表操作——头插法建立链表方法1
函数输入:结点值数组,结点个数
函数输出:链表的头指针
=============================================*/
LinkListNode *Create_Front1_LkList(ElemType a[], int n ) 
{    
    LinkListNode *head, *p, *q;
    int i;

    head=(LinkListNode *)malloc(sizeof(LinkListNode));
    head->next=NULL; 
    q=head->next;
    for(i=n-1; i>=0; i--)   
    {   
        p=(LinkListNode *)malloc(sizeof(LinkListNode)); 
        p->data=a[i];             
        p->next=q;         
        head->next=p;  
        q=head->next;
    }
  1. 方法二
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    尾部前插c代码:
/*===========================================
函数功能:单链表操作——头插法建立链表方法2
函数输入:结点值数组,结点个数
函数输出:链表的头指针
=============================================*/
LinkListNode *Create_Front2_LkList(ElemType a[],int n ) 
{    
    LinkListNode *head, *p, *q;
    int i;
    q=NULL;
    for(i=n-1; i>=0; i--)   
    {   
        p=(LinkListNode *)malloc(sizeof(LinkListNode)); 
        p->data=a[i];             
        p->next=q;
      q=p; 
    }
    head=(LinkListNode *)malloc(sizeof(LinkListNode));
    head->next=q;

    return head;
}
  1. 方法三
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    写的时候理顺关系,直接写就成。相关数据结构需要有结点值、*head、数组的大小n,以上都是。

程序实现:

/*===========================================
函数功能:单链表操作——头插法建立链表方法3
函数输入:结点值数组,结点个数
函数输出:链表的头指针
=============================================*/
LinkListNode *Create_Front3_LkList( ElemType a[],int n )
{
    LinkListNode *head,*p;
    int i;
    head=(LinkListNode *)malloc(sizeof(LinkListNode));
    head->next=NULL;  //head指向的那块区域中data没赋初始值
    for(i=n-1; i>=0; i--)
    {
        p=(LinkListNode *)malloc(sizeof(LinkListNode));
        p->data=a[i];
        p->next=head->next;
        head->next=p;
    }
     return head;
}

3 . 单链表查找运算

单链表查找有两种方式,可以按值查找或按序号查找。
1)单链表中按值查找结点
意思是:在单链表中查找关键字

在设计前书写出所有情形
单链表按值查找——测试用例,代表性的测试数据,使具有完备性

输入:单链表头指针,要查找的关键字
输出:关键字所在结点的地址

在这里插入图片描述
单链表按值查找——函数结构设计
在这里插入图片描述
这个表,上功能区我能看出来,下边功能区始终还有疑问,去掉更好吗?

思考:关键字找不到时,输出NULL合适吗?
一般看课本上查找操作用返回的是boolean值,当然也要输出查找值的地址。这里返回地址,找不到关键字,应该返回和结点地址同类型的指针值,为了和正常的地址有区别,输出NULL是合适的。

单链表按值查找步骤:
在这里插入图片描述
思考:表非空,未找到——p=NULL
---------空表,head->next=NULL——p=NULL
这里关于NULL的定义:define NULL 0

=================================
程序实现:

/*===========================================
函数功能:单链表操作——按值查找结点
函数输入:链表的头指针,结点值
函数输出:找到:返回结点指针;未找到:返回NULL
=============================================*/
LinkListNode * Locate_LkList( LinkListNode *head, ElemType key) 
{  
    LinkListNode *p; 
    p=head->next; //跳过表头结点     
    while(p != NULL && p->data != key ) //结点非空且结点值不是key 
    {
       p=p->next; //p指向下一个结点
    }
    return p;
}

·
单链表中按序号查找结点
在单链表中查找第i个结点(头结点i=0)

测试用例设计
输入:单链表头指针,要查找的结点编号
输出:第i个结点的地址
·

测试用例取值范围如下表
在这里插入图片描述
异常情形描述:
(1)表非空,i值出界;
(2)空表;
算法中函数正常返回找到的结点地址,异常可以返回NULL。

函数结构设计:
在这里插入图片描述
查找方法:

  1. 找到的情形:在这里插入图片描述
    计数器“j” 置为“0”后,扫描指针p从链表的头结点开始顺着链扫描。当p扫描下一个结点时,计数器 j 相应地加 1 。
    继续查找的条件:(1)j<i (2)p->next 非空
    找到时的条件: (1)j=i (2) p非空

2.找不到的情形:
在这里插入图片描述
当p的后继为NULL且“j != i”时,则表示找不到第i个结点。
注意:头结点是第0个结点,把“ i=0 ”也归为异常。

算法描述:
在这里插入图片描述

问题:链表是不是随机存取结构?
(我去访问某一结点的时间相同就叫可以随机存取。)
答:在链表中,即使知道被访问结点的序号i,也不能像顺序表中那样直接按序号访问结点,而只能从链表的头指针出发,顺链域next逐个结点往下搜索,直至搜索到第 i 个结点为止。因此,链表不是随机存取结构。

程序实现:

/*===========================================
函数功能:单链表操作——按序号查找结点
函数输入:链表的头指针,待查结点序号
函数输出:找到:返回结点指针;未找到:返回NULL
=============================================*/
LinkListNode *Get_LkList(LinkListNode *head, int i )
{ 
  int j;
  LinkListNode *p;
  p=head;j=0;
  if (i==0) return NULL;
  while( j<i && p->next != NULL)  //未到达第i个结点且下一个结点非空
   { 
       p=p->next;
        j++;
   }
  if (i==j) return p;  //找到第 i 个结点 
  else return NULL;
}

4.单链表的插入运算
在链表指定位置插入给定的值

后插法介绍
示意图如下
在这里插入图片描述
描述:在单链表结点a(i)之后插入x;已知a(i)的地址为Ptr
注释:ptr – pointer 指针

测试用例设计:
输入:插入点地址Ptr,待插入结点的值x
输出:无

设计用例取值范围
在这里插入图片描述
函数结构:
在这里插入图片描述
算法伪代码描述:
在这里插入图片描述
在这里插入图片描述
程序实现:

/*===========================================
函数功能:单链表操作——在指定位置后插入结点
函数输入:插入点地址,结点值
函数输出:无
=============================================*/
void  Insert_After_LkList(LinkListNode *Ptr,ElemType x )  
{ 
  LinkListNode  *s;
  s=(LinkListNode*)malloc(sizeof(LinkListNode));
  s->data=x;
  s->next=Ptr->next;
  Ptr->next=s;
}

·
前插法
在单链表结点a(i)之前插入x;已知a(i)的地址为Ptr,如图
在这里插入图片描述
测试用例:
输入:
(1)链表头指针
(2)ai结点地址
(3)结点值x
输出:无

测试用例取值范围:
在这里插入图片描述
函数结构设计:
在这里插入图片描述
算法伪代码描述:
在指定的结点Ptr前插入值为x的结点
在这里插入图片描述
在这里插入图片描述
看到这我想到双链链表,虽然多花费了存储空间,多了front链域,不过“前插后插都很方便”,我只要改变:s->front=Ptr->front; Ptr->front->next=s; s->next=p; p->front=s;插入结点的两个链域和前一个结点的next域,插入位置的front域。

程序实现:

/*===========================================
函数功能:单链表操作——在指定位置后插入结点
函数输入:插入点地址,结点值
函数输出:无
=============================================*/
void  Insert_After_LkList(LinkListNode *Ptr,ElemType x )  
{ 
  LinkListNode  *s;
  s=(LinkListNode*)malloc(sizeof(LinkListNode));
  s->data=x;
  s->next=Ptr->next;
  Ptr->next=s;
}

===========================================================
5.单链表的删除运算
单链表结点的删除的方式按给定信息的不同,在此讨论两种方式。

  • 情形一:删除指定结点的后继结点,指定结点地址为Ptr
  • 情形二:删除单链表第i个结点(头结点 i =0)

对于情形一,因为对给定结点地址异常是难以判断的。为了方便处理异常情形,可以在调用前判断异常情形,异常按异常状况处理,比如提示信息啊!

函数结构设计:
在这里插入图片描述
说明:函数返回的是被删除结点的地址,而没有释放这个节点空间,主要是考虑调用者有可能要继续使用这个结点信息,这个结点的释放时机将由调用者决定,有时这么处理是方便的,但一定要记着这个结点一旦不再使用,是要释放的(free(fPtr)),否则会造成内存泄漏。内存一直占着,可不泄露(变少)了。

算法伪代码描述:
在这里插入图片描述
在这里插入图片描述
程序实现:

/*===========================================
函数功能:单链表操作——删除指定结点的后继结点
函数输入:指定结点地址
函数输出:被删除结点地址
=============================================*/
LinkListNode * Delete_After_LkList( LinkListNode *Ptr)
{ LinkListNode *fPtr;
  fPtr=Ptr->next;
  Ptr->next=fPtr->next; 
  return fPtr;
}

情形二: 删除单链表指定结点 i

测试用例:
输入:链表头指针、结点编号
输出:被删除结点的地址

测试用例取值范围
在这里插入图片描述
函数结构设计:
在这里插入图片描述
算法伪代码描述
在这里插入图片描述
操作示意图
在这里插入图片描述
思考:关于单链表删除指定结点 i 中,异常情形是否都处理了。
GetElem 函数在找不到结点 i 时会返回NULL,在此情形下删除 i 后继的函数DeleterAfter(Ptr)将不会被执行,直接就返回qPtr了,故qPtr应该设置初值NULL,避免了再做DeleterAfter(Ptr)是否被执行的判断。

程序实现:

/*===========================================
函数功能:单链表操作——删除第i个结点
函数输入:链表头指针,结点编号
函数输出:正常:被删除结点地址;异常:NULL
=============================================*/
LinkListNode *Delete_i_LkList( LinkListNode *head,  int i)
{  LinkListNode *Ptr,*qPtr=NULL;
   Ptr=Get_LkList(head,i-1); //找到i结点的前趋地址
   if( Ptr!=NULL && Ptr->next!=NULL ) 
       qPtr=Delete_After_LkList(Ptr);
   return qPtr;
}

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

最后,作为一篇一边记笔记一边记录的作业,要在另一篇写剩下的了,不然不好翻呀!

单链表的讨论作为结尾
链表中的对象也是按线性顺序排列的,但与数组不同,数组的线性顺序是由数组的下标决定的,而链表中的顺序则是由个对象中的指针决定的。相比于线性表顺序结构,其操作复杂。

  1. 动态结构,不需预先分配空间:使用链表结构可以克服顺序表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
  2. 指针占用额外存储空间:链表由于增加了结点的指针域额外占用空间。
  3. 不能随机存取,查找速度慢:链表失去了数组的随机读取的优点,且单向链表只能顺着一个方向查找
  4. 链表上实现的插入和删除运算,不需要移动结点,仅需修改指针。

如图所示:
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值