408数据结构-单链表 自学知识点整理

文章详细介绍了单链表的结构,包括带头结点和不带头结点的初始化、按位插入、删除、查找操作,以及尾插法和头插法建立单链表的方法。强调了考研初试可能需要掌握的单链表基础知识。
摘要由CSDN通过智能技术生成

线性表的链式储存又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。

为了建立数据元素之间的线性关系,对每个链表结点,除存放元素自身的信息之外,还需要存放一个指向其后继的指针。
定义一个基本的单链表代码如下:

typedef struct LNode {//typedef关键字:重命名关键字,使代码更简洁
	//此处等价于typedef struct LNode LNode;
	int data;
	struct LNode* next;//指针指向下一个节点
}LNode, *LinkList;//此处等价于typedef struct LNode *LinkList;

对单链表,带头结点的操作代码书写较为方便,但是408考研初试中可能考察不带头结点的单链表相关知识,故先放两个不带头结点的单链表操作函数。
初始化:

bool NHInitList(LinkList& L) {//初始化单链表(不带头结点)
	L = NULL;
	return true;
}

void Nohead() {//不带头结点的单链表
	//不带头结点,L.data中存放数据;带头结点,L.data中不存数据,即可视为第0个节点
	LinkList L;//声明一个指向单链表的指针
	NHInitList(L);//初始化一个空表
	//……然后进行后续操作
	return;
}

按位插入(带头结点的写法在后面):

bool NHListInsert(LinkList& L, int i, int e) {//不带头结点的按位插入
	if (i < 1)return false;
	if (i == 1) {//需要单独为第1位序的结点开辟一个逻辑,比较麻烦
		LNode* s = (LNode*)malloc(sizeof(LNode));//申请新节点
		s->data = e;//赋值
		s->next = L;//新节点后接原表L
		L = s;//头结点变为s
		return true;
	}
	else return ListInsert(L, i-1, e);//其他位置的插入同带头结点,直接调用相关函数
}

总之,不带头结点的单链表对第1位序的结点操作时通常要单独编写一套逻辑,非常不便。之后的代码全是默认带头结点的单链表。

初始化

bool InitList(LinkList& L) {//带头结点的初始化(绝大多数情况下)
	L = (LNode*)malloc(sizeof(LNode));//分配一个头结点
	if (L == NULL)return false;//内存不足,分配失败
	L->next = NULL;//头节点后暂无节点
	return true;//分配成功,返回值为true
}

按位插入

bool ListInsert(LinkList& L, int i, int e) {//按位插入,在第i个位置插入元素e(带头结点)
	if (i < 1)return false;//排除不合法的i
	LNode* p;//临时指针
	int j = 0;//表示p指向第j个结点
	p = L;//p初始状态和头结点指向同一位置,可视作第0个结点(不存数据)
	while (p != NULL && j < i - 1) {//循环找到第i-1个结点
		p = p->next;
		++j;
	}
	if (p == NULL)return false;//i值不合法
	LNode* s = (LNode*)malloc(sizeof(LNode));//申请一个新结点
	s->data = e;//插值
	s->next = p->next;//将p结点的下一个指向传给s
	p->next = s;//然后将p指向s,完成s的插入
	return true;
}

当然,这一段中的while循环也可用for代替,具体如下,不过初学者建议还是用while循环,便于理解。

LNode* p = L;
for (int j = 0; p != NULL && j < i - 1; p = p->next, ++j);
//(当然使用时要先声明后插函数)
/return InsertNextNode(p,e);

后插操作

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;
}

因为单链表的性质,对给定结点,其前驱总是未知的,但是可以通过一种“偷天换日”的方法实现前插操作,具体代码如下:

bool InsertPriorNode(LNode* p, int e) {//对给定结点进行前插操作(在前面插入一个数据)
	if (p == NULL)return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL)return false;
	s->next = p->next;//将s的后继指向p的后继
	p->next = s;//p指向s,完成将s插入链表
	s->data = p->data;//将p的数据赋给s
	p->data = e;//再把需要插入的值e赋给p,从而完成变相的“前插”
	return true;
}

