线性表的链式表示——单链表


顺序表可以随时存取表中的任意一个元素,它的存储位置可以用一个简单直观的公式表示, 但插入和删除操作需要移动大量元素链式存储线性表时,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻,它 通过“链”建立起数据元素之间的逻辑关系,因此 插入和删除操作不需要移动元素,而只需修改指针,但也会 失去顺序表可随机存取的优点

一、单链表的定义

线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表结点除存放元素自身的信息外还需要存放一个指向其后继的指针。单链表节点结构如下图所示,其中data为数据域,存放数据元素;next为指针域,存放其后继结点的地址。
单链表结点结构
单链表中节点类型的描述如下:

typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

利用单链表可以解决顺序表需要大量连续存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。由于单链表的元素离散地分布在存储空间中,所以单链表时非随机存取的存储结构,即不能直接找到表中某个特定的结点。查找某个铁定的结点时,需要从表头开始遍历,依次查找

通常用头指针来表示一个单链表,如单链表L,头指针为NULL时表示一个空表。此外,为了操作上的方便,在单链表第一个结点之前附加一个结点,称为头结点。头结点的数据域乐意不设任何信息,也可以记录表长等信息。头结点的指针域指向线性表的第一个元素结点。

二、单链表上基本操作的实现

1、采用头插法建立单链表

该方法从一个空表开始,生成新节点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。
头插法建立单链表
头插法建立单链表的算法如下:

LinkList List_HeadInsert(LinkList &L){//头插法建立单链表
	LNode *s;int x;
	L=(LinkList)malloc(sizeof(LNode));//创建头结点
	L->next=NULL;//初始为空链表
	scanf("%d",&x);//输入要创建的结点的值
	while(x!=9999){//输出9999,停止建立单链表
		s=(Lnode*)malloc(sizeof(LNode));//创建新结点
		s->data=x;
		s->next-L->next;//将新结点插入表中,L为头指针
		L->next=s;
		scanf("%d",&x);
	}
	return L;
}

采用头插法建立单链表时,读入数据的顺序与生成的链表中的元素的元素是相反的。每个结点插入的时间为O(1),设单链表长为n,则总时间复杂度为O(n)

2、采用尾插法建立单链表

头插法建立单链表的算法虽然简单,但生成的链表中结点的次序和输入数据的顺序不一致。若希望两者次序一致,则可采用尾插法。该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针r,使其始终指向当前链表的尾结点。
尾插法建立单链表

尾插法建立单链表的算法如下:

LinkList List_TailInsert(LinkList &L){//尾插法建立单链表
	int x;
	L=(LinkList)malloc(sizeof(LNode));//创建头结点
	LNode *s,*r=L;//r为表尾指针
	scanf("%d",&x);//输入结点的值
	while(x!=9999){//输入9999停止插入
		s=(LNode*)malloc(sizeof(LNode));
		s->data=x;
		r->next=s;//在表尾插入新结点
		r=s;//r指向新结点(使r始终指向链表表尾)
		scanf("%d",&s);
	}//插入结束
	r->next=NULL;//尾结点指针置空
	return L;
}

简单易得,尾插法的时间复杂度和头插法相同,为O(n)

3、按序号查找结点值

在单链表中从第一个结点出发,顺指针next域逐个往下搜索,指导找到第i个结点为止,否则(没找到)返回最后一个结点指针域NULL。
按序号查找结点值的算法如下:

LNode *GetElem(LinkList L,int i){//按序号查找结点值
	int j=1;//计数,从1开始查找
	LNode *p=L->next;//p指向第一个结点
	if(i==0) return L;//若要查找第0个结点,返回头结点
	if(i<1) return NULL;//若i序号为无效值,返回NULL
	while(p&&j<i){//从第一个结点开始,知道找到第i个结点(j==i)
		p=p->next;
		j++;
	}
	return p;//返回第i个结点的指针,若i大于表厂,则返回NULL
}

按序号查找操作的时间复杂度为O(n)

4、按值查找表结点

从单链表的第一个结点开始,由前往后依次比较表中各结点数据域的值,若某节点数据域的值等于给定值e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL。
按值查找表结点的算法如下:

LNode *LocateElem(LinkList L,ElemType e){
	LNode *p=L->next;
	while(p!=NULL&&p->data!=e) p=p->next;//从第一个结点开始查找data域为e的结点
	return p;//找到后返回该结点指针,否则返回NULL
}

按值查找操作的时间复杂度为O(n)

5、插入结点操作

插入节点操作将值为x的新结点插入到单链表的第i个位置上。先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,再在其后插入新结点。

算法首先调用按序号查找算法GetElem(L,i-1),查找第i-1个结点。假设返回的第i-1个结点为p,然后令新结点s的指针域指向p的后继结点,再令结点p的指针域指向新插入的结点*s。其操作过程如下图所示:
插入操作

实现插入结点的代码片段如下:

p=GetElem(L,i-1);//查找插入位置的前驱结点
s->next=p->next;
p->next=s;

注意:算法中第二、三条语句的顺序不能颠倒,否则,链表断链,无法找到插入结点的后继结点。
该算法主要的时间开销在于查找第i-1个元素时间复杂度为O(n)

6、删除结点操作

删除结点操作是将单链表的第i个结点删除。先检查删除位置的合法性,后查找表中第i–1个结点,即被删除结点的前驱结点,再将其删除。其操作过程如下图所示:
删除操作
假设结点p为找到的被删结点的前驱结点,为实现这一操作的逻辑关系的变化,仅需修改p的指针域,即将p的指针域next指向p的下一个结点。实现删除结点的代码片段如下:

p=GetElem(L,i-1);//查找删除位置的前驱结点
p=q->next;//令q指向被删除结点
p->next=q->next;//将*q结点从链中断开
free(q);//释放结点的存储空间

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

7、求表长操作

求表长操作就是计算单链表中数据结点(不含头结点)的个数,需要从第一个结点开始顺序依次访问表中的每个结点。设置一个计数器变量,每访问一个结点,计数器加1,直到访问到空结点为止。算法的时间复杂度为O(n)
注意:单链表的长度不包括头结点。

三、双链表、循环链表、静态链表

另有链表的其他形式讲解见文章:双链表、循环链表、静态链表

