1.1 概念
链表是由一系列节点组成的,每个节点包含两个域,一个域是数据域,数据域用来保存数据;另一个是指针域,保存下一个节点的地址,链表在内存中是非连续的。
1.2 特点
链表在指定位置插入和删除不需要移动元素,只需要修改指针即可。
查找效率相对于数组会低一些。
链表相对于数组来讲,多了指针域空间开销。
拿到链表的第一个节点,就相当于拿到整个链表
1.3 分类
1.3.1 分类方式一
-
静态链表
所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表成为“静态链表”。
代码示例:
#include<stdio.h> #include<stdlib.h> #include<string.h> // 链表结点类型定义 struct linkNode{ int data; struct linkNode *next; }; void test(){ struct linkNode node1 = {10, NULL}; struct linkNode node2 = {20, NULL}; struct linkNode node3 = {30, NULL}; struct linkNode node4 = {40, NULL}; struct linkNode node5 = {50, NULL}; struct linkNode node6 = {60, NULL}; // 将链表串连起来 node1.next = &node2; node2.next = &node3; node3.next = &node4; node4.next = &node5; node5.next = &node6; // 遍历链表 // 先定义一个辅助指针变量 struct linkNode *pCurrent = &node1; while(pCurrent != NULL){ printf("%d ",pCurrent->data); // 指针移动到下一个元素的首地址 pCurrent = pCurrent->next; } } int main(){ test(); return 0; }
-
动态链表(创建、遍历、插入、清空、删除、销毁)
所谓“动态链表”,是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结 点和输入各结点数据,并建立起前后相继的关系
代码示例:
头文件:LinkList.h
// 定义数据结构
#define _CRT_SECURE_NO_WARNINGS
#pragma once // 防止头文件重复包含
#include<stdlib.h>
#include<malloc.h>
#include<stdio.h>
// 定义宏
#ifdef __cplusplus
extern "C"{
#endif
// 定义结点数据类型
struct LinkNode{
int data;
struct LinkNode *next;
};
// 初始化链表
struct LinkNode *Init_LinkList();
// 在值为oldval的地方插入一个新值newval,原数据往后挪
void InsertByValue_LinkList(struct LinkNode *header,int oldval,int newval); // 通过值来插入数据(链表,旧值,新值)
// 删除值为val的结点
void RemoveByValue_LinkList(struct LinkNode *header,int delval); // 传入链表和要删除的值
// 遍历
void Foreach_LinkList(struct LinkNode *header);
// 销毁链表(无法插入数据)
void Destory_LinkList(struct LinkNode *header);
// 清空(还能插入数据)
void Clear_LinkList(struct LinkNode *header);
#ifdef __cplusplus
}
#endif
LinkList.c
#include"LinkList.h"
// 初始化列表
struct LinkNode *Init_LinkList(){
int val = -1;
// 创建头节点
struct LinkNode *header = (struct LinkNode*)malloc(sizeof(struct LinkNode));
// 尾部的指针
struct LinkNode *pRear = header;
header->data = -1;
header->next = NULL;
while(1){
// 先创建结点
struct LinkNode *newnode = (struct LinkNode*)malloc(sizeof(struct LinkNode));
printf("输入插入数据:\n");
scanf("%d",&val);
if(val==-1){
break;
}
newnode->data = val;
newnode->next = NULL;
// 新结点插入到链表中
pRear->next = newnode;
// 更新尾部指针指向
pRear = newnode;
}
return header; // 返回头结点 相当于返回链表
}
// 在值为oldval的后面插入一个新的数据newval
void InsertByValue_LinkList(struct LinkNode *header,int oldval,int newval){ // 通过值来插入数据(链表,旧值,新值)
// 定义两个辅助指针变量
struct LinkNode *pPrev = header;
struct LinkNode *pCurrent = pPrev->next;
// 创建新结点
struct LinkNode *newnode = (struct LinkNode*)malloc(sizeof(struct LinkNode));
if(NULL == header){
return; // 如果链表不存在,直接返回
}
while (pCurrent != NULL){
if(pCurrent->data == oldval){ // 如果找到了传递过来的旧值,停止循环
break;
}
// 如果没找到,pPrev和pCurrent各向后挪一位
pPrev = pCurrent;
pCurrent = pCurrent->next;
}
#if 0 // 当找不到oldval时,添加的值放在链表的尾部
// 如果pCurrent为空,说明不存在值为oldval的结点
if(pCurrent == NULL){
return;
}
#endif
newnode->data = newval;
newnode->next = NULL;
// 新结点插入到链表中
newnode->next = pCurrent;
pPrev->next = newnode;
}
// 删除值为val的结点
void RemoveByValue_LinkList(struct LinkNode *header,int delval){ // 传入链表和要删除的值
// 两个父组指针变量
struct LinkNode *pPrev = header;
struct LinkNode *pCurrent = pPrev->next;
if(NULL == header){
return; // 如果链表不存在,直接返回
}
while(pCurrent != NULL){
if(pCurrent->data == delval){
break;
}
// 移动两个指针
pPrev = pCurrent;
pCurrent = pCurrent->next;
}
if(NULL == pCurrent){
return;
}
// 重新建立待删除结点的前驱和后继结点关系
pPrev->next = pCurrent->next;
// 释放删除结点内存
free(pCurrent);
pCurrent = NULL;
}
// 遍历
void Foreach_LinkList(struct LinkNode *header){
// 定义辅助指针变量
struct LinkNode *pCurrent = header->next;
if(NULL == header){
return; // 如果链表不存在,直接返回
}
//printf("%d ",pCurrent->data);
while(pCurrent != NULL){
printf("%d ",pCurrent->data); // 输出当前节点的数据
pCurrent = pCurrent->next; // 指针后移
}
}
// 销毁链表(无法插入数据)
void Destory_LinkList(struct LinkNode *header){
// 定义辅助指针变量
struct LinkNode *pCurrent = header->next;
if(NULL == header){
return; // 如果链表不存在,直接返回
}
while(pCurrent != NULL){
// 先保存当前结点的下一结点的地址
struct LinkNode *pNext = pCurrent->next;
// 释放当前结点内存
free(pCurrent);
// 指针向后移动
pCurrent = pNext;
}
}
// 清空(还能插入数据,只剩一个头结点)
void Clear_LinkList(struct LinkNode *header){
// 定义辅助指针变量
struct LinkNode *pCurrent = header->next;
if(NULL == header){
return; // 如果链表不存在,直接返回
}
while(pCurrent != NULL){
// 保存当前结点的下一个结点地址
struct LinkNode *pNext = pCurrent->next;
// 释放当前结点内存
free(pCurrent);
// pCurrent指向下一个结点
pCurrent = pNext;
}
// 清空最后一个数据
header->next = NULL;
}
TestLinkList.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include"LinkList.h"
void test(){
// 初始化链表
struct LinkNode *header = Init_LinkList();
// 打印链表
Foreach_LinkList(header);
// 插入数据
InsertByValue_LinkList(header,1000,666);
// 打印链表
printf("\n----------------\n");
Foreach_LinkList(header);
// 清空链表
Clear_LinkList(header);
// 打印链表
printf("\n----------------\n");
Foreach_LinkList(header);
// 插入数据
InsertByValue_LinkList(header,1000,111);
// 插入数据
InsertByValue_LinkList(header,1000,222);
// 插入数据
InsertByValue_LinkList(header,1000,333);
// 打印链表
printf("\n----------------\n");
Foreach_LinkList(header);
RemoveByValue_LinkList(header,222);
// 打印链表
printf("\n----------------\n");
Foreach_LinkList(header);
// 销毁链表
Destory_LinkList(header);
}
int main(){
test();
system("pause");
return EXIT_SUCCESS;
}
链表合并
已有a、b两个链表,每个链表中的结点包括学号、成绩。要求把两个链表合并,按学号升序排列。
输入:第一行,a、b两个链表元素的数量N、M,用空格隔开。 接下来N行是a的数据 然后M行是b的数据 每行数据由学号和成绩两部分组成
输出:按照学号升序排列的数据
样例输入
2 3 // a链表的元素数量 b链表的元素数量
5 100 // a链表中的元素
6 89 // a
3 82 // b链表中的元素
4 95 // b
2 10 // b
样例输出:
2 10
3 82
4 95
5 100
6 89
代码演示:
#include<stdio.h>
#include<malloc.h>
struct student{ // 设计结点
double number; // 数据域--学号
double score; // 数据域--成绩
struct student *next; // 指针域
};
struct student *create(int n); // 创建链表函数
void output(struct student *header); // 链表输出函数
void order(struct student *header1,struct student *header2); // 链表排序函数
int main()
{
int n,m; // n为a链表的元素数量,m为b链表中的元素的数量
struct student *header1,*header2; // 创建两个链表的头结点
if(scanf("%d %d",&n,&m) == 2){ // 对数量值进行输入
header1 = create(n); // 创建链表a 元素数量为n个
header2 = create(m); // 创建链表b 元素数量为m个
order(header1,header2); // 对两个链表进行排序
}
return 0;
}
struct student *create(int n){ // 创建链表函数
struct student *header; // 创建头结点指针
header = (struct student*)malloc(sizeof(struct student)); // 创建头结点
header->next = NULL; // 将头结点的指针域指向NULL
struct student *q = header; // 创建指针q,指向头结点
struct student *p; // 创建结点指针p
for(int i=0;i<n;i++){ // 遍历数量
p = (struct student*)malloc(sizeof(struct student)); // 创建结点
if(scanf("%lf %lf",&(p->number),&(p->score)) == 2){ // 给结点输入学号和成绩
p->next = NULL; // 采用尾插法插入结点
q->next = p; // 第一轮里把头结点的指针域指向了新建的结点p
q = p; // 将p赋值给q,以便进行下一次循环
}
}
return(header); // 返回链表
}
void output(struct student *header){ // 输出链表每个结点的信息
header = header->next; // 将header指向下一个结点
struct student *q; // 定义一个结点指针(辅助)
while(header != NULL){ // 当结点存在时进行打印
printf("%.0f %.0f\n",header->number,header->score); // 打印结点数据
q = header; // 记录下当前结点的指针
header = header->next; // 指向下一个结点,以便循环
free(q); // 释放内存
}
}
void order(struct student *header1,struct student *header2){ // 链表排序
struct student *q; // 定义辅助指针变量
q = header1; // 将q指向链表a的头结点
header2 = header2->next; // 链表b的结点下移,header2不再是头结点
while(q->next != NULL){ // 如果链表a的结点的指针域不为空
q = q->next; // 则将下一个结点赋值给q,直到最后一个结点
}
q->next = header2; // 表a的最后一个结点的next指向链表b的首结点完成连接
q = header1->next; // 再次把q指向表a的头结点
struct student *min; // 定义指向最小学号的指针
int t; // 保存最小学号的变量
int n,g; // 交换学号、成绩的中间变量
struct student *p; // 定义辅助指针变量p
while(q != NULL){ // 开始遍历,排序选择最外层
p = q; // 令p等于查找的结点q
t = p->number; // 令t等于开始查找的第一个结点的学号
min = p; // 令最小学号结点指针指向p
while(p!=NULL){ // 如果p不为空
if(p->number < t){ // 判断当前遍历的结点的学号是否小于第一个结点的学号(也就是预设的最小学号)
t = p->number; // 如果小于,就把t的值换成小于它的p->number
min = p; // 将最小学号的结点换成p
}
p = p->next; // 如果不是,则把p往后挪
}
// 交换学号
n = q->number; // n等于第一个结点的学号
// 将最小学号结点的学号与第一个结点的学号进行交换
q->number = min->number;
min->number = n;
// 同理交换成绩
g = q->score;
q->score = min->score;
min->score = g;
// 第一遍排序完后q后移,也就是选择排序开始排第二遍时的第一个数
q = q->next;
}
output(header1);
}