C语言 | 万字详解线性表之链表

<一>单链表

1.定义结点以及增加一个新的结点

  • 使用struct定义

    struct LNode {
    	int data;  //数据域
    	struct LNode *next; //指针域
    };
    //开辟一个结点的空间
    struct LNode *p = (struct LNode*)malloc(sizeof(struct LNode));
    
  • 用typedef关键字简化代码–使用别名代替struct LNode,语法:

    typedef <数据类型> <别名>
    

    typedef struct LNode{
    	int data;  //数据域
    	struct LNode *next; //指针域
    }LNode,*LinkList;  //可以理解为给struct LNode取别名,此后LNode即struct LNode,LinkList即struct LNode*
    //开辟一个结点的空间
    LNode *p = (LNode*)malloc(sizeof(LNode));
    //要表示一个单链表,只需声明一个头指针,指向单链表的第一个结点
    LinkList L;  //等价于 LNode *L;  虽然效果上一样,但为了良好的可读性,前者强调这是一个单链表,后者强调这是一个普通的结点
    

2.初始化

初始化一个不带结点的单链表 (即将表头置为NULL,表示还没有任何结点,这是一个空表,初始化目的是为了防止有脏数据)

//为了便于此后判空的操作,设计返回类型为bool
bool InitList(LinkList &L) {
	L = NULL;
	return true;
}

初始化一个带头节点的单链表(头节点本身不存放数据,数据存在头节点之后的结点里)

bool InitList(LinkList &L) {
	L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点
	if (L == NULL) //内存不足,分配失败
		return false;
	L->next = NULL; //头节点之后无结点,初始化为空
	return true;
}

带头节点初始化一个单链表对之后的操作更方便,因此推荐。(比如按次序的插入操作,如果插在第一个位置,那么带头节点的(不含数据)便可看作是第0个结点,操作则和其它位置插入相同;如果带不带头结点,插入在表首时,表头被更新,还需要重置表头)

3.判空

不带头节点的单链表-判空

bool Empty(LinkList L) {
	return L == NULL;
}

带头节点的单链表-判空

bool Empty(LinkList L) {
	return L->next == NULL;
}

4.插入

按位序在链表中插入

按位序插入(带头节点)

在这里插入图片描述

