数据结构链表学习

这是笔者第一次发布自己关于计算机学习的博客,以后会更加精进,共勉!

计算机的学习方面人生哲学方面:学习计算机语言前期就是程序很容易报错,一些莫名其妙的错误,很难得到及时的正反馈。很多事情也是这样的,导致大部分难以坚持,本人也是一样的。使用能坚持下来的人,要么有着很强的意志力足以克服学习历程上的带来的不好情绪,要么对这件事非常有兴趣,他可以靠着兴趣和乐趣克服大部分的由于失败带来负面体验.很多事情不是解决问题的方法问题,只是单纯做事让态度问题,你的理智是否会被情绪所夺舍.所以坚持就是把心思放在问题本身而不是他带来的负面情绪上,情绪难以被解决,但是现实问题很具体是更加容易解决的,遇到问题解决问题.不断成长不断进步.

对程序理解:对现实世界的数据化然后使用逻辑思考和算法使数据状态变化符合现实的状态变化,方法比现实世界灵活但是更加抽象。比如尾插法并不太符合现实生活的思考习惯,但是这样的方法在逻辑上是合理的

加深了对数据类型的认识,和指针认识(对指针的认识与计算机结构相结合,很多东西的逻辑结构实现基础是物理结构。

下面是自己对数据中链表自己依照网课编写的c语言代码(关于链表部分的)并且加入自己的理解,如有错误希望读者指正

内容:

1.单链表的初始化

2.数据类型的理解:就是相同的东西你用什么方式去解读

3.单链表头结点和普通结点的名称区别  typedef的认识使用 typedef<数据类型><别名>  malloc函数的运用和理解

4.单链表有无头结点插入方法的异同

5.单链表的头插法和尾插法(尾插法有一种移花接木的感觉,笔者不知道是否用词准确)

6.双链表的删除插入的关键点其实就是把需要现有结构推理出来的地址先保存,如果改变现有逻辑结构会导致推理出来的地址是错误的。

7.循环单链表循环双链表

8.静态链表

#include<stdio.h>
#include<string.h>
#include<stdbool.h>
#include<stdlib.h>
#include<math.h>
#include <limits.h>

struct LNode {          //定义单链表节点类型
	int data;           //每个节点存放一个数据元素
	struct LNode* next; //指针指向下一个结点
	//为什么不是单纯的一个指针就行了
};
//增加一个新结点:在内存中申请一个结点所需空间,并用指针p指向这个结点
struct LNode* p = (struct LNode*)malloc(sizeof(struct LNode));

//使用struct LNode 太复杂了简化,使用typdef关键字--数据类型重命名
//typedef<数据类型><别名> 
//typedef int zhengshu ;
//typedef int *zhengshuzhizhen;

//数据类型加*就是当前这个变量所存地址里面的内容应该如何解读

typedef struct LNode {
	int data;
	struct LNode* next;
};
typedef struct LNode;
typedef struct LNode* LinkList;

//要表示一个单链表是=时只需要声明一个头指针L,指向单链表的第一个结点;   *加一个变量名 这个变量是一个指针变量
// LNode *L 声明一个指向单链表第一个结点指针 或者  LinkList L 声明一个指向单链表第一个结点指针
//强调这是一个单链表 --使用LinkList
//强调这是一个结点使用 LNode *
//因为一个链表类型指针所指如果是头节点那么他就算指出了整个链表 如果指的是链表中一个非头结点结点那他只能代表链表中一个平台结点
//就是区分普通结点和头结点的作用 因为头结点就可以代表整个链表

//单链表的查找 按位查找,返回第i个元素(带头结点)头结点作为第“0”个结点
LNode* GetElem(LinkList L, int i) {
	int j = 1;//结点位置标志变量
	LNode* p = L->next;//指向头结点后一个结点 第一个结点
	if (i == 0)return L;//就是头结点本身
	if (i < 1)return NULL;//非法输入
	while (p != NULL && j < i) {//函数功能找到第i个结点,并且判断输入的合法性
		p = p->next;//不断向后找
		j++;
	}
	return p;//将第i个结点返回
	//可以为插入操作和删除操作完成查找的步骤,封装基本操作的好处(避免重复代码,简洁,易维护)
}

//按值查找,找到数据域==e的结点
LNode* LocateElem(LinkList L, int e) {
	LNode* p = L->next;//因为是带头结点,从第一个结点开始寻找
	while (p != NULL && p->data != e) p = p->next;//不等于e远比等于e的判断条件好这样可以找出全部等于e的结点,逻辑关系否定
	return p;//返回所有等于e的结点

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

	return len;//返回元素个数,表长度不计算头结点
}
//头插法建立单链表的算法
LinkList List_HeadInsert(LinkList& L) {//逆向建立单链表
	LNode* s; int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头节点
	L->next = NULL;//初始化空链表
	scanf("%d", &x);//输入结点的值
	while (x != 9999) {//输入9999表示结束
		s = (LNode*)malloc(sizeof(LNode));//创建普通结点
		s->data = x;
		s->next = L->next;
		L->next = s;  //将新结点插入表中,L所指结点为为头节点,此时S就是新的头节点
		scanf("%d", &x);
	}//是否带头结点
	return L; //返回新结点
}








//带头结点和不带头结点的区别就是第一个指针所指的结点data域是否有数据和其他结点是否有区别
//带头结点写代码更加容易方便

//单链表的插入和删除(有头结点的情况)
//Listinsert(&l,i,e),插入操作。在表中第i个位置上插入指定元素(找到第i-1个结点将元素插入其后)
//头结点可以看作"第0个结点"
bool ListInsert(LinkList& L, int i, int e) {
	if (i < 1)return false;
	LNode* p;//指针p指向当前扫描结点
	int j = 0;//当前指向的是第几个结点
	p = L;//L指向头结点,头结点是第0个元素(不存储数据),将p指针指向头结点 p中存储的是L头结点的地址
	while (p != NULL && j < i - 1)//循环找到第i-1个结点
	{
		p = p->next;//将p指针所指结点的下一个结点地址赋给p,让p指针指向下一个元素
		j++;//相当于j作为一个标记位表示着当前指针所指结点是第几个结点

	}
	if (p == NULL)return false;//i值不合法
	LNode* s = (LNode*)malloc(sizeof(LNode));//开辟一块新结点大小空间来存放加进来的元素,并将该空间地址用结点类型定义
	//并将开辟空间的地址赋给变量s,
	s->data = e;//s变量所指空间的数据域赋值为e
	s->next = p->next;//新结点的指针指向i-1结点的下一个结点
	p->next = s;//i-1结点把指针指向作为第i个结点的s指针所致结点;
	return true;//插入成功
}

//尾插法建立单链表
LinkList List_Taillnsert(LinkList& L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode*));//初始化一个单链表
	LNode* s, * r = L;//将指针r指向L的头结点
	scanf("d%", &x);//输入x,&保证了函数内的X可以传导外面作为判断条件跳出循环
	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));//开辟一片空间作为新结点
		s->data = x;
		r->next = s;//将新结点插入尾结点后面
		r = s;//尾指针指向新结点
		scanf("%d", &x);//可以通过输入9999跳出循环
	}
	r->next = NULL;//让尾结点指向空
	return L;//将创建好的链表返回
}
//头插法建立单链表  重要运用链表逆置
LinkList List_HeadInsert(LinkList&L) {
	LNode* s;
	int x;
	L = (LinkList)malloc(sizeof(LNode*));
	L->next = NULL;//小心野指针,洋葱好习惯只要初始化单链表,都将头指针指向NULL
	scanf("%d", &x);
	while (x != 9999) {
		LNode* s = (LNode*)malloc(sizeof(LNode));
		if (s == NULL)return false;//判断是否创建结点成功
		s->data = x;
		s->next = L->next;
		L->next = s;//将新结点插入到头指针之后
		scanf("%d", &x);
	}
	return L;
}

