链表,是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表中每一个元素称为结点,链表由一系列结点组成,结点可以在运行时动态生成。作用是解决顺序表中数据大量移动的问题以及顺序表中元素数量固定的问题。但是链表也有缺点,链表不能数组随机读取,同时链表由于增加了结点的指针域,空间占用比较大。链表有两个域,分别是数据域,指针域。
typedef struct node_t
{
int data; //数据域
struct node_t *next; //指针域,通过指针域可以找到下一个节点
}link_node_t;//结构体类型重定义
链表的类型有四种,分别是:单向链表,双向链表,单向循环链表,双向循环链表。
单项链表还分为两种类型:带头结点单向链表,不带头结点单项链表。
头结点是一个空节点,这个节点不存放有效数据,只有next是有效的。
首先,带头结点的单向链表的格式:
while(p->next != NULL)
{
p = p->next;
printf("%d\n", p->data);
}
其次,不带头结点的单向链表的格式:
while(p != NULL)
{
printf("%d\n", p->data);
p = p->next;
}
单向循环链表: 一般是不带头结点的,所有节点都有效 (尾节点的next 是首节点)
例题:用带头结点的单向链表存储n个学生成绩 ,成绩由键盘输入,输入<=0 时结束
#include <stdio.h>
#include <stdlib.h>
typedef struct node_t
{
int data;
struct node_t *next;
}link_node_t;
int main()
{
link_node_t *h = malloc(sizeof(link_node_t));//h 是头节点 malloc动态内存分配
h->next = NULL;
link_node_t *q = h; //q 永远指向最后一个节点
while(1)
{
int n;
scanf("%d", &n);//输入学生成绩
if(n <= 0)
break;
link_node_t *p = malloc(sizeof(link_node_t));//定义一个p节点,目的是:承接
p->data = n;
p->next = NULL;
q->next = p;
q = p;
}
while(h->next != NULL)
{
h = h->next;
printf("%d\n", h->data);
}
}
同时,在这里我要总结一下类似于顺序表中的链表的基本操作。
1.创建空链表(带头结点)
#include <stdio.h>
#include <stdlib.h>//有关头结点的应用,在我之前的博客里有提到,可以去看
typedef struct node_t
{
int data;
struct node_t *next;
}link_node_t;
link_node_t *CreateEmptyLinklist()
{
link_node_t *p = malloc(sizeof(link_node_t));//malloc 一个头节
p->next = NULL;//头节点的next = NULL
return p;//返回p
}
2.求链表长度
int LengthLinklist(link_node_t *p)
{
int len = 0;
while(p->next != NULL)
{
len++;
p = p->next;
}
return len;
}
3.取出某个位置元素的值
int getValue(link_node_t *p, int pos)
{
int i;
for(i = 0; i < pos; i++)
p = p->next;
return p->data;
}
4.插入元素
int InsertLinklist(link_node_t *p, int pos, int x)
{
int i;
//容错处理, 如果pos 错,不能插入
if(pos > LengthLinklist(p) + 1)
return -1;
for(i = 0; i < pos - 1; i++)
p = p->next;
link_node_t *q = malloc(sizeof(link_node_t));
q->data = x;
q->next = p->next;
p->next = q;
return 0;
}
5.删除元素
int DeleteLinklist(link_node_t *p, int pos)
{
int i;
if(pos > LengthLinklist(p))
return -1;
for(i = 0; i < pos - 1; i++)
p = p->next;
link_node_t *q = p->next;
p->next = q->next;
free(q);
return 0;
}
6.反向输出(也是递归的简单应用)
void reverse_print_all(link_node_t *p)
{
if(p->next == NULL)
return;
reverse_print_all(p->next);
printf("%d,", p->next->data);
}
7.链表逆转 //也就是原来的最后一个节点,变成第一个节点
void ReverseLinklist(link_node_t *h)
{
link_node_t *p, *q;
p = h->next;
h->next = NULL;
while (p != NULL)
{
q = p;
p = p->next;
q->next = h->next;
h->next = q;
}
return;
}
8.链表的释放
void free_list(link_node_t *p)
{
while(p != NULL)
{
link_node_t *q = p->next; //保存下一个节点
free(p);
printf("1\n");
p = q;
}
}
9.打印
void print_all(link_node_t *p)
{
while(p->next != NULL)
{
p = p->next ;
printf("%d,", p->data);
}
printf("\n");
}
10.主函数
int main()
{
link_node_t *h = CreateEmptyLinklist();
InsertLinklist(h, 1, 40); //40
InsertLinklist(h, 1, 30); //30, 40
InsertLinklist(h, 1, 10); //10, 30, 40
InsertLinklist(h, 2, 20); //10, 20, 30, 40
print_all(h); //打印所有元素
ReverseLinklist(h);
print_all(h); //打印所有元素 //40, 30, 20, 10
DeleteLinklist(h, 2);
print_all(h); //打印所有元素 //10, 30, 40
reverse_print_all(h); //40, 30, 10
free_list(h); //释放所有元素(每释放一个,打印1)
}
最后举一个单向链表的经典例题:约瑟夫问题
要求:1) 形成有8个节点的单向循环链表,里面的值(1,2,3,4,5,6,7,8);2) 先找到k(3);3) 循环报数m(4)(删除节点), 直到只剩一个节点;4) 将最后一个节点输出
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef struct node_t
{
int data;
struct node_t *next;
}link_node_t;
int main()
{
int i, k = 3, m = 4;
link_node_t *h = malloc(sizeof(link_node_t)); //先创建 新节点
link_node_t *p, *q = h;
h->data = 1;
h->next = NULL;
for(i = 2; i <= 8; i++)
{
p = malloc(sizeof(link_node_t));
p->data = i;
q->next = p;
q = p;
}
q->next = h; //构成单向循环链表
for(i = 0; i < k - 1; i++)
h = h->next; //找到第k个节点
while(h->next != h) //循环报数 说明还有多于1个节点
{
//报数, 报道m 出局,应该找到 m 前面的节点
for(i = 0; i < m - 1 - 1; i++)
{
h = h->next;
}
q = h->next; //q 要删除的节点
h->next = q->next; //从链表中删除
printf("%d\n", q->data);
free(q); //释放掉
h = h->next; //从下一个开始报数
}
printf("%d\n", h->data);
}
PS:关于链表,这里仅仅总结了常用的单向链表,在之后如有机会,还会总结其他几种类型的链表。好了,本次就到这里,如有错误,还望评论区纠正。Respect!