单链表的实现以及相关操作
记录自己学习的知识如果有错误的地方多多包涵请您指出,我会加以改造。都是自己的理解一个成长的爱好者。
1>什么是单链表?
不同于线性表中的顺序储存,每个节点只存放数据元素(而且分配时是连续的物理地址),而单链表每个节点除了存放数据还有存放指向下一个节点的指针(而且分配时不需要连续的物理地址空间)
所以在理论上两者实现方式不同
顺序表优缺点 单链表优缺点
优点:可以随机存取,存取密度高 优点:不需要连续的地址空间,移动元素比较便利
缺点:需要连续的空间地址,移动元素不方便 缺点:不可以随机存取,同时需要耗费一定的空间去存放指针
2>定义一个单链表
typedef的含义与用法:typedef是在计算机编程语言中用来为复杂的声明定义简单的别名,它与宏定义有些差异。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在同一个表达式中(可以对数据类型进行重新定义)
例如:对int i进行赋值
在没有对数据类型进行重定义时:int i=5
对数据类型进行重定义后:i=5;
由此可见因为单链表是一种链式节点的表他的类型是struct(结构型)所以使用重定义后可以是编写代码时更加简便提升效率
typedef struct LNode //定义单链表的类型
{
int data;//每个节点存放的数据元素
struct LNode *next;//指针指向下一个节点
}LNode,*LinkList;//对struct LNode进行重命名,用LinkList去表示指向struct LNode的指针
同时LNode *和LinkList是等价的但是强调的对象不同:LNode *->节点 LinkList->链表(可以着重去区分两者代表含义)
3>初始化单链表(带头节点和不带头节点)
不带头节点
void InitList(LinkList &L)//不带头节点
{
L=NULL;//空表暂时没有数据
if(L==NULL)
{
printf("初始化成功");
}
}
空表对L=NULL的操作是为了防止计算机内部的脏数据
不带头节点不利于编写代码,因为对于第一个数据节点的处理和后续节点的处理需要用不同的逻辑同样对空表和非空表的处理也需要用不同的逻辑
带头节点
void InitList(LinkList &L)
{
L = (LNode*)malloc(sizeof(LNode));//分配一个头节点
if (L==NULL)//校验内存分配
{
printf("内存分配失败");
}
else {
L->next = NULL;
}
}
对链表的初始化主要是分配一个头节点,应注意对分配情况的校验。L->next=NULL是因为当前表只有一个头节点应指向为空,同时头指针不存储数据
4>对单链表添加数据
这里是用尾插法对单链表进行数据的输入
思路:经过初始化以后现在的单链表是一个空表->单链表不同于线性表具有连续的地址空间(每个数据直接的连续通过指针链接)->类似于a,b进行数据交换
同理对于节点可以用类似的方法使用一个节点去保持数据,一个节点去指向下一个节点的指针域,完成对上一个节点的赋值后移动到下一个节点
所以循环赋值不会影响,D节点的内容。因为已经移动到了下一个节点
void CreatList(LinkList &L)//输入数据
{
LNode *P,*D;//DATA是用来保持每次输入的数据,P的作用是指向单链表尾部
L = (LNode*)malloc(sizeof(LNode));//创建头节点
L->next = NULL;//初始化单链表,一开始为空表
P = L;//空表时头节点也是尾部节点
int n;//输入数据的个数
printf("请输入数据的个数:");
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
D = (LinkList)malloc(sizeof(LNode));//给D节点开辟一个空间
printf("data[%d]=",i);
scanf("%d",&D->data);//每次输入的数据到D
D->next=P->next;//第一个节点已经填充,将移动下一个节点
P->next = D;//D代表的是一个节点,P->next是指向下一个节点
P = D;
}
}
5>输出单链表的数据
对于单链表的输出,同理需要定义一个新节点去指向头节点L。利用循环条件去打印全体数组
void ShowList(LinkList L)
{
LNode * P;
P =(LNode*)malloc(sizeof(LNode));//分配空间
P = L->next;//单链表从P点开始遍历,等同于从头节点开始
int i = 0;//记录位序
printf("打印单链表\n");
while (P!=NULL)//只要节点不为空就可以继续输出
{
printf("data[%d]=%d\n",i,P->data);
i++;
P = P->next;
}
}
6>单链表的插入
元素的插入(1按位序插入 2节点后插 3节点前插)
按位序插入(带头节点)
思路:找到第i-1个节点—>将元素插入其后(将第i-1个元素的指针指向插入元素,将插入元素的指针指向第i+1个元素)
void ListInsert(LinkList &L, int i, int e)//插入数据
{
if (i < 1)
printf("插入位置不合法");
else {
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)
printf("插入位置不合法");
printf("插入位置为%d是%d\n", i, e);
LNode*s = (LNode*)malloc(sizeof(LNode));//为插入元素分配一个节点
s->data = e;//将e赋值新节点的数据域
s->next = p->next;//将插入元素的指针指向第i+1个元素
p->next = s;//将第i-1和插入元素连接
}
}
对于头指针不要轻易使用如果使用头指针会造成存储空间的混乱和数据的混乱,所以重新定义一个新的节点*p去指向查询的元素同时还需要一个记录节点的数据。
LNode*s = (LNode*)malloc(sizeof(LNode))是为插入元素分配一个节点
s ->data = e;将e赋值新节点的数据域
s->next = p->next;将插入元素的指针指向第i+1个元素
p->next = s;将第i-1和插入元素连接
s->next = p->next;将插入元素的指针指向第i+1个元素
p->next = s;将第i-1和插入元素连接(不可以颠倒,则s节点就会指向s节点)则后面的数据就会混乱
不带头节点插入元素(按位)
void ListInsert(LinkList &L, int i, int e)//无头节点插入
{
if (i < 1)
printf("插入位置不合法");
if (i == 1)//插入到头节点
{
LNode* s = (LNode*)malloc)(sizeof(LNode));//分配空间
s->data = e;//元素传递给头节点
s->next = L;//和单链表进行链接
L = s;//现在s就是头节点方便变量
}
else {
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)
printf("插入位置不合法");
printf("插入位置为%d是%d\n", i, e);
LNode*s = (LNode*)malloc(sizeof(LNode));//为插入元素分配一个节点
s->data = e;//将e赋值新节点的数据域
s->next = p->next;//将插入元素的指针指向第i+1个元素
p->next = s;//将第i-1和插入元素连接
}
}
不带头节点主要需要考虑插入第一个位置时的情况,其他位置与带头节点相同
7>删除单链表的数据(带头节点)
思路:找到第i-1个节点—>将元素删除后(将第i-1个元素的指针指向第i+1个元素数据域)同时还需要释放掉第i个节点的空间
void ListDelet(LinkList &L, int i)
{
if (i < 1)
printf("删除位置不合法");
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)
printf("删除内容为空");
LNode*q = p->next;
int e;
e = q->data;//记录删除的数据
p->next = q->next;
free(q);//释放掉该节点
printf("删除元素%d成功\n",e);
}
类似于对数据的插入删除的要点再去删除第i个元素将第i-1个节点直接指向第i+1即可
不带头节点
类似于带头节点主要在于第一个节点的删除需要将第一个节点下一个节点作为头节点继续指向下一个节点
8>单链表的查找(按位/按值)
查找的本质还是要遍历单链表,还需要一个计数器去计算遍历到第几个节点
void GetElem(LinkList L, int i)
{
if (i < 0)
printf("查询位置不合法");
else {
LNode* P;//创建一个记录节点的指针
int number = 0;//记录查询到几个节点
P = L->next;//指向头节点,从头节点开始查询变量
if (i == 0)
printf("头节点为空");//头节点不存放数据
while (P != NULL && number < i)
{
P = P->next;//寻找到第i节点
number++;
}
printf("查询第%d个元素为%d\n", i,P->data );
}
}
按 值查找
void GetElem_data(LinkList L, int e)
{
LNode*P;//分配一个节点
P = L->next;
int number = 1;
while (P!=NULL&&P->data!=e)
{
P = P->next;
number++;
}
if (P = NULL)
{
printf("单链表中没有该元素");
}
else
printf("链表中有该元素位置位%d",number);
}
如果单链表中没有查询元素随着指针移动到最后一个节点的指针指向的为NULL,如果有该元素则会不满足data!=e这个条件就会跳出循环
9>整体代码
对于vissual studioC4996报错 在项目属性->C++预处理中添加_CRT_SECURE_NO_WARNINGS即可
#include<stdio.h>
#include<stdlib.h>
typedef struct LNode //定义单链表的类型
{
int data;//每个节点存放的数据元素
struct LNode *next;//指针指向下一个节点
}LNode,*LinkList;//对struct LNode进行重命名,用LinkList去表示指向struct LNode的指针
void InitList(LinkList &L)//初始化单链表
{
L = (LNode*)malloc(sizeof(LNode));//分配一个头节点
if (L==NULL)//校验内存分配
{
printf("内存分配失败");
}
else {
L->next = NULL;
}
}
void CreatList(LinkList &L)//输入数据
{
LNode *P,*D;//DATA是用来保持每次输入的数据,P的作用是指向单链表尾部
L = (LNode*)malloc(sizeof(LNode));//创建头节点
L->next = NULL;//初始化单链表,一开始为空表
P = L;//空表时头节点也是尾部节点
int n;//输入数据的个数
printf("请输入数据的个数:");
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
D = (LinkList)malloc(sizeof(LNode));//给D节点开辟一个空间
printf("data[%d]=",i);
scanf("%d",&D->data);//每次输入的数据到D
D->next=P->next;//第一个节点已经填充,将移动下一个节点
P->next = D;//D代表的是一个节点,P->next是指向下一个节点
P = D;
}
}
void ShowList(LinkList L)
{
LNode * P;
P =(LNode*)malloc(sizeof(LNode));//分配空间
P = L->next;//单链表从P点开始遍历,等同于从头节点开始
int i = 0;//记录位序
printf("打印单链表\n");
while (P!=NULL)//只要节点不为空就可以继续输出
{
printf("data[%d]=%d\n",i,P->data);
i++;
P = P->next;
}
}
void ListInsert(LinkList &L, int i, int e)//插入数据
{
if (i < 1)
printf("插入位置不合法");
else {
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)
printf("插入位置不合法");
printf("插入位置为%d是%d\n", i, e);
LNode*s = (LNode*)malloc(sizeof(LNode));//为插入元素分配一个节点
s->data = e;//将e赋值新节点的数据域
s->next = p->next;//将插入元素的指针指向第i+1个元素
p->next = s;//将第i-1和插入元素连接
}
}
void ListInsert_no(LinkList L, int i, int e)//无头节点插入
{
if (i < 1)
printf("插入位置不合法");
if (i == 1)//插入到头节点
{
LNode* s = (LNode*)malloc(sizeof(LNode));//分配空间
s->data = e;//元素传递给头节点
s->next = L;//和单链表进行链接
L = s;//现在s就是头节点方便变量
}
else {
LNode *p;//用来指向查询元素的指针
int number = 0;//记录节点的位置
p = L;//查询节点应从头开始,所以指向头节点
while (p != NULL && number < i - 1)
{
p = p->next;//只是寻找到第i-1节点
number++;
}
if (p == NULL)
printf("插入位置不合法");
printf("插入位置为%d是%d\n", i, e);
LNode*s = (LNode*)malloc(sizeof(LNode));//为插入元素分配一个节点
s->data = e;//将e赋值新节点的数据域
s->next = p->next;//将插入元素的指针指向第i+1个元素
p->next = s;//将第i-1和插入元素连接
}
}
void ListDelet(LinkList &L, int i)
{
if (i < 1)
printf("删除位置不合法");
LNode *P;//用来指向查询元素的指针
int number = 0;//记录节点的位置
P = L;//查询节点应从头开始,所以指向头节点
while (P != NULL && number < i - 1)
{
P = P->next;//只是寻找到第i-1节点
number++;
}
if (P== NULL)
printf("删除内容为空");
LNode*Q = P->next;
int e;
e = Q->data;//记录删除的数据
P->next = Q->next;
free(Q);//释放掉该节点
printf("删除元素%d成功\n",e);
}
void GetElem(LinkList L, int i)
{
if (i < 0)
printf("查询位置不合法");
else {
LNode* P;//创建一个记录节点的指针
int number = 0;//记录查询到几个节点
P = L->next;//指向头节点,从头节点开始查询变量
if (i == 0)
printf("头节点为空");//头节点不存放数据
while (P != NULL && number < i)
{
P = P->next;//寻找到第i节点
number++;
}
printf("查询第%d个元素为%d\n", i,P->data );
}
}
void GetElem_data(LinkList L, int e)
{
LNode*P;//分配一个节点
P = L->next;
int number = 1;
while (P!=NULL&&P->data!=e)
{
P = P->next;
number++;
}
if (P = NULL)
{
printf("单链表中没有该元素");
}
else
printf("链表中有该元素位置位%d",number);
}
void main()
{
LinkList L;
InitList(L);//初始化一个带头节点的单链表
CreatList(L);//输入元素
ListInsert(L,3,99);//插入元素(按位)
ListInsert_no(L, 1, 11);//不带头节点
ListDelet(L, 3);//删除节点数据
GetElem(L,3);//按位查找
GetElem_data(L,3);//按值查找
ShowList(L);//打印表
}