在之前学完顺序表之后,相信大家肯定对顺序表有一些不满意,他确实存在缺陷
1.顺序表要求存储空间的物理地址必须是连续的,但是如果我要放很多数据可能就存在异地存储的问题,这是要付出代价的
2.顺序表不能跳跃存储,存放数据的时候必须一个挨着一个,不如数组那样灵活
所以我们选择一个更加灵活的方式——链表
目录
1.链表简介
顾名思义,如果我们要解决之前顺序表空间访问与存储不自由的问题,就可以拿一个“链子”把所有不必连续的存储单元连接起来这样不就简单多了
那么只是这样太粗糙了,我们可以把这条链子的起止都记录一下,更方便查找其中任何一个小单元,所以链表要有头 ,但是不一定有哨兵位
怎么实现我们这个想法呢
每个存储单元里放进去下一个单元的地址!!!真是太妙了对不对
那链表尾这个位置放什么呢?空指针
除了下一个单元的地址,链表最基本的还是要存储数据啊!!!!
所以我们设计出
注:
下面来解释一下刚才说的哨兵位的问题
我们这个只是最简单的链表,他还有很多的类型
双向
带头和不带头
这里的头就是之前说的哨兵位,我们发现有头的没有存放任何数据,只有下一个的地址,这样的优势在于不用每一次换了新的起始位置之后要考虑头更改的问题,如果这个哨兵位一支不变那头就特别好找。
循环和非循环
但是我们最常用的还是这两种
本文只针对无头单向非循环说明
我们把每一个小的存储单元叫做节点
2.分析优劣
优点:
优势在前面就说过,无需连续存储更加灵活,而且每次增加一个新的节点,只需要小小的改动
从内存的角度更节省
缺点:
查找不方便,每次都需要从头结点开始向后面寻找节点
3. 实现
不难想到节点里面存储的两个元素就需要用结构体实现
而且不确定数据的类型,最好可以灵活一点:typedef
链表的英文名字太长,也typedef一下
typedef int type;
typedef struct Sequence_List
{
type data;
struct Sequence_List* next;
}SL;
那么我们考虑链表的增删查改
头文件的内容
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int type;
typedef struct Sequence_List
{
type data;
struct Sequence_List* next;
}SL;
SL* Buy(type x);
void Print_List(SL* phead);
void PushFront(SL** pphead, type x);
void PopFront(SL** pphead);
void PopBack(SL** pphead);
void PushBack(SL** pphead, type x);
void PosSLdeletAfter(SL** pphead, type val);
void PosSLBefore(SL** pphead, type val, type x);
void PosSLAfter(SL** pphead, type val, type x);
void Destory(SL* phead);
实现部分
#define _CRT_SECURE_NO_WARNINGS
#include "sequence.h"
SL* Buy(type x)
{
SL* newnode = (SL*)malloc(sizeof(SL));
if (newnode == NULL)
{
perror("Buy");
exit(0);
}
else
{
newnode->data = x;
newnode->next = NULL;
}
return newnode;
}
void Print_List(SL*phead)
{
while (phead)
{
printf("%d", phead->data);
phead = phead->next;
}
printf("\n");
}
//头插
void PushFront(SL** pphead,type x)
{
SL* newnode = Buy(x);
newnode->next = *pphead;
newnode->data = x;
*pphead = newnode;
}
//头删
void PopFront(SL** pphead)
{
assert(*pphead);
SL* tmp = ( * pphead)->next;
free(*pphead);
*pphead = tmp;
}
//尾删
void PopBack(SL** pphead)
{
assert(*pphead);
if ((*pphead)->next == NULL)
{
//相当于头删
SL* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
else
{
SL* prev = *pphead;
SL* tail = ( * pphead)->next;
while (tail->next)
{
tail = tail->next;
prev = prev->next;
}
prev->next = NULL;
free(tail);
tail = NULL;
}
}
//尾插
void PushBack(SL** pphead,type x)
{
SL* newnode = Buy(x);
SL* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next= newnode;
newnode->next = NULL;
}
//任意位置之后写入
void PosSLAfter(SL** pphead,type val , type x)
{
SL* newnode =Buy(x);
SL* tail = *pphead;
while (tail->data != val)
{
tail = tail->next;
}
SL* tmp = tail->next;
tail->next = newnode;
newnode->next = tmp;
free(tmp);
tmp = NULL;
}
//任意位置之前插入
void PosSLBefore(SL** pphead, type val, type x)
{
assert(*pphead);
SL* newnode = Buy(x);
if ((*pphead)->next == NULL)
{
//相当于头插
SL* tmp = (*pphead)->next;
*pphead = tmp;
free(tmp);
tmp = NULL;
}
else
{
SL* tail = *pphead;
while ((tail->next)->data!=val)
{
tail = tail->next;
}
SL* tmp = tail->next;
tail->next = newnode;
newnode->next = tmp;
}
}
int FindSL(SL*phead,type x)
{
while (phead)
{
if (phead->data == x)
{
printf("找到了\n");
return 1;
}
phead = phead->next;
}
return 0;
}
//任意位置之前删除
void PosSLdeletAfter(SL** pphead, type val)
{
assert(*pphead);
//先查找一下
int tmp = FindSL( * pphead, val);
if (!tmp)
{
perror("Find");
exit(0);
}
if ((*pphead)->data == val)
{
printf("无法删除第一个元素之前的元素\n");
exit(0);
}
if ((*pphead)->next == NULL) //只有一个元素
{
//相当于头删
SL* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
else if ((*pphead)->next->next->next == NULL)// 有两个元素最后一个元素是我要找到的位置
{
(*pphead)->next = (*pphead)->next->next;
free((*pphead)->next);
}
else
{
SL* tail = ( * pphead)->next;
SL* prev = *pphead;
while ((tail->next)->data != val)
{
tail = tail->next;
prev=prev->next;
}
//这时候tail就是要删除的位置
prev->next=tail->next;
free(tail);
}
}
//销毁
void Destory(SL* phead)
{
assert(phead);
while (phead)
{
SL* tmp = phead->next;
free(phead);
phead = tmp;
}
}
建议每次写完一个小功能都及时测试,提高效率,类似的测试可以参考如下
text.c
#define _CRT_SECURE_NO_WARNINGS
#include "sequence.h";
void test1(void)
{
SL* list = NULL;
PushFront(&list,1);
PushFront(&list,2);
PushFront(&list, 3);
PushBack(&list, 7);
Print_List(list);
}
void test2(void)
{
SL* list = NULL;
PushFront(&list, 1);
PushFront(&list, 2);
PushFront(&list, 2);
PopFront(&list);
Print_List(list);
}
void test3(void)
{
SL* list = NULL;
PushFront(&list, 7);
PushFront(&list, 6);
PushFront(&list, 5);
PushFront(&list, 4);
PushFront(&list, 3);
PushFront(&list, 2);
PushFront(&list, 1);
PosSLdeletAfter(&list, 4);
Print_List(list);
}
void test4(void)
{
SL* list = NULL;
PushFront(&list, 7);
PushFront(&list, 5);
PushFront(&list, 4);
PushFront(&list, 3);
PushFront(&list, 2);
PushFront(&list, 1);
Print_List(list);
//PosSLBefore(&list, 7, 6);
PosSLAfter(&list, 7, 8);
Print_List(list);
}
void test5(void)
{
SL* list = NULL;
PushFront(&list, 7);
PushFront(&list, 5);
PushFront(&list, 4);
PushFront(&list, 3);
PushFront(&list, 2);
PushFront(&list, 1);
Destory(list);
}
int main()
{
//test1();
//test2();
//test3();
//test4();
test5();
/*test6();*/
return 0;
}
当我们测试结束之后,可以自己写一个喜爱菜单,让自己的链表真正应用,具体菜单的格式可以很丰富,最简单的可以参考之前三子棋,扫雷,通讯录的菜单
https://blog.csdn.net/weixin_71138261/article/details/126999227?spm=1001.2014.3001.5501
本文至此结束啦,感谢大家的观看