第二章--线性表之单链表


在这里插入图片描述

一、单链表的定义

在这里插入图片描述

1.什么是单链表

在这里插入图片描述
单链表要找到某一个位序的结点只能从第一个结点开始依次往后寻找,直到找到为止。所以不支持随机存取

随机存取:就是直接存取,可以通过下标直接访问的那种数据结构,与存储位置无关,例如数组。 非随机存取:就是顺序存取了,不能通过下标访问了,只能按照存储顺序存取,与存储位置有关,例如链表。

2.用代码定义一个单链表

在这里插入图片描述
在这里插入图片描述课本上的代码:

在这里插入图片描述

代码实现:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表结点类型
typedef struct LNode {
	ElemType data; //每个结点存放一个数据元素
	struct LNode* next; //指针指向下一个结点
	 //将struct LNode为LNode重命名为
	 //并且用LinkList表示这是一个指向struct LNode的指针
}LNode,*LinkList;

注意:

王道课程采用的写法是

在这里插入图片描述
举例:
在这里插入图片描述

3.不带头结点的单链表

在这里插入图片描述

代码实现:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表结点类型
typedef struct LNode {
	ElemType data; //每个结点存放一个数据元素
	struct LNode *next; //指针指向下一个结点
	 //将struct LNode为LNode重命名为
	 //并且用LinkList表示这是一个指向struct LNode的指针
}LNode,*LinkList;
//初始化一个空链表
bool InitList(LinkList &L) {
	L = NULL;//空表,暂时还没有任何结点,防止脏数据
	return true;
}
LNode* p = (LNode*)malloc(sizeof(LNode));
void test() {
	LinkList L;//声明一个执行那个单链表的指针
	InitList(L);
}
void main() {
	
}

4.带头结点的单链表

在这里插入图片描述

在这里插入图片描述
不带头结点的头指针指向的下一个结点就是实际用于存放数据的节点

带头结点头指针所指向的结点称为头结点,此头结点不存放实际的元素,头结点之后的下一个结点才会存放数据

在这里插入图片描述

二、插入和删除

在这里插入图片描述
在这里插入图片描述

1.按位序插入

(1)、带头结点

在这里插入图片描述
在这里插入图片描述
如果要在i=1处插入,这时就能体会到带头结点的好处

在这里插入图片描述
具体代码实现:
在这里插入图片描述
在这里插入图片描述
这两句不能颠倒

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode,*LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r=L;
	scanf("%d",&x);
	while (x!=9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;
	}
	LNode* p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第0个结点
	while (p != NULL && j < i - 1)//循环找到第i-1个结点
	{
		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; //将结点s连到p之后
	return true; //插入成功
}
int Length(LinkList& L) {
	int len = 0;
	LNode* p = L;
	while (p->next!=NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main(){
	LinkList L;
	L = List_TailInsert(L);
	int len = Length(L);
	LNode* p = L;
	printf("插入前的单链表:\n");
	for (int i = 0; i < len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n", i, p->data);
	}
	//-------------------------------------------------------------------
	
	printf("插入后的单链表:\n");
	ListInsert(L,2,8);
	int len1 = Length(L);
	LNode* a = L;
	for (int i = 0; i < len1; i++)
	{
		a = a->next;
		printf("L[%d]=%d\n", i, a->data);
	}
}

运行结果:

在这里插入图片描述

(2).不带头结点

在这里插入图片描述
在最开始插入时需要特殊处理

在这里插入图片描述代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode,*LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	scanf("%d", &x);
	L= (LinkList)malloc(sizeof(LNode));
	L->data = x;
	LNode* s, * r=L;
	while (x!=9999)
	{
		scanf("%d", &x);
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		
	}
	r->next = NULL;
	return L;
}
//在第i个节点插入元素e(不带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;
	}
	if (i == 1) {//插入第1个结点的操作与其他结点的操作不同
		LNode* s = (LinkList)malloc(sizeof(LNode));
		s->data = e;
		s->next = L;
		L = s;//头指针指向新结点
		return true;
	}
	LNode* p; //指针p指向当前扫描到的结点
	int j = 1; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第1个结点
	while (p != NULL && j < i - 1)//循环找到第i-1个结点
	{
		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; //将结点s连到p之后
	return true; //插入成功
}
int Length(LinkList& L) {
	int len = 0;
	LNode* p = L;
	while (p->next!=NULL)
	{
		len++;
		p = p->next;
	}
	return len;
}
int main(){
	LinkList L;
	L = List_TailInsert(L);
	int len = Length(L);
	LNode* p = L;
	printf("插入前的单链表:\n");
	for (int i = 0; i < len; i++)
	{
		printf("L[%d]=%d\n", i, p->data);
		p = p->next;
	}
	//-------------------------------------------------------------------
	
	printf("插入后的单链表:\n");
	ListInsert(L,1,8);
	int len1 = Length(L);
	LNode* a = L;
	for (int i = 0; i < len1; i++)
	{
		printf("L[%d]=%d\n", i, a->data);
		a = a->next;
	}
}