//后插操作:在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;//将新结点连接到p结点之后
	return true;
}

//按位插入(不带头结点啊)不存在‘第0个’结点,因此i=1时要特殊处理

bool ListInsert1(LinkList& L, int i, int e) {
	if (i == 1) {//需要特殊处理所以单拎出来,插入第一个结点操作与其他结点不同
		LNode* s = (LNode*)malloc(sizeof(LNode*));//开辟一片空间结点
		s->data = e;//把数据放进去
		s->next = L;//有点向头指针前面插入第一个元素,保证了可以插入且最后一个元素的指针不指向空,指向了最开始L的地址
		L = s;//头指针终于有地方可以指了,那就是第一个结点
		return true;
	}
	LNode* p;//定义一个指针变量作用时指向当前所扫描的结点
	int j = 1;//j作为p指针指向第几个结点的计数器
	p = L;//p当前指向了第一个结点
	while (p != NULL && j < i - 1) {//循环找到第i-1位元素
		p = p->next;//指针不断跳转到下一个结点
		j++;
	}//找的了第i-1的结点
	if (p == NULL)return false;//i值不合法,因为链表没有那么长,没有位置可以插入
	LNode* s = (LNode*)malloc(sizeof(LNode));//开辟一块新结点大小空间来存放加进来的元素,并将该空间地址用结点类型定义
	//并将开辟空间的地址赋给变量s,
	s->data = e;//s变量所指空间的数据域赋值为e
	s->next = p->next;//新结点的指针指向i-1结点的下一个结点
	p->next = s;//i-1结点把指针指向作为第i个结点的s指针所致结点;
	return true;//插入成功

	//其余结点和之前有头结点操作一致
}
//指定结点的后插操作
//后插操作;在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 ListNextInsert(LinkList& L, int i, int e) {//在第i个位置插入元素e(带头结点)
	if (i < 1)return false;
	LNode* p;
	int j = 0;
	p = L;
	while (p != NULL && j < i - 1) {
		p = p->next;
		j++;
	}
	return InsertNextNode(p, e);

}
//前插操作;在p结点之前插入元素e
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;
	p->next = s;
	s->data = p->data;
	p->data = e;//移花接木,相当于把后插结点位置作原来的结点的数据,原来结点位置给了新元素,
	//在逻辑上就完成了前插却没有真正的在前面插入元素
	return true;//算法只要求最后输出状态一样不管你用什么方法,重新能力,可以不用现实生活中的方法,用更加抽象简洁的方法解决

}
//按位序删除(带头结点)ListDelete(&L,i,&e):删除操作。删除表中第i个结点位置的元素,并用e返回删除元素的值
//找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。
bool ListDelete(LinkList& L, int i, int& e) {//使用Linklist&符号是保证改变链表
	//e使用&是保证函数里面的结果能被外面所接受,&e是一个实参,单纯的e就是一个形参
	if (i < 1)return false;//说明输入非法
	LNode* p;  //定义一个结点指针
	int j = 0;   //指针所指结点位置的标记变量,当前应该要指向头结点
	p = L;//将链表的头结点地址赋值给了p,让p指针指向头结点
	while (p != NULL && j < i - 1) {//寻找i-1个位置结点
		p = p->next;
		j++;
	}
	if (p == NULL)return false;//i值不合法
	if (p->next == NULL)return false;//第i-1个结点的后一个结点(所要删除i结点不存在)
	LNode* q = p->next; //定义一个q指针并将指针指向第i个元素
	e = q->data; //将第i个元素的数据传递给变量e
	p->next = q->next;//将i-1的指针指向第i+1个结点,让i结点在链上断开
	free(q);//释放q指针
	return true;//删除操作成功
}
//单链表指定结点的删除 但是前一个结点未知
bool DeleteNode(LNode* p) {//传入一个指定结点,单链表特点就是前面结点位置无法知道但后面结点可以知道
	if (p == NULL)return false;
	LNode* q = p->next;//将结点指针q指向了指定待删除结点的下一个结点
	p->data = p->next->data;//把内容前移,覆盖掉要删除上的结点数据域
	//(逻辑上删除,删除了数据实际上分配的空间并没有删除,被删除的空间是指定结点的后一个结点的空间)
	p->next = q->next;//让指定结点后一个结点从单链表上断开
	free(q);//释放后继结点的空间
	return true;//删除成功返回ture

}
//双链表