bool ListInsert(LinkList &L, int i, int e) { //在第i个节点处插入数据e
	if (i < 1)
		return false;
	LNode *p;
	int j = 0; //表示当前p指向第几个结点,头节点看作第0个结点,头节点不存放数据
	p = L;
	while (p != NULL && j < i - 1) {  //循环找到第i-1个结点位置[i]
		p = p->next;
		j++;
	}
	if (p == NULL) //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

按位序插入(不带头节点)(在带头结点的插入操作基础上,对插在第一个位置进行特殊处理)

//不带头结点的插入操作,对插在表首进行特殊处理
/*
if (i == 1) {
	LNode *s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = L;
	L = s; //表头重置
	return true;
}
*/
//合并即可
bool ListInsert(LinkList &L, int i, int e) { //在第i个节点处插入数据e
	if (i < 1)
		return false;
	if (i == 1) {
		LNode *s = (LNode*)malloc(sizeof(LNode));
		s->data = e;
		s->next = L;
		L = s;
		return true;
	}
	LNode *p;
	int j = 0; //表示当前p指向第几个结点,头节点看作第0个结点,头节点不存放数据
	p = L;
	while (p != NULL && j < i - 1) {  //循环找到第i-1个结点位置[i]
		p = p->next;
		j++;
	}
	if (p == NULL) //i值不合法
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}
指定结点的后插操作
bool InsertNextNode(LNode *p, int e) {
	if (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; //插入成功
}
指定结点的前插操作

因为节点只能往后链接到其它节点,因此,对于前插操作,考虑应该引入头指针,才能找到该节点前面的一个节点,需要循环查找到该结点的前驱,那么时间复杂度为O(n)。

从另一个角度考虑,可以前插操作可以:先直接后插,然后交换数据,这样达到了和前插一样的效果,同时时间复杂度为O(1)。

bool InsertPriorNode(LNode *p, int e) {
	if (p == NULL) //p指向的是表中的一个结点(不考虑表头、表尾的下一个NULL,当它为NULL,即不合法)
		return false;
	LNode *s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL) //内存分配失败
		return false;
	//先后插
	s->next = p->next;
	p->next = s;
	//然后交换数据
	s->data = p->data;
	p->data = e;
	return true;
}

5.删除

按位序删除
  • 带头结点(找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点)

    bool ListDelete(LinkList &L, int i, int &e) { //删除第i个元素,并将被删除的值返回给e
    	if (i < 1)
    		return false;
    	LNode *p;
    	int j = 0; //当前p指向第几个结点
    	p = L; //指向不存数据的第0个结点
    	while (p != NULL && j < i - 1) { //找到第i-1个结点[i]
    		p = p->next;
    		j++;
    	}
    	if (p == NULL) //i值不合法,即第i-1个结点不存在
    		return false;
    	if (p->next == NULL) //第i-1个结点后面已经没有结点,即第i个结点不存在
    		return false;
    	LNode *q = p->next; //q指向被删除的结点
    	e = q->data; //用e返回被删除结点的值
    	p->next = q->next;
    	free(q);
    	return true;
    }
    
  • 不带头结点(即在带头结点的基础上,对删除第一个结点特殊处理,因为要重置表头)

    //不带头结点,对删除第一个结点特殊处理
    /*
    if (i == 1) {
    		LNode *s = L; //s指向头结点
    		L = s->next; //表头指向第2个结点,即重置表头
    		free(s); //释放原来第一个结点
    	}
    */
    //合并
    bool ListDelete(LinkList &L, int i, int &e) { //删除第i个元素,并将被删除的值返回给e
    	if (i < 1)
    		return false;
    	if (i == 1) {
    		LNode *s = L; //s指向头结点
    		L = s->next; //表头指向第2个结点,即重置表头
    		free(s); //释放原来第一个结点
    	}
    	LNode *p;
    	int j = 0; //当前p指向第几个结点
    	p = L; //指向不存数据的第0个结点
    	while (p != NULL && j < i - 1) { //找到第i-1个结点[i]
    		p = p->next;
    		j++;
    	}
    	if (p == NULL) //i值不合法,即第i-1个结点不存在
    		return false;
    	if (p->next == NULL) //第i-1个结点后面已经没有结点,即第i个结点不存在
    		return false;
    	LNode *q = p->next; //q指向被删除的结点
    	e = q->data; //用e返回被删除结点的值
    	p->next = q->next;
    	free(q);
    	return true;
    }
    
指定结点删除

和指定结点前插类似,第一种思路,传入头指针,循环找到其前驱,修改其前驱的next指针,循环查找过程,时间复杂度为O(n);

第二种思路,偷天换日,将需要删除的结点的后继的数据赋值给它,然后修改它的next指针,删除它的后继即可

//带头结点
bool DeleteNode(LNode *p) {
	if (p == NULL)
		return false;
	//需要删除的p结点是最后一个结点
	if (p->next == NULL)//此时只能用方法1循环找到它的前驱,因为此时用方法2在数据域赋值时会报错,NULL无数据域
	{
		LNode *q = L;
		while (q != NULL && q->next!=p) {//循环退出时q->next==p,即q为p的前驱
			q = q->next;
		}
		q->next = p->next; //修改需要删除的结点的前驱的next指针
		free(p);
	}
	LNode *q = p->next; //q指向需要删除的结点的后继
	p->data = q->data; //将它的后继的数据赋值给它
	p->next = q->next; //修改它的next指针,将后继从链表中排除
	free(q); //释放后继结点
}

6.查找

按位查找
//带头结点的按位查找,返回第i个结点,头结点不存数据,视为第0个结点
LNode* GetElem(LinkList L, int i) {
	if (i == 0)
		return L; //返回头结点
	if (i < 0)
		return false;
	LNode *p = L->next; //p指向第1个结点
	int j = 1; //j记录当前p指向第几个结点
	while (p != NULL && j < i) { //循环找到第i个结点
		p = p->next;
		j++;
	}
	return p;
}
//不带头结点的按位查找,返回第i个结点
LNode* GetElem(LinkList L, int i) {
	if (i <= 0)
		return false;
	LNode *p = L; //p指向第1个结点
	int j = 1; //j记录当前p指向第几个结点
	while (p != NULL && j < i) { //循环找到第i个结点
		p = p->next;
		j++;
	}
	return p;
}
按值查找
//按值查找(带头结点)
LNode* LocateElem(LinkList L, int e) {
	LNode *p = L->next; //p指向第一个结点
	while (p != NULL && p->data != e)
		p = p->next;
	return p; //找到后返回该指针,若不存在会返回NULL
}

7.求表长

//求单链表长度(带头结点)
int Length(LinkList L) {
	LNode *p = L;
	int len = 0;
	while (p->next != NULL) {
		p = p->next;
		len++;
	}
	return len;
}

8.打印

//打印单链表(带头结点)
void Show(LinkList L) {
	LNode *s = L;
    printf("此单链表为:");
	while (s->next) {
		s = s->next;
		printf("%d ", s->data);
	}
}

9.单链表的建立

以带头结点的单链表为例

尾插法

每次插入都记录表尾,以避免每次需要循环找到表尾。

//尾插法建立单链表(带头结点)
LinkList TailBuild(LinkList &L) {
	L = (LinkList)malloc(sizeof(LNode)); //建立头结点
	printf("请输入单链表数据:");
	int x; //设置x临时存储用户输入的数据
	LNode *s, *r = L; //s为临时结点,r记录当前表尾
	while (~scanf("%d", &x)) { //只要用户输入的不是ctrl+z,即不是EOF,那么进入循环,继续获取用户输入
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s; //连接
		r = s; //重置表尾
	}
	r->next = NULL;
	return L;
}

测试

int main() {
	LinkList test;
	test = TailBuild(test);
	Show(test);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvZbz7Pu-1651417439641)(D:\AboutMyCollege\XMind\数据结构\运行示例\尾插法创建单链表测试.png)]

头插法

即逆向建立单链表

和链表插入思想一致

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mqTBvzw2-1651417439642)(D:\AboutMyCollege\XMind\数据结构\头插法建立单链表图解.jpg)]

//头插法建立单链表(带头结点)
LinkList HeadBuild(LinkList &L) {
	L = (LinkList)malloc(sizeof(LNode)); //建立头结点
	L->next = NULL;
	printf("请输入单链表数据:");
	int x; //设置x临时存储用户输入的数据
	LNode *s;
	while (~scanf("%d", &x)) { //只要用户输入的不是ctrl+z,即不是EOF,那么进入循环,继续获取用户输入
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next; //s为第一个结点
		L->next = s; //更新头结点的指向
	}
	return L;
}

测试

int main() {
	LinkList test;
	test = HeadBuild(test);
	Show(test);
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2K4IQLN-1651417439643)(D:\AboutMyCollege\XMind\数据结构\运行示例\头插法创建单链表测试.png)]

<二>双链表

DNode,即double node

单链表是单向的,一个结点含数据域和一个指针域next,一个结点只能通过其next指向它的后继,无法直接获取它的前驱。而双链表则是双向的,一个结点含两个指针域,一个指针域prior指向它的前驱,next指向它的后继。

以下以带头结点的双链表示例

1.结点定义

