24考研数据结构复习(3)——顺序表的链式表示和实现

本系列文章是笔者复习自用,参考教材为严蔚敏编著的《数据结构(C语言版)》,如能对大家有所帮助实属荣幸

本篇的内容主要是线性表的链式表示和实现,主要包括线性链表的定义和常用函数的实现。

3.1 单链表的类型定义

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

 单链表中节点类型的描述如下:

typedef struct LNode{
	ElemType data;//数据域
	struct LNode *next;//指针域
}LNode,*LinkList;

利用单链表可以解决顺序表需要大量连续存储单元的缺点,但同时,单链表附加指针域,也存在浪费存储空间的缺点。

顺序表占用的是一段连续的空间,而链表占用的是离散的空间,属于非随机存取的存储结构,即不能直接找到表中某个特定的结点,需要从头开始遍历,依次查找。

通常用头指针来标识一个单链表,头指针的指针域指向该单链表的第一个元素,如果为null,则该单链表为空表。

3.2 单链表上基本操作的实现

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

该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头。其过程为,让新结点的指针域指向头结点的指针域所指结点,然后修改头结点的指针域,使其指向新结点。

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

LinkList List_HeadInsert(LinkList &L){
	LNode *s; int x;
	L = (LinkList)malloc(sizeof(LinkList));//创建头结点空间
	L->next = NULL;
	printf("请输入一个数字\n");
	scanf("%d", &x);
	while (x!=9999)//当x等于9999时退出
	{
		s = (LNode *)malloc(sizeof(LNode));//创建新的结点空间
		s->data = x;
		s->next = L->next;//链式插入原则都是先建新链,再拆旧链
		L->next = s;
		printf("请输入一个数字\n");
		scanf("%d", &x);
	}
	return L;
}

采用头插法建立链表时,链表的顺序和读入数据的顺序相反,每个结点插入的时间为O(1),设单链表长为n,则该算法时间复杂度为O(n)。

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

头插法虽然简单,但是生成链表与输入数据的顺序不一致,不便于操作。若使用尾插法,则可保持链表中次序和输入数据的顺序一致。尾插法的思路为,生成新结点,改变表中最后一个结点的指针域,使其指向新结点,并且使新结点的指针域为null,为方便操作,一般增加一个指针t,使其始终指向链表的尾结点。

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

LinkList List_TailInsert(LinkList &L){
	LNode *s,*t; int x;//t为指向尾结点的指针
	L = (LinkList)malloc(sizeof(LinkList));
	L->next = NULL;
	t = L;//空表时指向头结点
	printf("请输入一个数字\n");
	scanf("%d", &x);
	while (x!=9999)
	{
		s = (LNode *)malloc(sizeof(LNode));//创建新的结点空间
		s->data = x;
		t->next = s;//始终指向尾结点
		s->next = NULL;
		t = s;
		printf("请输入一个数字\n");
		scanf("%d", &x);
	}
	return L;
}

时间复杂度与头插法系相同,都为O(n)。

3.按序号查找结点

在单链表中从头结点出发,沿着指针域依次往后搜索,直到找到第i个结点为止,否则返回最后一个结点指针域,即为null。

按序号查找结点值的算法如下:

LNode *GetElem(LinkList L, int i){
	int index=1;
	if (i < 1)
		return NULL;
	LNode *p = L->next;
	while (i != index && p->next != NULL){//遍历,直到找到值或找到结尾
		p = p->next;
		index++;
	}
	return p;
}

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

4.插入结点操作

插入结点操作指的是将值为x的新结点插入到单链表的第i的位置上,先检查位置i的合法性,然后找到待插入结点的前驱,然后生成新结点并插入。

算法思想为先生成一个新结点,其数据域为输入的数据,从头开始遍历,找到第i-1个元素,将新结点的指针域等于第i-1个元素的指针域,然后将第i-1个元素的指针域改为指向新结点,值得注意的是,这类插入算法都是先连后断。

插入结点操作的算法如下:

LinkList List_Insert(LinkList &L, ElemType x, int i){
	if (i < 1)//判断位置取值是否合法
		return L;
	LNode *p; int index = 1;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = x;
	p = L;
	while (i!=index && p->next!=NULL){//遍历,直到找到位置
		p = p->next;
		index++;
	}
	s->next = p->next;
	p->next = s;
	return L;
}

本算法的主要开销是遍历寻找到前驱结点,时间复杂度为O(n),将结点插入表中的时间复杂度仅为O(1)。

5.删除结点操作

删除结点操作指的是将链表中第i个结点删除。先检查位置i的合法性,然后找到待删除的结点,将其删去。

算法思想为,通过遍历找到第i个结点和第i-1个结点,改变第i-1个结点的指针域使其等于第i个结点的指针域,然后释放第i个结点的空间,值得注意的是,删除操作一般只需要连不需要断

删除结点操作的算法如下:

LinkList List_Delete(LinkList &L, int i){
	int index = 1;
	LNode *p = L,*q=L->next;//p指向待删除结点的前驱,q指向待删除结点
	while (i != index && q->next != NULL){
		p = p->next;
		q = q->next;
		index++;
	}
	p->next = q->next;
	free(q);//释放q
	return L;
}

3.3 顺序表和链表的比较

1.存取方式

顺序表可以顺序存取,也可以随机存取,即顺序表可以按顺序遍历值,也可以使用次序直接访问值;链表只可以顺序存取,只能按顺序遍历值。因此在查找和修改方面,顺序表有优势。

2.逻辑结构和物理结构

使用顺序表时,在逻辑上相邻的元素,在物理上也相邻;使用链表时,在逻辑上相邻的元素,在物理上并不相邻,甚至可能不按序排列。

3.操作

按值查找时,顺序表和链表的算法相似,都是从头开始遍历,故时间复杂度也相同,为O(n);按序查找时,因为顺序表支持随机存取,所以时间复杂度为O(1),但链表的时间复杂度仍为O(n)。

4.空间分配

使用顺序表的静态分配时,往往会导致空间浪费或空间不够,需要预先计算所需的空间;使用顺序表的动态分配时,虽然可以扩容,但仍会造成资源浪费,效率低下。但使用链表时,空间可以随时增加,操作灵活、方便。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值