//双链表初始化

//双链表的结构体
typedef struct DNode {
	int data;
	struct DNode* prior, * next;
}DNode,*DLinklist;

bool InitDLinkist(DLinklist &L) {
	L = (DNode*)malloc(sizeof(DNode));//分配一个头结点
	if (L == NULL)return false;//内存不足分配空间失败,生成结点失败
	L->prior = NULL; //头指针的前指针永远为空
	L->next = NULL;//防止形成野指针
	return true;
}
//双链表的插入
//在p结点之后插入s结点(后插操作可以忽略头结点的特殊情况)
bool InsertNextDNode(DNode *p,DNode*s) {//不是插入元素而是插入一个已经存在的结点
	if (p==NULL || s == NULL)return false;//非法参数
	s->next = p->next;//这个最重要
	if (p->next != NULL)p->next->prior = s;
	s->prior=p;
	p->next = s;
	//修改指针次序,要把靠现有指针逻辑推断得到的指针(没有直接告诉你)需要先把这些指针处理了
	return true;
}
//删除p结点的后继结点
bool DeletNextDNode(DNode*p) {
	if (p ==NULL)return false;
	DNode* q = p->next;//先通过一个新指针保存防止要推断导致地址出错
	if (q == NULL)q->next->prior = p;
	free(q);
	return true;
}
//初始化一个循环单链表
bool InitList(LinkList&L) {//&作用就是为了使链表更改有效
	L = (LNode*)malloc(sizeof(LNode));
	if (L == NULL)return false;//判误
	L->next = L;//将头NEXT指针指向头结点形成应该环
	return true;
}
//判断循环单链表是否为空
bool Empty(LinkList L) {
	if (L->next == L)return true;//如果链表为空,只有头结点 则头结点的next指针指向头结点本身
	else 
		return false;
}
//判断某结点是否为循环单链表的尾结点
bool isTail(LinkList L,LNode *p) {//如果p结点next指针指向头结点则说明p为尾结点
	if (p->next == L)return true;
	else
		return false;
}
//循环双链表
//初始化空的循环双链表
bool InitDLinkList(DLinklist& L) {
	L = (DNode*)malloc(sizeof(DNode));
	if (L == NULL)return false;//创建失败
	L->next = L;
	L->prior = L;//前驱指针和后继指针都指向了头结点本身
	return true;//创建成功
}
//判断循环双链表是否为空
bool EmptyD(DLinklist&L) {
	if (L->next == L)return true;
	else
		return false;
}
//判断结点p是否为循环双链表的尾结点
bool isTail1(DLinklist& L,DNode *p) {
	if (p->next == L)return true;
	else return false;
}
//静态链表
# define MaxSize 10 //定义静态链表最大长度
struct Node {//静态链表结构体
	int data;//存储数据元素
	int next;//下一个数组下表
};

2024年8月7日 

作者:睡一会

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值