#include<stdio.h>
#include<stdlib.h>
typedef struct DNode {
	int data;
	struct DNode *prior, *next;
}DNode,*DLinkList;

2.初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRtxOS45-1651417439643)(D:\AboutMyCollege\XMind\数据结构\双链表初始化.jpg)]

//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L) {
	L = (DLinkList)malloc(sizeof(DNode));  //分配一个头结点
	if (L == NULL)
		return false; //内存不足,分配失败
	L->prior = NULL; //头结点的prior永远为NULL
	L->next = NULL;
	return true;
}

3.判空

bool Empty(DLinkList L) {
	return (L->next == NULL);
}

4.插入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZPQMuT6-1651417439644)(D:\AboutMyCollege\XMind\数据结构\双链表之插入结点.jpg)]

  • 指定结点的后插操作

    //在p结点之后插入s结点
    bool InsertNextDNode(DNode *p, DNode *s) {
    	if (p == NULL || s == NULL)
    		return false; //参数非法
    	s->next = p->next;
    	if (p->next != NULL) //如果p结点不是最后一个结点
    		p->next->prior = s;
    	s->prior = p;
    	p->next = s;
    	return true;
    }
    
  • 按位序插入

    //按位序插入s,找到插入位置的前驱结点p,然后问题和在p结点之后插入s等同
    bool InsertDNode(DLinkList &L, int i, int e) { //在第i个结点处插入值为e的结点
    	if (i < 1)
    		return false; //i值不合理
    	DNode *p = L; //用p找到插入位置的前驱结点
    	int j = 0;//记录当前p指向第几个结点
    	while (p->next != NULL && j < i-1) { //找到第i个结点的前驱结点,即第i-1个结点
    		p = p->next;
    		j++;
    	}
    	DNode *s = (DNode*)malloc(sizeof(DNode));
    	s->data = e;
    	InsertNextDNode(p, s);
    	return true;
    }
    
  • 指定结点的前插操作

    //在p结点之前插入s结点,先直接后插,然后交换数据域即可
    bool InsertPriorDNode(DNode *p, DNode *s) {
    	InsertNextDNode(p, s);
    	int temp;
    	//交换数据
    	temp = p->data;
    	p->data = s->data;
    	s->data = temp;
    }
    

5.删除

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivQYigll-1651417439645)(D:\AboutMyCollege\XMind\数据结构\双链表之删除节点.jpg)]

//删除p的后继结点
bool DeleteNextDNode(DNode *p) {
	if (p == NULL)
		return false;
	DNode *q = p->next; //q为待删除的结点,即p的后继结点
	if (q == NULL) //即p没有后继
		return false;
	p->next = q->next;
	if (q->next != NULL) //即q不是最后一个结点
		q->next->prior = p; 
	free(q);
	return true;
}

6.销毁

//销毁双链表(循环释放所有结点)
void DestoryList(DLinkList &L) {
	while (L->next != NULL)
		DeleteNextDNode(L);
	free(L);
	L = NULL;
}

7.遍历

  • 后向遍历(顺序遍历)

    //双链表的后向遍历
    void Show(DLinkList L) {
    	DNode *p = L;
    	printf("顺序遍历双链表:");
    	while (p->next != NULL) {
    		p = p->next;
    		printf("%d ", p->data);
    	}
    }
    
  • 前向遍历(逆序遍历)

    //双链表的前向遍历
    //先找到表尾
    void ReverseShow(DLinkList L) {
    	DNode *p = L;
    	//找到最后一个结点
    	while (p->next != NULL)
    		p = p->next;
    	//前向遍历
    	printf("逆序遍历双链表:");
    	while (p->prior != NULL) {
    		printf("%d ", p->data);
    		p = p->prior;
    	}
    }
    

8.完整代码与测试

