没有指针和对象引用怎么实现单链表?一次性搞懂静态链表的原理与实现

✨一、静态链表的提出背景

  C语言的指针是个好东西,它可以让我们轻松的操纵内存,所以我们可以比较轻松的实现链表这一数据结构,后来的一些面向对象编程的高级语言,由于存在对象引用机制,从某种角度上也间接的实现了指针的某些作用。但对于一些语言,如Basic、Fortran等早期高级编程语言,没有指针功能,那他们是怎么实现链表的呢?

  这里就不得不感叹前人的智慧,居然想出了用数组代替指针描述单链表,我们来看看他们是怎么实现的。

✨二、静态链表的原理

🔥2.1 静态链表的数据类型

  首先,我们定义一个结构体类型Node作为节点,为了后续方便,我们把元素个数有MAXSIZE个的Node结构体数组这种类型重定义为StaticLinklist。

#define MAXSIZE 1000//定义最大长度
typedef struct {
	int data;
	int next;//next代表下一成员的下标
}Node,StaticLinklist[MAXSIZE];

🔥2.2 静态链表所在的空间的初始化

我们利用一个初始化函数把这个结构体数组初始化成下图

在这里插入图片描述

  数组的最后一个元素我们计划作为头结点一样的东西,此时他的next放0表明头结点还没有建立。

  数组的0下标元素作为一个提示域,它的next值代表目前第一个还没被申请的节点的下标。

int InitLinklist(StaticLinklist space)
{
	if (MAXSIZE - 1 <= 0)
	{
		return -1;
	}
	for (int i = 0; i < MAXSIZE - 1; i++)
	{
		space[i].next = i + 1;
	}
	space[MAXSIZE - 1].next = 0;
	return 0;
}

🔥2.3 创建静态链表——创建头结点并按顺序输入n个元素

  接下来要输入元素了,首先我们要创建头结点,也就是让最后一个元素的next值为1(其实不为1也行,不过这样很麻烦),然后顺序输入n个元素,最后把最后一个元素的next值赋成0表示当前链表到达结尾了。

//顺序输入链表的n个元素
int inputlist(StaticLinklist L, int n)
{
	if (n >= MAXSIZE - 1)
		return -1;
	L[MAXSIZE - 1].next = 1;
	int i = 1;
	for (i = 1; i <= n; i++)
	{
		printf("请输入链表的第%d个元素\n", i);
		scanf("%d", &L[i].data);
	}
	L[i - 1].next = 0;
	L[0].next = i;
	return 0;
}

输入完成后我们的链表状态如图:

在这里插入图片描述

🔥2.4 在链表的第i个位置插入元素

  与在链表中一样,我们要插入元素必须向空间申请一个空闲的节点给我们来用,所以需要一个static_malloc函数,我们向这个函数传入我们的静态链表,它向我们返回一个开辟好的待使用的位置的下标,并且把0位置的next下标更新为下一个空闲的节点的下标。

//为实现像链表一样的插入元素 我们也需要一个malloc函数 传入我们的静态表 返回一个它为我们开辟好的待用的位置的下标
//返回-1表示没有空间了 插入失败
int static_malloc(StaticLinklist space)
{
	int i = space[0].next;
	if (i != 0 && i!=MAXSIZE-1)
	{
		space[0].next = space[i].next;
		return i;
	}
	else
	{
		return -1;
	}
}

  接下来就是要插入元素了,这里和链表中很像,我们先找到第i-1个元素,让我们申请的节点的next等于它的next,让第i-1个节点的next值等于我们申请的节点的下标,然后让我们申请的这个节点的值输入进去,不过,这里找第i-1个元素的方法和链表中看起来有所不同,其实是相似的,都是从头结点走i-1步,只不过有指针的情况下我们可以用指针直接实现,没有指针的情况下我们要用下标的next值(这个代替了指针功能的东西)来走到第i-1个元素,先从MAXSIZE-1开始走,k=MAXSIZE-1,k=L[k].next—走一步走到第一个节点的位置,k=L[k].next走两步走到第二个节点的位置,走i-1次,走到第i-1个节点。