运行结果:
在这里插入图片描述

(3).指定结点的后插操作

在这里插入图片描述
在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
//后插操作:在p结点之后插入元素e
bool InsertNextNode(LNode *p,ElemType e) {
	if (p == NULL) {
		return false;
	}
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL) { //内存分配失败
		return false;
	}
	s->data = e;//用结点s保存数据元素e
	s->next = p->next;
	p->next = s;//将结点s连接到p之后
	return true;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;
	}
	LNode* p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第0个结点
	while (p != NULL && j < i - 1)//循环找到第i-1个结点
	{
		p = p->next;
		j++;
	}
	return InsertNextNode(p, e);
}
int Length(LinkList& L) {
	int len = 0;
	LNode* p = L;
	while (p->next != NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main() {
	LinkList L;
	L = List_TailInsert(L);
	int len = Length(L);
	LNode* p = L;
	printf("插入前的单链表:\n");
	for (int i = 0; i < len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n", i, p->data);
	}
	//-------------------------------------------------------------------

	printf("插入后的单链表:\n");
	ListInsert(L, 2, 8);
	int len1 = Length(L);
	LNode* a = L;
	for (int i = 0; i < len1; i++)
	{
		a = a->next;
		printf("L[%d]=%d\n", i, a->data);
	}
}

运行结果:

在这里插入图片描述

(4).指定结点的前插操作

在这里插入图片描述
但是如果不传头指针这个思路就没办法实现

另一种实现方式:

在这里插入图片描述
申请一个新的结点:
在这里插入图片描述

在这里插入图片描述
把这个结点作为p的后继结点:
在这里插入图片描述
在这里插入图片描述
复制原来p中的数字
在这里插入图片描述
在这里插入图片描述
再把e放到原来的p节点里边

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
//前插操作:在p结点之前插入元素e
bool InsertPriorNode(LNode *p,ElemType e) {
	if (p == NULL) {
		return false;
	}
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL) { //内存分配失败
		return false;
	}
	s->next = p->next;
	p->next = s;//新结点s连接到p之后
	s->data = p->data;//将p中元素复制到s中
	p->data = e;//p中元素覆盖为e
	return true;
}
//在第i个节点插入元素e(带头结点)
bool ListInsert(LinkList& L, int i, ElemType e) {
	if (i < 1) {
		return false;
	}
	LNode* p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第0个结点
	while (p != NULL && j < i)//循环找到第i个结点
	{
		p = p->next;
		j++;
	}
	return InsertPriorNode(p, e);
}
int Length(LinkList& L) {
	int len = 0;
	LNode* p = L;
	while (p->next != NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main() {
	LinkList L;
	L = List_TailInsert(L);
	int len = Length(L);
	LNode* p = L;
	printf("插入前的单链表:\n");
	for (int i = 0; i < len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n", i, p->data);
	}
	//-------------------------------------------------------------------

	printf("插入后的单链表:\n");
	ListInsert(L, 2, 8);
	int len1 = Length(L);
	LNode* a = L;
	for (int i = 0; i < len1; i++)
	{
		a = a->next;
		printf("L[%d]=%d\n", i, a->data);
	}
}

运行结果:

在这里插入图片描述

2.按位删除

(1).带头结点

在这里插入图片描述
在这里插入图片描述

(2).指定结点的删除

在这里插入图片描述
如果要删除最后一个节点这种思路就会报错

在这里插入图片描述代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
//删除指定节点p
bool DeleteNode(LNode* p) {
	if (p==NULL)
	{
		return false;
	}
	if (p->next==NULL)
	{
		return false;
	}
	LNode* q = p->next; //令q指向*p的后继结点
	p->data = p->next->data; //和后继结点交换数据域
	p->next = q->next;  //将*q结点从链中“断开”
	free(q); //释放后继结点的存储空间
	return true;
}
bool ListDelete(LinkList& L, int i) {
	if(i < 1) {
		return false;
	}
	LNode* p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第0个结点
	while (p != NULL && j < i)//循环找到第i个结点
	{
		p = p->next;
		j++;
	}
	return DeleteNode(p);
}
int Length(LinkList& L) {
	int len = 0;
	LNode* p = L;
	while (p->next != NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main() {
	LinkList L;
	L = List_TailInsert(L);
	int len = Length(L);
	LNode* p = L;
	printf("插入前的单链表:\n");
	for (int i = 0; i < len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n", i, p->data);
	}
	//-------------------------------------------------------------------

	printf("插入后的单链表:\n");
	ListDelete(L, 2);
	int len1 = Length(L);
	LNode* a = L;
	for (int i = 0; i < len1; i++)
	{
		a = a->next;
		printf("L[%d]=%d\n", i, a->data);
	}
}

运行结果:
在这里插入图片描述

三、单链表的查找

在这里插入图片描述

1.按位查找

在写插入函数时可以将查找的函数封装然后拿来直接使用

代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
//按位查找
LNode* GetElem(LinkList L, int i) {
	LNode* p; //指针p指向当前扫描到的结点
	int j = 0; //当前p指向的是第几个结点
	p = L; //L指向头结点,头结点是第0个结点
	while (p != NULL && j < i)//循环找到第i个结点
	{
		p = p->next;
		j++;
	}
	return p;
}
int main() {
	LinkList L;
	L = List_TailInsert(L);
	LNode * p = GetElem(L, 2);
	printf("此结点的值为:%d", p->data);
}

运行结果:

在这里插入图片描述

2.按值查找

在这里插入图片描述

在这里插入图片描述
如果是struct类型,那就不能用!=或者=来判断是否相等了

代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义一个单链表
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;
//用尾插法创建一个单链表
LinkList List_TailInsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	LNode* s, * r = L;
	scanf("%d", &x);
	while (x != 9999)
	{
		s = (LinkList)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d", &x);
	}
	r->next = NULL;
	return L;
}
LNode*  LocateElem(LinkList L, ElemType e) {
	LNode* p = L->next;
	//从第一个结点开始查找数据域为e的结点
	while (p != NULL && p->data!=e)//循环找到第i个结点
	{
		p = p->next;
	}
	return p;
}

int main() {
	LinkList L;
	L = List_TailInsert(L);
	LNode  *p = LocateElem(L, 2);
	printf("此结点的值为:%d",p->data);
}

运行结果:
在这里插入图片描述

3.求表的长度

在这里插入图片描述
在这里插入图片描述

四、单链表的建立

1.尾插法

在这里插入图片描述
我们没有必要每次都从头开始往后找
在这里插入图片描述

可以设置一个指针,让指针指向表尾的最后一个结点,当我们在尾部插入一个新的数据元素的时候,只需要对对r这个结点进行一个后插操作就可以

后插操作:
在这里插入图片描述
课本给出的尾插法代码:

在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表
typedef struct LNode {
	ElemType data; //每个结点存放一个数据元素
	struct LNode* next; //指针指向下一个结点
	 //将struct LNode为LNode重命名为
	 //并且用LinkList表示这是一个指向struct LNode的指针
}LNode, * LinkList;
LinkList List_TaillInsert(LinkList& L) {//正向建立单链表
	int x;
	L = (LinkList)malloc(sizeof(LNode));//建立头结点
	LNode* s, * r = L;//r为表尾指针
	scanf("%d", &x);//输入结点的值
	while (x!=9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;//r指向新的表尾结点
		scanf("%d", &x);
	}
	r->next = NULL;//尾结点指针置空
	return L;
}
//求表的长度
int length(LinkList L) {
	int len=0;
	LNode* p = L;
	while (p->next!=NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main() {
	LinkList L;
	L=List_TaillInsert(L);
	int len = length(L);
	LNode* p = L;
	for (int i = 0; i <len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n",i,p->data);
		
	}
}

输出结果:

在这里插入图片描述

2.头插法

在这里插入图片描述在这里插入图片描述

在这里插入图片描述
课本给出的代码:
在这里插入图片描述

如果去掉
在这里插入图片描述
在这里插入图片描述
那么头结点可能指向内存中的某一片神秘的区域

头插法可实现链表的逆置:

在这里插入图片描述
代码演示:

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
//定义单链表
typedef struct LNode {
	ElemType data; //每个结点存放一个数据元素
	struct LNode* next; //指针指向下一个结点
	 //将struct LNode为LNode重命名为
	 //并且用LinkList表示这是一个指向struct LNode的指针
}LNode, * LinkList;
LinkList List_HeadInsert(LinkList& L) {//逆向建立单链表
	int x;
	LNode* s;
	L = (LinkList)malloc(sizeof(LNode));//建立头结点
	L->next = NULL;//初始为空链表
	scanf("%d", &x);//输入结点的值
	while (x!=9999) {
		s = (LNode*)malloc(sizeof(LNode));//创建新结点
		s->data = x;
		s->next = L->next;
		L->next = s;//将新结点插入表中,L为头指针
		scanf("%d", &x);
	}
	return L;
}
//求表的长度
int length(LinkList L) {
	int len=0;
	LNode* p = L;
	while (p->next!=NULL)
	{
		p = p->next;
		len++;
	}
	return len;
}
int main() {
	LinkList L;
	L = List_HeadInsert(L);
	int len = length(L);
	LNode* p = L;
	for (int i = 0; i <len; i++)
	{
		p = p->next;
		printf("L[%d]=%d\n",i,p->data);	
	}
}

运行结果:
在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

诸葛东_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值