#include<stdio.h>
#include<stdlib.h>
typedef struct DNode {
	int data;
	struct DNode *prior, *next;
}DNode,*DLinkList;
//初始化双链表(带头结点)
bool InitDLinkList(DLinkList &L) {
	L = (DLinkList)malloc(sizeof(DNode));  //分配一个头结点
	if (L == NULL)
		return false; //内存不足,分配失败
	L->prior = NULL; //头结点的prior永远为NULL
	L->next = NULL;
	return true;
}
//判空(带头结点)
bool Empty(DLinkList L) {
	return (L->next == NULL);
}
//在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s) {
	if (p == NULL || s == NULL)
		return false; //参数非法
	s->next = p->next;
	if (p->next != NULL) //如果p结点不是最后一个结点
		p->next->prior = s;
	s->prior = p;
	p->next = s;
	return true;
}
//按位序插入s,找到插入位置的前驱结点p,然后问题和在p结点之后插入s等同
bool InsertDNode(DLinkList &L, int i, int e) { //在第i个结点处插入值为e的结点
	if (i < 1)
		return false; //i值不合理
	DNode *p = L; //用p找到插入位置的前驱结点
	int j = 0;//记录当前p指向第几个结点
	while (p->next != NULL && j < i-1) { //找到第i个结点的前驱结点,即第i-1个结点
		p = p->next;
		j++;
	}
	DNode *s = (DNode*)malloc(sizeof(DNode));
	s->data = e;
	InsertNextDNode(p, s);
	return true;
}
//在p结点之前插入s结点,先直接后插,然后交换数据域即可
bool InsertPriorDNode(DNode *p, DNode *s) {
	InsertNextDNode(p, s);
	int temp;
	//交换数据
	temp = p->data;
	p->data = s->data;
	s->data = temp;
}
//删除p的后继结点
bool DeleteNextDNode(DNode *p) {
	if (p == NULL)
		return false;
	DNode *q = p->next; //q为待删除的结点,即p的后继结点
	if (q == NULL) //即p没有后继
		return false;
	p->next = q->next;
	if (q->next != NULL) //即q不是最后一个结点
		q->next->prior = p; 
	free(q);
	return true;
}
//销毁双链表(循环释放所有结点)
void DestoryList(DLinkList &L) {
	while (L->next != NULL)
		DeleteNextDNode(L);
	free(L);
	L = NULL;
}
//双链表的后向遍历
void Show(DLinkList L) {
	DNode *p = L;
	printf("顺序遍历双链表:");
	while (p->next != NULL) {
		p = p->next;
		printf("%d ", p->data);
	}
}
//双链表的前向遍历
//先找到表尾
void ReverseShow(DLinkList L) {
	DNode *p = L;
	//找到最后一个结点
	while (p->next != NULL)
		p = p->next;
	//前向遍历
	printf("逆序遍历双链表:");
	while (p->prior != NULL) {
		printf("%d ", p->data);
		p = p->prior;
	}
}
int main() {
	DLinkList test;
	InitDLinkList(test);
	if (Empty(test))
		printf("此时双链表为空链表\n");
	int e;
	printf("请输入4个整数:");
	for (int i = 1; i < 5; i++) {
		scanf("%d", &e);
		InsertDNode(test, i, e);
	}
	Show(test);
	ReverseShow(test);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hkfTVBSz-1651417439645)(D:\AboutMyCollege\XMind\数据结构\运行示例\双链表代码测试.png)]

<三>循环链表

循环链表的好处:

  • 对于单链表,查找某个结点的前驱,可以直接从当前结点一直向后遍历,不用从头结点开始找(绕一圈,找到它的前驱);
  • 对于双链表,插入或删除等操作,不需要对尾结点特殊考虑,尾结点和内部结点操作是相同的

1.循环单链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T11iEf9a-1651417439646)(D:\AboutMyCollege\XMind\数据结构\循环单链表.png)]

