1、链表的定义
链表由一个个节点构成,每个节点一般由结构体形式组织:
typedef struct student
{
int num;
char name[20];
struct student* next;
}STU
[师说]一个存储数据元素的数据域,存储下一个节点地址的指针域
存储链表的数据是离散的
data表示数据,其可以是简单的类型(如int,double等等),也可以是复杂的结构体(struct类型)
next表示指针,它永远指向自身的下一个结点,对于只有一个结点的存在,这个next指针则永远指向自身,对于一个链表的尾部结点,next永远指向开头。
2、简单链表的创建
#include<stdio.h>
struct student
{long num;
float score;
struct student *next;
};
main()
{
struct student a,b,c,*head,*p;
a.num=10101;a.score=89.5;
b.num=10103;b.score=90;
c.num=10107;c.score=85;/*对结点的num和score成员赋值*/
head=&a;/*将结点a的起始地址赋给头指针head */
a.next=&b;/*将结点b的起始地址赋给a结点的next成员*//*将结点c的起始地址赋给b结点的next成员*//*c结点的next成员不存放其他结点地址*//*使p指针指向a结点*/
b.next=&c;
c.next=NULL;
p=head;
do{
printf("%ld %5.1f\n",p->num,p->score); /*输出p指向的结点的数据*/
p=p->next; /*使p指向下一结点*//*输出完c结点后p的值为NULL*/
}while(p!=NULL); /*输出完c结点后p的值为NULL*/
}
#include <stdio.h>
struct student {
long num;
float score;
struct student *next;
};
int main() {
struct student a, b, c, *head;
a.num = 10101;
a.score = 89.5;
b.num = 10103;
b.score = 90;
c.num = 10107;
c.score = 85; // 对结点的 num 和 score 成员赋值
head = &a; // 将结点 a 的起始地址赋给头指针 head
a.next = &b; // 将结点 b 的起始地址赋给 a 结点的 next 成员
b.next = &c; // 将结点 c 的起始地址赋给 b 结点的 next 成员
c.next = NULL; // c 结点的 next 成员不存放其他结点地址
// 输出链表中的每个结点
while (head != NULL) {
printf("%ld %5.1f\n", head->num, head->score); // 输出 head 指向的结点数据
head = head->next; // 使 head 指向下一个结点,会丧失掉head这个节点的起始位置
}
return 0;
}
【师说】
没有头指针 head 行不行?
- 理论上可以不用 head,但使用 head 有几个重要优点: a. 它提供了一个固定的入口点来访问整个链表。 b. 使得在链表开头添加或删除元素变得更容易。 c. 使代码更清晰,表明链表的起始位置。
- 如果没有 head,我们就需要直接使用 &a 来开始遍历,这样会使代码less flexible不够灵活。
p起什么作用?没有它行不行?
- p 是一个用于遍历链表的临时指针。
- 它的作用是: a. 指向当前正在访问的结点。 b. 通过 p = p->next 移动到下一个结点。
- 没有 p 也可以完成遍历,但会使代码复杂化:
- 我们可以直接使用 head,但这样会改变 head 的值,失去了链表的起始点。
- 或者每次都使用 head->next->next 这样的形式,但这样代码会变得很难读和维护。
本例是比较简单的,所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表称为“静态链表”。
3、链表的创建,增、删、改,查
3.1 尾插和头插
节点定义要牢靠,数据指针记得清; 头尾指针初始化,新添节点malloc行; 头插尾插各有法,巧妙连接灵活应; 遍历全程打印验,释放内存莫忘记。
3.1.1 无头节点的尾插法链表
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
Node *next;
}Node;
int main()
{
int x;
Node *p,*head=NULL,*r=NULL;
for (x=1;x <= 3;x++) {
p = (Node *) malloc(sizeof( Node ));
p->data = x;
p->next = NULL;
if (head==NULL){
head = p; // 使head指向第一个节点
r = p; // r 也指向第一个节点
}
else {
r->next = p; // 将上一个节点的next指向当前新节点
r = p; // 更新r,使其始终指向最后一个节点
}
}
p = head; //为了让head总指向链表头,不发生变化
while (p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
[师说]一定要连接起来
3.1.2. 有头节点的尾插法
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
Node *next;
}Node;
int main()
{
int x;
Node *p;
// 头节点通常初始化,不存储实际数据
Node *head = (Node *) malloc(sizeof(Node));
head->next = NULL;
Node *r = head; // r 开始时指向头节点
for (x = 1; x <= 3; x++) {
p = (Node *) malloc(sizeof(Node));
p->data = x;
p->next = NULL;
r->next = p; // 始终在尾部插入新节点
r = p; // 更新尾指针
}
p = head->next; //为了让head总指向链表下一个指针,不发生变化
while (p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
3.1.3 无头节点的头插法链表
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
Node *next;
}Node;
int main()
{
int x;
Node *p,*head=NULL,
for (x=1;x <= 3;x++) {
p = (Node *) malloc(sizeof( Node ));
p->data = x;
p->next = head;
head=p;
}
p = head; //为了让head总指向链表头,不发生变化
while (p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
3.1.4 带头节点的头插法链表
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
Node *next;
}Node;
int main()
{
int x; Node *p;
Node *head = (Node *)malloc(sizeof(Node)); // 创建一个虚拟头节点
head->next = NULL; // 初始化虚拟头节点的next指针
for (x=1;x <= 3;x++) {
p = (Node *) malloc(sizeof( Node ));
p->data = x;
p->next = head->next;
head->next=p;
}
p = head->next ; //为了让head总指向链表头,不发生变化
while (p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
3.2 插入节点 无论是头节点、尾节点或者中间节点 (有无头结点都包括)
#include<stdio.h>
#include<stdlib.h>
typedef struct Node{
int data;
Node *next;
}Node;
int main()
{
int x,i,e;
Node *p,*head=NULL,*r=NULL;
for (x=1;x <= 3;x++) {
p = (Node *) malloc(sizeof( Node ));
p->data = x;
p->next = NULL;
if (head==NULL){
head = p; // 使head指向第一个节点
r = p; // r 也指向第一个节点
}
else {
r->next = p; // 将上一个节点的next指向当前新节点
r = p; // 更新r,使其始终指向最后一个节点
}
}
printf("请输入要插入的位置和元素:");
scanf("%d %d", &i, &e);
Node *pTemp = (Node *)malloc(sizeof(Node));
pTemp->data = e;
if (i == 1) { // 插入第1个结点的操作 //带头结点不包括
pTemp->next = head;
head = pTemp;
}else{
p = head;
int j = 1; //有头结点的int j = 0; // 从头结点开始计数
while (p != NULL && j < i - 1) { // 循环找到要插入位置的前驱结点
p = p->next;
j++;
}
if (p == NULL) {
printf("插入位置无效\n");
free(pTemp);
return 1;
}
pTemp->next = p->next;
p->next = pTemp;
}
p = head; //为了让head总指向链表头,不发生变化
while (p!=NULL){
printf("%d ",p->data);
p=p->next;
}
}
- 不带头结点:位置从1开始计数,1表示第一个实际数据节点。
- 带头结点:位置从0开始计数,0表示头结点,1表示第一个实际数据节点。
3.3 删除
3.3.1 不带头结点的删除
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
int main() {
int x, i, e, delPos;
Node *p, *head = NULL, *r = NULL;
// 构建初始链表
for (x = 1; x <= 3; x++) {
p = (Node *)malloc(sizeof(Node));
p->data = x;
p->next = NULL;
if (head == NULL) {
head = p; // 使head指向第一个节点
r = p; // r 也指向第一个节点
} else {
r->next = p; // 将上一个节点的next指向当前新节点
r = p; // 更新r,使其始终指向最后一个节点
}
}
// 输入插入位置和要插入的值
printf("请输入要插入的位置和元素:");
scanf("%d %d", &i, &e);
// 创建新的节点
Node *pTemp = (Node *)malloc(sizeof(Node));
if (pTemp == NULL) {
printf("内存分配失败\n");
return 1;
}
pTemp->data = e;
if (i == 1) { // 插入到链表头部的情况
pTemp->next = head;
head = pTemp;
} else {
p = head;
int j = 1;
while (p != NULL && j < i - 1) { // 循环找到要插入位置的前驱结点
p = p->next;
j++;
}
if (p == NULL) {
printf("插入位置无效\n");
free(pTemp);
return 1;
}
pTemp->next = p->next;
p->next = pTemp;
}
// 打印链表
printf("插入之后的链表: ");
p = head;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
// 删除节点
printf("请输入要删除的位置:");
scanf("%d", &delPos);
if (delPos == 1) { // 删除第一个结点
p = head;
head = p->next;
free(p);
} else {
p = head;
int j = 1;
while (p != NULL && j < delPos - 1) { // 找到待删除位序的前一位结点
p = p->next;
j++;
}
if (p == NULL || p->next == NULL) {
printf("删除位置无效\n");
// 注意:这里应添加部分代码,以便在输入位置无效时,不继续执行下面的代码
return 1;
}
Node *q = p->next; // 待删除结点
p->next = q->next; // q跳过
free(q);
}
// 打印链表
printf("删除之后的链表: ");
p = head;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
3.3.2 带头结点的删除
#include <stdlib.h>
#include <stdio.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
int main() {
int x, i, e, delPos;
Node *p, *head, *r;
// 创建头结点
head = (Node *)malloc(sizeof(Node));
if (head == NULL) {
printf("内存分配失败\n");
return 1;
}
head->data = 0; // 头结点不存储有效数据
head->next = NULL;
r = head; // r 指向当前链表的尾部
// 构建初始链表,三个节点
for (x = 1; x <= 3; x++) {
p = (Node *)malloc(sizeof(Node));
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
p->data = x;
p->next = NULL;
r->next = p; // 将新节点连接到链表末尾
r = p; // 更新r,使其指向最后一个节点
}
// 输入插入位置和要插入的值
printf("请输入要插入的位置和元素:");
scanf("%d %d", &i, &e);
// 创建新的节点
Node *pTemp = (Node *)malloc(sizeof(Node));
if (pTemp == NULL) {
printf("内存分配失败\n");
return 1;
}
pTemp->data = e;
// 插入节点
p = head;
int j = 0; // 从头结点开始计数
while (p != NULL && j < i - 1) { // 循环找到要插入位置的前驱结点
p = p->next;
j++;
}
if (p == NULL) {
printf("插入位置无效\n");
free(pTemp);
return 1;
}
pTemp->next = p->next;
p->next = pTemp;
// 打印链表
printf("插入之后的链表: ");
p = head->next; // 跳过头结点
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
// 删除节点
printf("请输入要删除的位置:");
scanf("%d", &delPos);
p = head;
for (j = 0; j < delPos - 1 && p != NULL; j++) { // 循环找到要删除位置的前驱结点
p = p->next;
}
if (p == NULL || p->next == NULL) {
printf("删除位置无效\n");
return 1;
}
Node *q = p->next; // 待删除结点
p->next = q->next; // q跳过
free(q);
// 打印链表
printf("删除之后的链表: ");
p = head->next; // 跳过头结点
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return 0;
}
一线教师吐血奉献【链表总结】
最新推荐文章于 2024-09-23 15:47:10 发布