本文为个人学习总结所得,如有错误和问题欢迎指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
数据结构——用C描述答案 习题解答(唐策善版)(其他版本在上面) 第一章 绪论(参考答案) 1.3 (1) O(n) (2) (2) O(n) (3) (3) O(n) (4) (4) O(n1/2) (5) (5) 执行程序段的过程中,x,y值变化如下: 循环次数 x y 0(初始) 91 100 1 92 100 2 93 100 …… …… …… 9 100 100 10 101 100 11 91 99 12 92 100 …… …… …… 20 101 99 21 91 98 …… …… …… 30 101 98 31 91 97 到y=0时,要执行10*100次,可记为O(10*y)=O(n) 1.5 2100 , (2/3)n , log2n , n1/2 , n3/2 , (3/2)n , nlog2n , 2 n , n! , n n 第二章 线性表(参考答案) 在以下习题解答中,假定使用如下类型定义: (1)顺序存储结构: #define MAXSIZE 1024 typedef int ElemType;// 实际上,ElemType可以是任意类型 typedef struct { ElemType data[MAXSIZE]; int last; // last表示终端结点在向量中的位置 }sequenlist; (2)链式存储结构(单链表) typedef struct node {ElemType data; struct node *next; }linklist; (3)链式存储结构(双链表) typedef struct node {ElemType data; struct node *prior,*next; }dlinklist; (4)静态链表 typedef struct {ElemType data; int next; }node; node sa[MAXSIZE]; 2.1 头指针:指向链表的指针。因为对链表的所有操均需从头指针开始,即头指针具有标识链表的作用,所以链表的名字往往用头指针来标识。如:链表的头指针是la,往往简称为“链表la”。 头结点:为了链表操作统一,在链表第一元素结点(称为首元结点,或首结点)之前增加的一个结点,该结点称为头结点,其数据域不无实际意义(当然,也可以存储链表长度,这只是副产品),其指针域指向头结点。这样在插入和删除中头结点不变。 开始结点:即上面所讲第一个元素的结点。 2.2 只设尾指针的单循环链表,从尾指针出发能访问链表上的任何结点。 2•3 void insert(ElemType A[],int elenum,ElemType x) // 向量A目前有elenum个元素,且递增有序,本算法将x插入到向量A中,并保持向量的递增有序。 { int i=0,j; while (i<elenum && A[i]<=x) i++; // 查找插入位置 for (j= elenum-1;j>=i;j--) A[j+1]=A[j];// 向后移动元素 A[i]=x; // 插入元素 } // 算法结束
线性表实验报告 实验的目的要求 了解线性表的逻辑结构特性,以及这种结构特性在计算机内的两种存储结构。 掌握线性表的顺序存储结构的定义及其C语言实现。 掌握线性表链式存储结构——单链表的定义及其C语言实现。 掌握线性表在顺序存储结构即顺序表中的各种基本操作。 掌握线性表链式存储结构——单链表中的各种基本操作。 6、认真阅读和掌握实验的程序。 7、上机运行本程序。 8、保存和打印出程序的运行结果,并结合程序进行分析。 二、 实验的主要内容 题目: 请编制C语言,利用链式存储方式来实现线性表的创建、插入、删除和查找等操作。 具体地说,就是要根据键盘输入的数据建立一个单链表,并输出该单链表;然后根据屏 幕 菜单的选择,可以进行数据的插入或删除,并在插入或删除数据后,再输出单链表;最 后 在屏幕菜单中选择0,即可结束程序的运行。 三、 解题思路分析 在链表中插入数据,不需要进行大量的数据移动,只需要找到插入点即可,可以采用后 插入的算法,在插入点的后面添加结点。在链表中删除数据,先找到删除点,然后进行 指针赋值操作。 四、程序清单 #include<stdio.h> #include<stdlib.h> #include<math.h> typedef int ElemType; typedef struct LNode {ElemType data; struct LNode *next; }LNode; LNode *L; LNode *creat_L(); void out_L(LNode *L); void insert_L(LNode *L,int i,ElemType e); ElemType delete_L(LNode *L,ElemType e); int locat_L(LNode *L,ElemType e); void main() {int i,k,loc; ElemType e,x; char ch; do{printf("\n"); printf("\n 1.建立单链表"); printf("\n 2.插入元素"); printf("\n 3.删除元素"); printf("\n 4.查找元素"); printf("\n 0.结束程序运行"); printf("\n================================"); printf("\n 请输入您的选择(1,2,3,4,0)"); scanf("%d",&k); switch(k) {case 1:{L=creat_L(); out_L(L); }break; case 2:{printf("\n请输入插入位置:"); scanf("%d",&i); printf("\n请输入要插入元素的值:"); scanf("%d",&e); insert_L(L,i,e); out_L(L); }break; case 3:{printf("\n请输入要删除元素的位置:"); scanf("%d",&i); x=delete_L(L,i); out_L(L); if(x!=-1) {printf("\n删除的元素为:%d\n",x); printf("删除%d后的单链表为:\n",x); out_L(L); } else printf("\n要删除的元素不存在!"); }break; case 4:{printf("\n请输入要查找的元素值:"); scanf("%d",&e); loc=locat_L(L,e); if(loc==-1)printf("\n为找到指定元素!"); else printf("\n已找到,元素位置是%d",loc); }break; } printf("\n----------------"); }while(k>=1&&k<5); printf("\n 按回车键,返回...\n"); ch=getchar(); } LNode *creat_L() { LNode *h,*p,*s; ElemType x; h=(LNode *)malloc(sizeof(LNode)); h->next=NULL; p=h; printf("\n请输入第一个数据元素:"); scanf("%d",&x); while(x!=-999) {s= (LNode *)malloc(sizeof(LNode)); s->data=x; s->next=NULL; p->next=s; p=s; printf("请输入下一个数据:(输入-999表示结束.)"); scanf("%d",&x); } return(h); } void out_L(LNode*L) { LNode*p; p=L->next; printf("\n\n");

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小橘子xxb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值