#include<stdio.h>
#include<stdlib.h>
//循环单链表
//定义单链表结点类型
typedef struct LNode {
	int data;
	struct LNode *next;
}LNode,*LinkList;
//初始化一个带头结点的循环单链表,头结点的next指向自己
bool InitList(LinkList &L) {
	L = (LinkList)malloc(sizeof(LNode)); //分配一个头结点
	if (L == NULL)
		return false; //空间不足,分配失败
	L->next = L;
	return true;
}
//判空,头结点的next指向自己,即为空
bool Empty(LinkList L) {
	return (L->next == L);
}
//判断结点p是否是表尾结点
bool isTail(LinkList L, LNode *p) {
	return (p->next == L);
}
//查找指定结点的前驱结点
LNode* FindPriorNode(LNode *p) { //查找p的前驱结点
	LNode *q = p;
	while (q->next != p)
		q = q->next;
	return q;
}

2.循环双链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WLN2d0ad-1651417439647)(D:\AboutMyCollege\XMind\数据结构\循环双链表.png)]

#include<stdio.h>
#include<stdlib.h>
//循环双链表
//定义双链表的结点
typedef struct DNode {
	int data;
	struct DNode *prior, *next;
}DNode,*DLinkList;
//初始化(带头结点)(next,prior都指向自己)
bool InitDLinkList(DLinkList &L) {
	L = (DLinkList)malloc(sizeof(DNode));
	if (L == NULL)
		return false; //内存不足,空间分配失败
	L->prior = L;
	L->next = L;
	return true;
}
//判空,头结点的next指向自己,即为空
bool Empty(DLinkList L) {
	return (L->next == L);
}
//判断结点p是否是表尾结点
bool isTail(DLinkList L, DNode *p) {
	return (p->next == L);
}
//在p结点之后插入s结点(无需考虑p是否为尾结点)
bool InsertNextDNode(DNode *p, DNode *s) {
	if (s == NULL || p == NULL)
		return false;
	s->next = p->next;
	p->next->prior = s;
	s->prior = p;
	p->next = s;
}

<四>静态链表

1.静态链表的特点

静态链表又称为链表的游标(cursor)实现。有些语言,如BASIC,不支持指针,但又需要链表,而静态链表中的游标就是对链表指针的模拟。

链表指针的特点:
  1. 链表数据存储在一组结构体中,每一个结构体包含数据以及指向下一个结构体的指针
  2. 可以通过malloc开辟一个新的结构体,并能通过free将其释放
游标对上面两个特点的模拟:
  1. 用数组下标模拟指向下一个结构体的指针

    #define MAXSIZE 100
    typedef struct {
    	int data;
    	int cur;  //游标(cursor)
    }Component, StaticLinkList[MAXSIZE];
    
  2. 模拟malloc和free

    对数组StaticLinkList[MAXSIZE]的第一个元素和最后一个元素特殊处理。

    • 备用链表:未被使用的数组元素称为备用链表。数组的第一个元素的cur,即下标为0的元素的cur存放备用链表的第一个结点的下标。
    • 数组的最后一个元素的cur存放第一个存放了数据的元素的下标,相当于单链表中头结点的作用。最后一个存放数据的元素的cur为0,相当于单链表最后一个结点的指针指向NULL(用数值0模拟空指针NULL。)

2.初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IgfP7izr-1651417439647)(D:\AboutMyCollege\XMind\数据结构\静态链表的初始化1.png)]

bool InitList(StaticLinkList space) {
	for (int i = 0; i < MAXSIZE - 1; i++) {
		space[i].cur = i + 1;
	}
	space[MAXSIZE - 1].cur = 0;
	return true;
}

3.静态链表的插入操作:

在动态链表里,结点的申请和释放分别借用malloc和free两个函数,而在静态链表里,操作的是数组,不能使用这两个函数,但可以通过设计函数来达到与其相似的效果

为了辨别哪些分量未被使用,将所有未被使用过的以及已被删除的分量链成一个备用链表,每次进行插入时,获取备用链表的第一个结点作为待插入的新结点,该过程模拟了malloc

