TD02-线性表的链式存储-单链表、双链表、循环链表、静态链表


一、单链表

1.单链表基本概念

1、不需要使用地址连续的存储单元
2、通过“链”建立起数据元素之间的逻辑关系,插入和删除操作不需要移动大量元素,只需修改指针
3、每个链表结点,存放自身信息和一个指向后继的指针
4、查找某个特定结点,需要从表头开始遍历,依次查找

2.基本操作

1.单链表结点类型

typedef struct LNode {				//typedef关键字--数据类型重命名,如:typedef int zhengshu;
	int data;						//数据域
	struct LNode* next;				//指针域
}LNode, * Linklist;					//Linklist声明一个指向单链表第一个结点的指针

2.头插法建立单链表

//头插法建立单链表---------------头插法的重要应用:链表的逆置
Linklist List_HeadInsert(Linklist &L) {
	LNode* s;
	int x;
	L = (Linklist)malloc(sizeof(LNode));		//创建头结点
	L->next = NULL;								//初始为空链表		防止有脏数据,使得L指向一片神秘的区域		养成好习惯,初始化单链表,先把头指针指向NIULL
	scanf_s("%d", &x);
	while (x != 9999) {							//输入9999表示结束
		s = (LNode*)malloc(sizeof(LNode));		//增加新结点:在内存中申请一个结点所需空间,并用指针s指向这个空间
		s->data = x;
		s->next = L->next;
		L->next = s;							//将新结点插入表中,L为头指针
		scanf_s("%d", &x);
	}
	return L;
}

3.尾插法建立单链表

//尾插法建立单链表----------------------------------时间复杂度O(n)
Linklist List_TailInsert(Linklist& L) {
	int x;
	L = (Linklist)malloc(sizeof(LNode));		//建立头结点
	LNode* s, * r = L;							//r为表尾指针
	scanf_s("%d", &x);
	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;									//r指向新的表尾结点
		scanf_s("%d", &x);
	}
	r->next = NULL;								//尾结点指针置空
	return L;
}

4.按序号查找结点

//按序号查找结点
LNode* GetElem(Linklist L, int i) {		//LNode*:强调这是一个结点;  LinkList:强调这是一个单链表,(即使LinkList L等价于LNode* L,增加代码可读性。L通过头结点中指针域顺连着)
	if (i == 0)
		return L;
	if (i < 1)
		return NULL;
	int j = 1;
	LNode* p = L->next;
	while (p!=NULL && j < i) {				//循环找到第i个结点
		p = p->next;
		j++;
	}
	return p;
}

5.按值查找表结点

//按值查找表结点
LNode* LocationElem(Linklist L, int e) {
	LNode* p = L->next;
	while (p != NULL && p->data!=e)
		p = p->next;
	return p;
}

6.按位序插入结点(带头结点)

//插入结点操作	按位序插入(带头结点)。(如果不带头结点,要对第一个位置的删除或增加操作单独操作)
bool ListInsert(Linklist& L, int i, int e) {
	if (i < 1)							//判断插入位置是否合法
		return false;
	//LNode* p = GetElem(L, i - 1);		//可以用封装
	LNode* p;
	int j = 0;
	p = L;
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}
	
	if (p == NULL)						//判断插入位置是否合法,防止i太大(如果一共有四个元素,此时想在第六个位置插入数据,当GetElem循环里j==5时,p==null,即插入位置不合法)
		return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
	//return InsertNextNode(p, e);			//可以用封装
}

7.指定结点的后插操作

//指定结点的后插操作 
bool InsertNextNode(LNode* p, int e) {
	if (p == NULL)						//这里的p==NULL判断是为了ListInsert里调用时,会出现GetElem方法中返回的p会有NULL的情况--------提高健壮性
		return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL)						//判断内存是否分配失败,如内存不足(不是核心代码)
		return false;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

8.前插操作

//前插操作:在p结点之前插入元素e			
//方法1、传入头指针,循环寻找p的前驱结点------时间复杂度O(n)
//方法2、偷天换日-----------------------------时间复杂度O(1)
//这里用的方法2		效果:*s插入*p的前面	实现:首先仍将*s插入到*p的后面,再将p->data和s->data(即e)的值互换---------此时:时间复杂度O(1)
bool InsertPriorNode(LNode* p, int e) {
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (p == NULL|| s == NULL)
		return false;
	s->next = p->next;
	p->next = s;
	s->data = p->data;
	p->data = e;
	return true;
}

9.按位序删除

//按位序删除
bool ListDelete(Linklist& L, int i, int& e) {
	if (i < 1)
		return false;
	//LNode* p = GetElem(L, i - 1);			//可以用封装
	LNode* p;
	int j = 0;
	p = L;
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}

	if (p == NULL || p->next == NULL)
		return false;
	LNode* q = p->next;
	e = q->data;
	p->next = q->next;
	free(q);
	return true;
}

10.删除指定结点

//删除指定结点q----------类似于之前的前插操作
bool DeleteNode(LNode* p) {
	if (p == NULL)
		return false;
	LNode* q = p->next;
	p->data = p->next->data;		//有bug如果p是最后一个结点,,,不可取这种方法,只能从表头开始依次寻找p的前驱,时间复杂度O(n)
	p->next = q->next;
	free(q);
	return true;
}

11.求表长度

//求表长度
int Length(Linklist L) {
	int len = 0;
	LNode* p = L;
	while (p->next != NULL) {
		p = p->next;
		len++;
	}
	return len;
}

12.打印

void print(Linklist L) {
	LNode* p = (LNode*)malloc(sizeof(LNode));
	p = L->next;
	while (p != NULL) {
		printf("%d  ", p->data);
		p = p->next;
	}

}

13.主函数

int main(){
	Linklist L;
	printf("\n头插法创建单链表L1---请输入单链表元素(以9999结束):");
	List_HeadInsert(L);
	printf("单链表L1:  ");
	print(L);
	printf("\n尾插法建立单链表L2---请输入单链表元素(以9999结束):");
	List_TailInsert(L);
	printf("单链表L2:  ");
	print(L);
	int findByLocation = GetElem(L, 2)->data;
	printf("\n查找L2中位序为2的值:%d", findByLocation);
	ListInsert(L, 3, 77);
	printf("\n在L2第3个位置按位插入结点77:");
	print(L);
	int e;
	if (ListDelete(L, 3, e))
		printf("\n删除L2中位序为3的结点,删除值为:%d", e);
	else
		printf("\n删除位置不合法");
	printf("\n删除后单链表L2:");
	print(L);
	
	return 0;
}

实际编程时尽量使用封装,为了方便学习和回顾操作内部逻辑关系,没有选择封装。

3.运行结果

在这里插入图片描述

二、双链表

双链表中的按值查找和按位查找与单链表相同,删除、插入不同,因为方便找到前驱结点,所以插入删除操作时间复杂度O(1)

1.双链表的存储结构

//双链表---定义双链表结点类型
typedef struct DNode {
	int data;
	struct DNode* prior, * next;
}DNode, * DLinkList;

2.双链表的插入操作

	s->next = p->next;
	p->next->prior = s;
	s->prior = p;
	p->next = s;

3.双链表的删除操作

	p->next = q->next;
	q->next->prior = p;
	free(p);

三、循环链表

1.循环单链表

1、循环单链表和单链表区别在于:循环单链表中最后一个结点指向的不是NULL,而是指向头结点
2、判空条件:是否等于头指针(p!=L或p->next!=L,不带头结点和带头结点)
3、循环单链表不设头指针,而设尾指针,使得操作效率更高。若头指针,对表尾操作需要O(n);若尾指针为r,r->next即为头指针,对表头表尾的操作都只需要O(1)。

2.循环双链表

带头结点的双循环链表L判断空表条件:

L->prior==L&&L->next==L

四.静态链表

1、静态链表借助数组来描述线性表的链式存储结构
2、结点包含数据域data和指针域next
3、这里的指针是结点的相对地址(数组下标),又称游标
4、和顺序表一样,静态链表也需预先分配一块连续的内存空间
5、带头结点的双循环链表L判断空表条件:
6、静态链表以next==-1作为其结束的标志
7、静态链表插入删除操作与动态链表相同,只需修改指针,不需移动元素
静态链表结构类型描述

//静态链表结构类型描述
#define MaxSize=50
typedef struct {
	ElemType data;
	int next;
}SLinkList[MaxSize];

五.顺序表和链表的比较

1.逻辑结构

都是线性表

2.存储结构

线性表:
随机存取、存储密度大;
大片连续空间分配不方便,改变容量大小不方便;

链表:
离散的小空间分配方便,改容量方便;
不可随机存取,存储密度低;

3.数据的运算

顺序表:
1、销毁
静态分配:静态数组(销毁时系统自动回收)
动态分配:动态数组(销毁时手动free;malloc和free----创建和销毁----是成对出现)
2、插入和删除
插入或删除都要依次移动后面或前面的数据元素,时间复杂度O(n)
(这里的时间复杂度O(n)主要进行元素移动操作)
3、查找
按位查找:O(1)----随机存取
按值查找:O(n),若表内元素有序,折半查找等O(log2n)

链表:
1、销毁
依次删除各结点(free)
2、插入和删除
插入和删除只需要修改指针,时间复杂度O(n)
(这里的时间复杂度O(n)主要进行元素查找操作。结合考虑现实因素:如果数据元素大,则顺序表的O(n)移动的时间代价大,相比链表查找元素的时间代价更低)
3、查找
按值查找:O(n)
按位查找:O(n)

顺序表和链表的选择三方面:
逻辑结构----都是线性结构****
存储结构----顺序表***链表***
基本操作----初始化、插入、删除、查找***


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值