//插入静态表的第i个元素
int Insertlist(StaticLinklist space, int i, int e)
{
	if (i <= 0 || i > MAXSIZE - 1)
		return -1;
	int k = MAXSIZE - 1;
	//要插入第i个元素,首先要走到第i-1个元素的位置
	for (int j = 1; j <= i - 1; j++)
	{
		k = space[k].next;
		//循环一次,走到第一个链表第一个元素,两次,走到链表第一个元素
		//循环i-1次,走到链表的第i-1个元素
	}
	int m = static_malloc(space);
	//先把i-1号元素(也就是下标为j的元素)的next赋给m的next
	space[m].next = space[k].next;
	//然后再让i-1的next是m 就连上了链表
	space[k].next = m;
	space[m].data = e;
	return 0;
}

  插入操作的时间复杂度是O(N),N是链表当前长度。

🔥2.5 静态链表长度函数与静态链表打印函数

  为了查看我们的静态链表的实现情况,我们可以设计一个静态链表打印函数来看我们的链表的状态,为了打印链表,我们需要知道链表的长度(其实也可以不用,有个求链表长度的函数方便一点)。

  实现求静态链表长度的函数和打印静态链表的函数是类似的,都是从头结点开始走一直走到next为0的节点为止,利用计数器统计自己走了几步静态链表就是多长。

//链表长度函数
int listlen(StaticLinklist L)
{
	int j = L[MAXSIZE-1].next;
	int i = 0;
	while (j)
	{
		j = L[j].next;
		i++;
	}
	return i;
}
//打印链表的函数
void print(StaticLinklist L)
{
	
	int len = listlen(L);
	if (len == 0)
	{
		printf("链表中没有元素空空如也\n");
		return;
	}
	int k = MAXSIZE - 1;
	for (int i = 1; i <= len; i++)
	{
		k = L[k].next;
		printf("静态链表的第%d个元素是%d\n", i, L[k].data);
	}
}

🔥2.6 删除链表中的第i个节点

  与用指针实现的单链表类似,我们需要一个static_free函数去把删除节点归还给空间,与静态链表不同的是,我们归还给空间后要让我们换的这个元素和原本的第一个空闲元素先连上,然后让空间的指示域的next值变成我们还的这个元素的下标。

//释放下标为i的节点,把他放回待使用的节点中
void static_free(StaticLinklist L, int i)
{
	//把当前0下标的next指向的可用的元素的下标赋给L[i].next
	L[i].next = L[0].next;
	//让0下标的next指向i下标 这样吧i连到了原本的待用元素的前面
	L[0].next = i;
}

  删除第i个节点的函数的实现原理和用指针实现的单链表类似,都是先把第i个元素的next值赋给第i-1个元素的next值,然后static_free第i个元素就可以了。

//删除第i个元素
int listdelete_bynumber(StaticLinklist L, int i)
{
	if (i<1 || i>listlen(L))
		return -1;
	int k = MAXSIZE - 1;
	int j = 1;
	for (j = 1; j <= i - 1; j++)
	{
		k = L[k].next;
	}
	j = L[k].next;
	int m = L[j].next;
	L[k].next = m;
	static_free(L, j);
	return 0;
}

  删除操作的时间复杂度是O(N),N是链表当前长度。

🔥2.7 在链表中查找元素c与在链表中删除元素c

  查找函数的实现也是通过k=MAXSIZE-1 k=L[k].next遍历静态链表实现的。

//查找元素c
//若查找成功,返回查找到的元素在链表中的次序位置
//若查找失败 返回0
int listsearch(StaticLinklist L, int c)
{
	int k = MAXSIZE - 1;
	int ret = 0;
	for (int i = 1; i <= listlen(L); i++)
	{
		k = L[k].next;
		if (L[k].data == c)
		{
			ret = i;
			break;
		}
	}
	return ret;
}

  查找函数的时间复杂度也是O(N),N是当前链表长度。

  删除元素c的节点的操作是这样实现的,首先利用查找函数判断c元素是否在链表中,如果在的话,利用查找函数返回的元素在链表中的次序m,利用2.6解中的删除链表中的第m个节点,这样就实现了这个函数。

//删除c元素
//删除失败返回-1
//删除成功返回0
int listdelete_byvalue(StaticLinklist L, int value)
{
	int m = listsearch(L, value);
	if (m == 0)
	{
		printf("删除元素不存在\n");
		return -1;
	}
	listdelete_bynumber(L, m);
	return 0;
}

  删除指定元素的时间复杂度也是O(N).N是当前链表长度。

🔥2.8 静态链表排序(待实现)