int ListLength(StaticLinkList L) {
	int i = L[MAXSIZE - 1].cur; //i即第一个存有数据的元素的下标
	int len = 0;
	while (i) {
		i = L[i].cur; //遍历存有数据元素的下标
		len++;
	}
	return len;
}
int Malloc(StaticLinkList space) {
	int i = space[0].cur; //返回i,即取得第一个备用空闲的下标
	if (i)  //如果i=0,则存储空间已满,没有空闲分量
		space[0].cur = space[i].cur; //i已经拿去使用了,原来的第一个备用空闲的下标需要重置为新的空闲分量
	return i;
}
//在第i个元素之前插入数据元素e,即让数据元素e成为链表中第i个数据元素(则要找到第i-1个元素)
bool ListInsert(StaticLinkList L, int i, int e) {
	if (i<1 || i>ListLength(L) + 1)
		return false;
	int j = Malloc(L);//获得空闲分量的下标
	int k = MAXSIZE - 1; //k为最后一个元素的下标,L[k].cur即第一个存有数据的元素的下标
	if (j) { //j!=0时满足条件,即j分配到了空闲分量的下标
		L[j].data = e;
		for (int m = 1; m < i - 1; m++)
			k = L[k].cur;  //循环退出后,k值为第i-1个数据元素的下标
		//以下两行代码类似与插入操作里的修改指针指向
		L[j].cur = L[k].cur;
		L[k].cur = j;
		return true;
	}
	return false; //即j==0,此时链表已满,无法再插入新元素
}

4.静态链表的删除操作

模拟free函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hvYFIPPl-1651417439648)(D:\AboutMyCollege\XMind\数据结构\静态链表的删除1.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UD6RzSUG-1651417439648)(D:\AboutMyCollege\XMind\数据结构\静态链表的删除2.jpg)]

//释放下标为k的结点,即将该结点视为空闲结点回收到备用链表(如上图,比如释放下标为1的元素甲)
void Free(StaticLinkList space, int k) {
	//让回收的这个位置(即被删除的位置)成为第一个优先空位
	space[k].cur = space[0].cur; //与之前的优先空位交换,把之前的优先空位“降级”
	space[0].cur = k; //重置优先空位为被释放的结点k
}

删除操作:

//删除第i个数据元素(如上图,比如删除下标为1的甲)
bool ListDelete(StaticLinkList L, int i) {
	if (i<1 || i>ListLength(L))
		return false;  //参数i值不合理
	int k = MAXSIZE - 1, j;
	for (j = 1; j < i; j++)
		k = L[k].cur;
	j = L[k].cur; //j即第i个数据元素的下标,下面这行代码L[j].cur即第i+1个数据的下标
	L[k].cur = L[j].cur; //类似单链表删除结点的“覆盖”,即将第一个数据元素的下标变成第二个,第一个则被覆盖
    Free(L,j); //释放第i个数据元素
	return true;
}

5.优缺点分析

优点:插入和删除元素时,吸收了链表的特点,只需要修改游标,改进了顺序存储结构中插入和删除需要移动大量元素的缺点;

缺点:没有解决固定表长长度分配不灵活的问题;失去了顺序存储结构中根据下标快速访问的特性。

6.补充-给定一组数据建立静态链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DfZF4OqF-1651417439649)(D:\AboutMyCollege\XMind\数据结构\静态链表插入数据.png)]

建立静态链表图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WRed6aCK-1651417439649)(D:\AboutMyCollege\XMind\数据结构\创建静态链表.jpg)]

void BuildSList(StaticLinkList L)
{
	InitList(L);
	printf("请输入你想添加的数据成员的数量:");
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		printf("请输入第%d个数据成员:", i);
		scanf("%d", &L[i].data);
	}
	L[0].cur = n + 1;
	L[n].cur = 0;
    L[MAXSIZE - 1].cur = 1;
}