按位删除

bool ListDelete(LinkList& L, int i, int& e) {//按位删除:删除第i位序的元素,并用e返回其值
	if (i < 1)return false;
	LNode* p = L;
	for (int j = 0; p != NULL && j < i - 1; p = p->next, ++j);//循环查找第i-1个结点,逻辑同按位插入
	if (p == NULL || p->next == NULL)return false;//若第i-1个结点后无结点也非法(即不存在i结点)
	LNode* q = p->next;//令q指向被删除结点
	e = q->data;//用e返回被删除的值
	p->next = q->next;//用p的后继继承q的后继,从而将q从链表中断开
	free(q);//释放内存空间,完成删除操作
	return true;
}

对指定结点的删除操作,王道考研书上的代码无法实现对表尾结点的正确删除,我对其进行了补正,不一定正确,具体代码如下:

bool DeleteNode(LNode* p) {//删除指定结点p
	if (p == NULL)return false;
	if (p->next != NULL) {//如果p不是表尾结点
		LNode* q = p->next;//令q指向p的后继结点
		p->data = q->data;//将p结点的数据用后继结点的数据覆盖
		p->next = q->next;//再将p的后继指向p的后继的后继,从而实现q结点的断开
		free(q);//此时p的位置实际上已经是q,p已经被删,所以释放q即可(思想类似前插)
	}else free(p);
	//如果p是表尾结点则直接释放p
	//但是单链表的相应信息也要变化,比如len--,这里就需要使用全局变量或者将参数传入函数
	//具体实现可能较为复杂,视情况而定
	return true;
}

按位查找操作,其实在之前的“按位插入”操作中就已经实现,但是王道考研网课的讲解顺序是先讲了“按位插入”,所以我也这么写了。这个函数可以写在“按位插入”函数之前,然后直接调用之,代码如下:

LNode* GetElem(LinkList L, int i) {//按位查找,返回第i个元素,之前按位插入已经有过实现
	//可封装入按位插入,按位删除函数
	if (i < 0)return NULL;
	LNode* p = L;
	int j = 0;
	while (p != NULL && j < i) {
		p = p->next;
		++j;
	}
	return p;
}

按值查找

LNode* LocateElem(LinkList L, int e) {//按值查找,找到数据域为e的结点
	LNode* p = L->next;
	for (; p != NULL && p->data != e; p = p->next);
	return p;//若没找到会返回NULL
}

求表长

int Length(LinkList L) {//求表长
	int len = 0;
	for (LNode* p = L; p->next != NULL; p = p->next)//遍历单链表
		len++;
	return len;
}

基本操作之后是单链表的建立,一共有两种方式,分别是尾插法和头插法,其中头插法建立的单链表数据元素顺序与输入顺序相反。
尾插法

LinkList List_TailInsert(LinkList& L) {//尾插法建立单链表
	int x;
	L = (LinkList)malloc(sizeof(LNode));//建立头结点
	LNode* s, * r = L;//s为临时指针,r为表尾指针
	while (scanf("%d", &x) != EOF) {//循环输入数据,直到无输入
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;//数据入表
		r->next = s;//链接元素
		r = s;//r指向新的表尾
	}
	r->next = NULL;//尾结点指针置空(同时预防空表产生野指针)
	return L;
}

头插法

LinkList List_HeadInsert(LinkList& L) {//头插法建立单链表
	int x;
	LNode* s;//临时指针
	L = (LinkList)malloc(sizeof(LNode));//分配头结点
	L->next = NULL;//初始为空
	while (scanf("%d", &x) != EOF) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;//传入数据
		s->next = L->next;//将头结点的后继作为新节点的后继
		L->next = s;//新节点作为头结点的后继
	}
	return L;
}

关于单链表,考研初试需要掌握的知识基本上就是这么多,当然,学有余力的话也要思考一下不带头结点的单链表,基本操作又要如何实现。

完整代码可以看我的Github:传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值