🔥2.9 销毁静态链表

  与销毁单链表相似,我们使用两个指针作用的整形p q来实现销毁链表,首先,让p存第一个节点的下标,让q存p的下一个节点的下标(也就是L[p].next),然后static_free§,然后把q赋值给p,把p的下一个节点的下标赋值给q,如此循环往复,直到p走过链表的结尾来到指示域结束循环,然后让头结点的next值赋为0表示链表已被销毁,回到初始化后的状态了。

  有人会说,哎呀,你怎么不直接只用一个p不断static_free§ p=L[p].next就行了,这是新手常犯的错误,再好好思考一下,static_free§以后原本p的next的信息会丢失,因为你已经还给链表空间了,你还了以后p的next值是下一个空闲的节点的下标,所以这样并不能一路销毁我们的静态链表。

//销毁链表
void destroylist(StaticLinklist L)
{
	int p, q;
	p = L[MAXSIZE - 1].next;
	q = L[p].next;
	while (p != 0)
	{
		static_free(L, p);
		p = q;
		q = L[q].next;
	}
	L[MAXSIZE - 1].next = 0;
}

  销毁链表函数的时间复杂度也是O(N),N为链表长度。

✨三、静态链表的优缺点

😂优点:

  • 相较于顺序表,在插入和删除的操作时,只需要变化next值,不需要移动元素,从而改进了顺序存储结构中插入和删除操作需要大量移动元素的缺点。

🎃缺点:

  • 没有解决连续分配导致的明明有足够的空间却扩容失败的问题。
  • 失去了链式存储结构随机读取的特性。

✨四、汇总与测试函数

//"staticlinklist.h"
#ifndef __STATICLINKLIST__H__
#define __STATICLINKLIST__H__
#include<stdio.h>
#include <Windows.h>
#define MAXSIZE 1000//定义最大长度
typedef struct {
	int data;
	int next;
}Node,StaticLinklist[MAXSIZE];

int InitLinklist(StaticLinklist space);//初始化静态链表所在的结构体数组空间

int static_malloc(StaticLinklist space);
//为实现像链表一样的插入元素 我们也需要一个malloc函数 传入我们的静态表 返回一个它为我们开辟好的待用的位置的下标

int inputlist(StaticLinklist L, int n);//创建链表并按顺序向链表输入n个元素

int Insertlist(StaticLinklist space, int i, int e);//在链表的第i个节点的位置插入元素

int listlen(StaticLinklist L);//链表长度函数

void print(StaticLinklist L);//打印链表的函数

void static_free(StaticLinklist L, int i);//释放下标为i的节点,把他放回待使用的节点中

int listdelete_bynumber(StaticLinklist L, int i);//删除第i个元素

int listsearch(StaticLinklist L, int c);//查找元素c

int listdelete_byvalue(StaticLinklist L, int value);//删除c元素

void sortlist(StaticLinklist L);//链表排序(待实现)

void destroylist(StaticLinklist L);//销毁链表


#endif
//"staticlinklist.c"
#include "staticlinklist.h"
int InitLinklist(StaticLinklist space)
{
	if (MAXSIZE - 1 <= 0)
	{
		return -1;
	}
	for (int i = 0; i < MAXSIZE - 1; i++)
	{
		space[i].next = i + 1;
	}
	space[MAXSIZE - 1].next = 0;
	return 0;
}

//为实现像链表一样的插入元素 我们也需要一个malloc函数 传入我们的静态表 返回一个它为我们开辟好的待用的位置的下标
//返回-1表示没有空间了 插入失败
int static_malloc(StaticLinklist space)
{
	int i = space[0].next;
	if (i != 0 && i!=MAXSIZE-1)
	{
		space[0].next = space[i].next;
		return i;
	}
	else
	{
		return -1;
	}

}

//顺序输入链表的n个元素
int inputlist(StaticLinklist L, int n)
{
	if (n >= MAXSIZE - 1)
		return -1;
	L[MAXSIZE - 1].next = 1;
	int i = 1;
	for (i = 1; i <= n; i++)
	{
		printf("请输入链表的第%d个元素\n", i);
		scanf("%d", &L[i].data);
	}
	L[i - 1].next = 0;
	L[0].next = i;
	return 0;
}

//插入静态表的第i个元素
int Insertlist(StaticLinklist space, int i, int e)
{
	if (i <= 0 || i > MAXSIZE - 1)
		return -1;
	int k = MAXSIZE - 1;
	//要插入第i个元素,首先要走到第i-1个元素的位置
	for (int j = 1; j <= i - 1; j++)
	{
		k = space[k].next;
		//循环一次,走到第一个链表第一个元素,两次,走到链表第一个元素
		//循环i-1次,走到链表的第i-1个元素
	}
	int m = static_malloc(space);
	//先把i-1号元素(也就是下标为j的元素)的next赋给m的next
	space[m].next = space[k].next;
	//然后再让i-1的next是m 就连上了链表
	space[k].next = m;
	space[m].data = e;
	return 0;
}