7.完整代码以及运行测试

#include<stdio.h>
#define MAXSIZE 100
typedef struct {
	int data;
	int cur;  //游标(cursor)
}Component, StaticLinkList[MAXSIZE];
bool InitList(StaticLinkList space) {
	for (int i = 0; i < MAXSIZE - 1; i++) {
		space[i].cur = i + 1;
	}
	space[MAXSIZE - 1].cur = 0;
	return true;
}
int ListLength(StaticLinkList L) {
	int i = L[MAXSIZE - 1].cur; //i即第一个存有数据的元素的下标
	int len = 0;
	while (i) {
		i = L[i].cur; //遍历存有数据元素的下标
		len++;
	}
	return len;
}
int Malloc(StaticLinkList space) {
	int i = space[0].cur; //返回i,即取得第一个备用空闲的下标
	if (i)  //如果i=0,则存储空间已满,没有空闲分量
		space[0].cur = space[i].cur; //i已经拿去使用了,原来的第一个备用空闲的下标需要重置为新的空闲分量
	return i;
}
//在第i个元素之前插入数据元素e,即让数据元素e成为链表中第i个数据元素(则要找到第i-1个元素)
bool ListInsert(StaticLinkList L, int i, int e) {
	if (i<1 || i>ListLength(L) + 1)
		return false;
	int j = Malloc(L);//获得空闲分量的下标
	int k = MAXSIZE - 1; //k为最后一个元素的下标,L[k].cur即第一个存有数据的元素的下标
	if (j) { //j!=0时满足条件,即j分配到了空闲分量的下标
		L[j].data = e;
		for (int m = 1; m < i - 1; m++)
			k = L[k].cur;  //循环退出后,k值为第i-1个数据元素的下标
		//以下两行代码类似与插入操作里的修改指针指向
		L[j].cur = L[k].cur;
		L[k].cur = j;
		return true;
	}
	return false; //即j==0,此时链表已满,无法再插入新元素
}
//释放下标为k的结点,即将该结点视为空闲结点回收到备用链表
void Free(StaticLinkList space, int k) {
	//让回收的这个位置(即被删除的位置)成为第一个优先空位
	space[k].cur = space[0].cur; //与之前的优先空位交换,把之前的优先空位“降级”
	space[0].cur = k; //重置优先空位为被释放的结点k
}
//删除第i个数据元素(如上图,比如删除下标为1的甲)
bool ListDelete(StaticLinkList L, int i) {
	if (i<1 || i>ListLength(L))
		return false;  //参数i值不合理
	int k = MAXSIZE - 1, j;
	for (j = 1; j < i; j++)
		k = L[k].cur;
	j = L[k].cur; //j即第i个数据元素的下标,下面这行代码L[j].cur即第i+1个数据的下标
	L[k].cur = L[j].cur; //类似单链表删除结点的“覆盖”,即将第一个数据元素的下标变成第二个,第一个则被覆盖
	Free(L, j); //释放第i个数据元素
	return true;
}
void BuildSList(StaticLinkList L)
{
	InitList(L);
	printf("请输入你想添加的数据成员的数量:");
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		printf("请输入第%d个数据成员:", i);
		scanf("%d", &L[i].data);
	}
	L[0].cur = n + 1;
	L[n].cur = 0;
	L[MAXSIZE - 1].cur = 1;
}
int main() {
	StaticLinkList test;
	BuildSList(test);
	printf("此时静态链表的长度为:%d\n", ListLength(test));
	printf("此时删除了第二个数据元素\n");
	ListDelete(test, 2);
	printf("此时静态链表的长度为:%d\n", ListLength(test));
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PiLhpYud-1651417439650)(D:\AboutMyCollege\XMind\数据结构\运行示例\静态链表代码测试.jpg)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

鸭梨大妈

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

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

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

打赏作者

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

抵扣说明:

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

余额充值