1.单链表
1.1 概念
- 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- 在单链表中,每个节点都存储着一个数据(data),还有一个指向下个节点的指针(next)
- 单链表用结点存储了数据以及下一个结点的地址,因此结点一般分为多个部分,即数据域与指针域
- 数据域存储 有效数据
- 指针域存储下一个结点的地址。
- 同时单链表只有一个指针域,双链表有两个指针域。
1.2 单链表与顺序表
- 两者区别:
- 顺序表是顺序存储结构,它的特点是逻辑关系上相邻的两个元素在物理位置上也相邻。
- 单链表存储结构的特点是:逻辑上相邻的元素在物理位置上不一定相邻。
- 单链表的优缺点
- 优点
- 可以按照实际所需创建结点增减链表的长度,更大程度地使用内存。
- 不要求大片连续空间,改变容量方便
- 缺点
- 不可随机存取,要耗费一定空间存放指针
- 进行尾部或者任意位置上插入或删除时时间复杂度和空间复杂度较大,每次都需要通过指针的移动找到 所需要的位置,相对于顺序表查找而言效率较低。
- 优点
- 顺序表的优缺点
- 优点
- 可以通过下标直接访问所需要的数据
- 可随机存取,存储密度高。
- 缺点
- 要求大片连续空间,改变容量不方便
- 不能按实际所需分配内存,只能使用malloc函数进行扩容,不容易实现频繁扩容,容易导致内存浪费与 数据泄露等问题。
- 优点
1.3 单链表定义
typedef struct LNode // 定义单链表节点类型
{
int data; // 数据域
struct LNode *next; // 指针域
} LNode, *LinkList;
// LNode 结构体变量
// *LinkList 结构体指针
1.3.1 不带头结点的单链表
- 单链表的创建,不用像顺序表一样需要初始化的,是由一个个节点组成,需要多少个结点就创建多少个结点
bool InitList(LinkList L)
{
L=NULL; //空表,暂时没有任何结点
return true;
}
1.3.2 带头节点的单链表
LinkList InitList(LinkList L)
{
L = (Lnode *)malloc(sizeof(Lnode)); // 分配一个头结点
if (L == NULL) // 内存不足,分配失败
return NULL;
L->next = NULL; // 头结点暂时还没有结点
return L;
}
1.3.3 带头节点 VS 不带头节点
- 带头结点,写代码更 方便,用过都说好
- 不带头结点,写代码更麻烦 ,对第一个数据结点和后续数据结点的 处理需要用不同的代码逻辑 ,对空表和非空 表的处理需要用不同的代码逻辑
2.单链表操作
2.1 插入数据
- 在表 L中的第 place 位置上插入指定元素 idata 。
void addLink(LinkList L) // 增
{
int place, idata;
puts("你想插入数据的位置:");
scanf("%d",&place);
LNode *s, *p=L;
if(place<1||place>L->data+1){
puts("add error!! @error: out of range");
}else{
puts("你想插入的数值:");
scanf("%d",&idata);
for(int i=1; i<place; i++){
p=p->next;
}
s = (LNode *)malloc(sizeof(LNode));
if (s == NULL){ // 判断是否为空 ==
puts("add error!! @error: out of memory");
}else{
s->data = idata;
s->next = p->next;
p->next = s;
L->data++;
}
}
}
- 考虑要点:
- 在开头插入时
- 在中间插入时
- 在结尾插入时
- 超出范围时
- 不带头结点写代码更不方便,推荐用带头结点,除非特别声明,之后的代码默认带头结点。
2.2 删除数据
- 按位删除
void delList(LinkList L) // 删
{
int place;
puts("你想删除的位置:");
scanf("%d",&place);
LinkList dp,p=L;
if(place<1||place>L->data){
puts("delete error!! @error: out of range");
}else{
for(int i=1; i<place; i++){
p=p->next;
}
dp = p->next;
p->next=dp->next;
free(dp);
L->data--;
}
}
- 考虑要点:
- 在开头删除时
- 在中间删除时
- 在结尾删除时
- 超出范围时
2.3 修改数据
void changeList(LinkList L) // 改
{
int place, idata;
puts("你想修改的位置:");
scanf("%d",&place);
LinkList p=L;
if(place<1||place>L->data){
puts("change error!! @error: out of range");
}else{
puts("你想修改成:");
scanf("%d",&idata);
for(int i=0; i<place; i++){
p=p->next;
}
p->data = idata;
}
}
2.4 查找数据
- 按位查找
- 按值查找
void findList(LinkList L) // 查
{
int mak;
puts("|<1>按位查找 or |<0>按值查找");
scanf("%d",&mak);
LinkList p=L;
if(mak == 1){
int place;
puts("你想查看的位置:");
scanf("%d",&place);
if(place<1||place>L->data){
puts("find error!! @error: out of range");
}else{
for(int i=0; i<place; i++){
p=p->next;
}
printf("第 %d 个数据是: %d \n",place,p->data);
}
}else if(mak == 0){
int idata, mak=0;
puts("你想查看的数值为:");
scanf("%d",&idata);
for(int i=0; i<L->data; i++){
p = p->next;
if(p->data==idata){
mak = 1;
printf(" %d 是第 %d 个数\n",idata, i+1);
}
}
if(mak==0)
puts("find error!! @error: no the data");
}else{
puts("请选择正确的选项!!");
}
}
2.5 新建单链表
- 头插法(略,同尾插法)
- 尾插法
void createList(LinkList L) // 创建链表,尾插法
{
int idata;
LNode *s;
LinkList p = L;
puts("--|请输入你想存入的数据,两两之间空格隔开,以'999'结尾|--");
scanf("%d", &idata);
while (idata != 999)
{ // 不等于999,接着循环,等于999结束
s = (LNode *)malloc(sizeof(LNode));
if (s == NULL){ // 判断是否为空 ==
puts("create error!! @error: out of memory");
}else{
s->data = idata;
s->next = NULL;
p->next = s;
L->data++;
p = s;
scanf("%d", &idata);
}
}
}
2.6 打印单链表
void printList(LinkList L) // 打印链表
{
LinkList p = L->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
putchar('\n');
}
3.完整代码
代码: