链式存储结构实现的线性表:链表
链式存储结构:逻辑上连续的数据,在存储时不连续,但是还得通过一定手段使得这些数据逻辑上连续:指针
指针指向:地址赋值
链表:基于指针实现的
- data 数据
- 下一个数据的地址:指针类型的变量
1+2=结点--->结构体类型
结构体:
链表:用指针把相互关联的结点连接在一起就形成了链表
链表:单(向)链表 双向链表 循环链表(循环链表和循环双链表)
相关概念
- 头指针:p,保存第一个结点地址的指针 就是头指针,
告诉了整个链表的指针(所以一般用头指针视为该链表的名字)
2.首元结点:保存线性表第一个数据的结点 就是首元结点
3.头结点:一个不存放数据,仅仅存放首元结点地址的结点
首元结点和头结点是不是同一个结点?----不是
有头结点的第二个才是首元结点 没有头结点的 第一个就是首元结点
为什么带头结点:使得首元结点就不特殊了,更改后面的结点更容易(使首元结点一起更改)
不然还要单独修改首元结点的数据域,为了使首元结点和后面结点的操作统一起来
遍历链表:
Node *p=L;(相当于增加一个头指针,然后利用这个指针来遍历)
While(p!=NULL)
p=p->next;
为什么不用Node s(变量名来申请空间而是要用malloc直接申请)
因为Node s之后 s为这个结构体的变量名,在链表中用不着变量名,因为每个结点保存的是下一个结点的地址,所以用变量名的话,找地址的时候还得加取地址符号(&),所以直接用malloc来取空间,得到的就是地址,很方便直接用这个地址取赋值了,所以一般不用数据类型直接声明变量而是用malloc申请空间用指针来指向它
/操作:增删改查
增:头插法 尾插法 中间插入
头插法:固定在头结点后插入数据(结点),新插入的结点做首元结点
尾插法:在最后一个结点后面插入数据,新插入的结点做最后一个结点(尾结点)
中间插入:在指定结点之后插入数据,涉及查找函数
查找:找到指定数据所在的结点(遍历,找到数据域相等的结点)
改:指定数据改成某个数据
删除:删除指定数据
- 判断是否为空链表
- 查找:这里有区别:需要找到对应结点的前面一个结点,即需要增加一个辅助结点(把p移动下一个结点,把q再移动到p结点这样动)
链表:灵活,空间限制
缺点:不能随机访问
#include<stdio.h>
#include<stdlib.h>
//结点:结构体类型
typedef struct Node {
int data;//数据域
struct Node* next;//指针
}Node, * linklist;
//linklist p;这种写法代表p是结构体指针类型的
//linlist == Node* 为了增强代码的可读性
//linklist 强调该指针是头指针,通过保存头结点地址,标记了整个链表
//Node* q 强调该指针指向一个普通的结点
//初始化一个带头结点空链表
linklist initlist()//返回值是头指针所以类型是linklist类型的
{
//申请一个头结点空间,把地址赋值给头指针
linklist l = (Node*)malloc(sizeof(Node));
if (l == NULL)
{
printf("内存分配不成功\n");
}
else
{
l->next = NULL;
}
return l;
}
linklist head_insert(linklist l, int k)
{
Node* s = (Node*)malloc(sizeof(Node));
//判断空间会不会申请失败----略
s->data = k;
s->next = l->next;//重要的是里面的数据 (l->next里面的结点数据传给了s->next)
l->next = s;//之后再把新结点放入l->next后面
return l;
}
linklist tail_insert(linklist l, int k)
{
//找尾结点---先声明一个指针来辅助遍历找到最后一个结点
Node* p = l;
while (p->next != NULL)
{
p = p->next;
}
//找到后申请结点
Node* s = (Node*)malloc(sizeof(Node));
s->data = k;
s->next = NULL;
p->next = s;
return l;
}
//查找要找到结点,所以需要返回的是结点的地址即指针
Node* find(linklist l, int x)
{
Node* p = l->next;//从首元结点开始
//判断x数据是否存在(因为x是在结点p里面的,即判断p结点不为空)
while (p != NULL && p->data != x)
{
p = p->next;
}
return p;
}
linklist mid_insert(linklist l, int x, int k)
{//在x后面插入k
Node* p = find(l, x);
if (p == NULL)
{
printf("数据x不存在,无法插入\n");
}
else
{
Node* s = (Node*)malloc(sizeof(Node));
s->data = k;
s->next = p->next;
p->next = s;
}
return l;
}
linklist remo(linklist l, int x, int k)
{
Node* p = find(l, x);
if (p == NULL)
{
printf("数据x不存在,无法修改\n");
}
else
{
p->data = k;
}
return l;
}
linklist delet(linklist l, int k)
{
//判断是否为空链表
if (l->next == NULL)
{
printf("空链表\n");
}
else
{
//查找:
Node* p = l->next; //p从首元结点开始找
Node* q = l;//q为p的前一个结点
while (p != NULL && p->data != k)
{
q = p;
p = p->next;
}
q->next = p->next;
free(p);
p = NULL;//防止p成为野指针
}
return l;
}
删除另一个写法
//linklist delet1(linklist l, int k)
//{
// if (l->next == NULL)
// {
// printf("空链表\n");
// }
// else
// {
// Node* q = l;
// Node* p;//声明指针p
// while (q->next != NULL && q->next->data != k)
// {
// q = q->next;
// }
// q = q->next->next;
// //防止q->next为野指针 所以另把它赋值为q然后free掉
// p = q->next;
// free(p);
// }
// return l;
//}
void show(linklist l)
{
Node* p = l->next;//从首元结点开始
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
int main()
{
linklist l = initlist();//声明一个头指针 用初始化函数来初始化链表
//插入结点 增:头插法 尾插法 中间插入
//头插法 插入顺序为逆序的
l = head_insert(l, 1);//可不可以写成head_insert(&l,1);
l = head_insert(l, 4);
l = head_insert(l, 2);
l = head_insert(l, 6);
show(l);
//尾插法 插入顺序为正序 但需要定义一个指针来遍历找到尾结点( 尾结点特征:最后next指向NULL)
l = tail_insert(l, 7);
show(l);
//中间插法 在4数据后面插入数据9
l = mid_insert(l, 4, 9);
show(l);
//改
l = remo(l, 2, 3);
show(l);
//删除(先写,再去写函数内部)
l = delet(l, 6);
//l = delet1(l, 6);//里面指针有报错的地方
show(l);
return 0;
}