//链表长度函数
int listlen(StaticLinklist L)
{
	int j = L[MAXSIZE-1].next;
	int i = 0;
	while (j)
	{
		j = L[j].next;
		i++;
	}
	return i;
}

//打印链表的函数
void print(StaticLinklist L)
{
	
	int len = listlen(L);
	if (len == 0)
	{
		printf("链表中没有元素空空如也\n");
		return;
	}
	int k = MAXSIZE - 1;
	for (int i = 1; i <= len; i++)
	{
		k = L[k].next;
		printf("静态链表的第%d个元素是%d\n", i, L[k].data);
	}
}


//释放下标为i的节点,把他放回待使用的节点中
void static_free(StaticLinklist L, int i)
{
	//把当前0下标的next指向的可用的元素的下标赋给L[i].next
	L[i].next = L[0].next;
	//让0下标的next指向i下标 这样吧i连到了原本的待用元素的前面
	L[0].next = i;
}

//删除第i个元素
int listdelete_bynumber(StaticLinklist L, int i)
{
	if (i<1 || i>listlen(L))
		return -1;
	int k = MAXSIZE - 1;
	int j = 1;
	for (j = 1; j <= i - 1; j++)
	{
		k = L[k].next;
	}
	j = L[k].next;
	int m = L[j].next;
	L[k].next = m;
	static_free(L, j);
	return 0;
}

//查找元素c
//若查找成功,返回查找到的元素在链表中的次序位置
//若查找失败 返回0
int listsearch(StaticLinklist L, int c)
{
	int k = MAXSIZE - 1;
	int ret = 0;
	for (int i = 1; i <= listlen(L); i++)
	{
		k = L[k].next;
		if (L[k].data == c)
		{
			ret = i;
			break;
		}
	}
	return ret;
}

//删除c元素
//删除失败返回-1
//删除成功返回0
int listdelete_byvalue(StaticLinklist L, int value)
{
	int m = listsearch(L, value);
	if (m == 0)
	{
		printf("删除元素不存在\n");
		return -1;
	}
	listdelete_bynumber(L, m);
	///*int k = MAXSIZE - 1;
	//int i = 1;
	//for (i = 1; i <= m - 1; i++)
	//{
	//	k = L[k].next;
	//}
	//i = L[k].next;
	//L[k].next = L[i].next;
	//static_free(L, i);*/
	return 0;
}

//销毁链表
void destroylist(StaticLinklist L)
{
	int p, q;
	p = L[MAXSIZE - 1].next;
	q = L[p].next;
	while (p != 0)
	{
		static_free(L, p);
		p = q;
		q = L[q].next;
	}
	L[MAXSIZE - 1].next = 0;
}
//test.c
#include "staticlinklist.h"
int main()
{
    //测试创建静态链表
	StaticLinklist L;
	InitLinklist(L);
	printf("请输入要创建链表的元素个数\n");
	int n;
	scanf("%d", &n);
	inputlist(L, n);
	print(L);
    
    //测试插入函数
	printf("请输入要插入的位置i和插入元素c\n");
	printf("i=");
	int i = 0;
	scanf("%d", &i);
	printf("c=");
	int c = 0;
	scanf("%d", &c);
	Insertlist(L, i, c);
	print(L);
    
    //测试删除函数
	printf("请输入你要删除第几个元素\n");
	int k = 0;
	printf("k=");
	scanf("%d", &k);
	listdelete_bynumber(L, k);
	print(L);
    
    //测试查找函数和按值删除函数
	printf("请输入你要查找的元素:\n");
	int m = 0;
	scanf("%d", &m);
	if (listsearch(L, m) == 0)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,是链表的第%d个元素\n", listsearch(L, m));
	}
	printf("请输入你要删除的元素\n");
	int w = 0;
	scanf("%d", &w);
    
    //测试销毁链表函数
	listdelete_byvalue(L, w);
	print(L);
	printf("即将销毁链表\n");
	Sleep(5000);
	destroylist(L);
	print(L);
	return 0;
}

✨五、参考文献

1.《大话数据结构》 程杰 清华大学出版社

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值