1、链表的介绍
(1)定义
链表是一种链式存储的线性表
链表是一种基本的数据结构,它由一系列节点组成,每个节点包含一个值和指向下一个节点的指针
节点如下图所示:
与数组不同,链表中的节点不一定是连续的存储空间,因此可以有效地利用内存空间
(2)特点:可以动态添加和删除节点,而不需要预先知道数据的数量,不擅长随机访问(要遍历)
(3)优缺点
优点:不要求大片连续空间,改变容量方便;可以动态的添加和删除节点
缺点:不方便可随机存取,要耗费一定空间存放指针
2、链表的分类
链表可以分为三种类型:
单向链表:节点由2部分组成(数据域存储当前元素,指针域用来指向下一个节点),每个节点只有一个指针指向下一个节点,最后一个节点的指针指向NULL
双向链表:节点由3部分组成(数据域存储当前元素,2个指针域:一个用来指向上一个节点地址,一个用来指向下一个节点地址),每个节点有两个指针,分别指向上一个节点和下一个节点,可以在O(1)时间内实现向前和向后遍历
循环链表:在单向或双向链表的基础上,将最后一个节点的指针指向头节点,形成一个环
链表的种类其实有很多种,按照单双向、带头(没有元素值,只有指针)不带头、循环不循环,一共可以分为8种类型,但最常见就是单向链表和双向链表
3、链表的操作
插入操作:可以在链表头或尾插入节点,也可以在指定位置插入节点(头插、尾插、中间插)
删除操作:可以删除指定节点或按照值删除节点(头删、尾删、中间删)
查找操作:可以查找指定节点或按照值查找节点
遍历操作:可以遍历整个链表,输出每个节点的值或执行其他操作
4、 单向链表(单向、不带头、不循环)
4.1 01单向链表.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <memory.h>
#include <assert.h>
//定义单向、不带头、不循环链表的元素节点结构体类型
typedef int eleT;
typedef struct Link_Node {
eleT val; //数据域:存储节点元素值
struct Link_Node* next; //指针域,指向下一个节点
}LKNode;
void print_menu();
LKNode* init_List(eleT value);
void print_List(LKNode* LinkNode);
void head_Insert(LKNode** LinkNode, eleT value);
void tail_insert(LKNode* LinkNode, eleT value);
void head_delete(LKNode** LinkNode);
void tail_delete(LKNode** LinkNode);
LKNode* find_item(LKNode* LinkNode, eleT value);
void middle_delete(LKNode* LinkNode, eleT value);
void mid_insert(LKNode* temp, eleT newvalue);
void destroy(LKNode** LinkNode);
void fn3();
4.2 main.c
#include "01单向链表.h"
int main() {
fn3();
return 0;
}
4.3 01单向链表.c
#include "01单向链表.h"
//逻辑结构:线性结构
//存储结构(物理结构):链式存储(存储顺序和逻辑顺序不在乎是否一致)
//头节点变传地址,不变传值
void fn3() {
int order = 0;
print_menu();
LKNode* LinkNode = NULL; //存放头节点指针
eleT value = 0;
eleT newvalue = 0;
LKNode* temp = NULL;
while (1) {
printf("请输入操作指令:");
scanf("%d", &order);
switch (order) {
case 1: //链表初始化,初始化一个头节点(包含元素)
printf("请输入头节点元素值:");
scanf(" %d", &value);
LinkNode = init_List(value);
break;
case 2: //打印链表,遍历
print_List(LinkNode);
break;
case 3: //链表头插
printf("请输入要插入的元素:");
scanf(" %d", &value);
head_Insert(&LinkNode, value);
break;
case 4: //链表尾插
printf("请输入要尾插的元素:");
scanf(" %d", &value);
tail_insert(LinkNode, value);
break;
case 5: //链表头删
head_delete(&LinkNode);
break;
case 6: //链表尾删
tail_delete(&LinkNode);
break;
case 7: //链表的查找
//思路:找到了,返回当前元素节点的指针;找不到,返回NULL
printf("请输入要查找的元素:");
scanf(" %d", &value);
temp = find_item(LinkNode, value);
if (temp == NULL) {
printf("没有此元素!\n");
}else {
printf("找到了,该元素值为:%d\n", temp->val);
}
break;
case 8: //链表的中间删,至少要3个元素
printf("请输入要删除的元素:");
scanf(" %d", &value);
middle_delete(LinkNode, value);
break;
case 9: //链表的中间插
//要求:至少有1个元素
printf("请输入要插入的元素:");
scanf(" %d", &newvalue);
printf("请输入要在哪个元素的后边插入:");
scanf(" %d", &value);
temp = find_item(LinkNode, value);
if (temp == NULL) {
printf("没有此元素,无法插入!\n");
}else{
mid_insert(temp, newvalue);
}
break;
case 10: //链表销毁
destroy(&LinkNode);
break;
case 11: //退出
return;
default:
printf("输入错误,请重新输入!\n");
}
}
}
//打印菜单
void print_menu(){
system("cls"); //屏幕清空
printf("操作指令说明:\n");
printf("1:链表初始化\n2:打印链表\n");
printf("3:链表头插\n4:链表尾插\n");
printf("5:链表头删\n6:链表尾删\n");
printf("7:链表的查找\n8:链表的中间删\n");
printf("9:链表的中间插\n10:链表销毁\n");
printf("11:退出\n");
printf("************************\n");
}
//链表初始化,初始化一个头节点(包含元素)
LKNode* init_List(eleT value) {
//申请节点内存
LKNode* newNode = (LKNode*)malloc(sizeof(LKNode));
判断内存是否申请成功
//if (!newNode) {
// printf("内存申请失败!\n");
// return;
//}
//补充两个调试代码常用的方法
//exit(参数) //用于终止程序执行,输出一些参数,一般用0表示正常退出,非0异常退出
//assert(条件) //条件为真,程序正常执行;条件为假,程序报错,退出(需要导入<assert.h>)
//简化 判断内存是否申请成功
assert(newNode);
newNode->val = value;
newNode->next = NULL;
printf("初始化成功!\n");
return newNode;
}
//打印链表,遍历
void print_List(LKNode* LinkNode) {
assert(LinkNode);
遍历
//LKNode* temp = LinkNode;
//while (temp->next != NULL) {
// printf("%d ", temp->val);
// temp = temp->next;
//}
//printf("%d ", temp->val);
//另一种遍历方法
LKNode* temp = LinkNode;
while (1) {
printf("%d ", temp->val);
if (temp->next == NULL) { //尾节点
break;
}
temp = temp->next; //temp是一个指针;temp->next也是一个指针,它存储在当前节点temp中,指向链表中的下一个节点,我将其理解为存储的是下一个节点的地址
}
printf("\n");
}
//链表头插
void head_Insert(LKNode** LinkNode, eleT value) {
assert(*LinkNode);
//(1)创建新节点
LKNode* newNode = (LKNode*)malloc(sizeof(LKNode));
assert(newNode);
//(2)给新节点成员赋值
newNode->val = value;
newNode->next = *LinkNode;
//(3)更新头节点
*LinkNode = newNode;
printf("头插成功!\n");
}
//链表尾插
void tail_insert(LKNode* LinkNode, eleT value) {
assert(LinkNode);
//(1)创建新节点
LKNode* newnode = (LKNode*)malloc(sizeof(LKNode));
assert(newnode);
//(2)给新节点成员赋值
newnode->val = value;
newnode->next = NULL;
//(3)将新节点放到链表的尾部
//首先找到链表的尾节点
LKNode* temp = LinkNode;
while (1) {
if (temp->next == NULL) { //尾节点
//是尾节点,将新节点插到尾节点的后边
temp->next = newnode;
break;
}
temp = temp->next; //temp是一个指针,temp->next也是一个指针,它存储在当前节点temp中,指向链表中的下一个节点
}
printf("尾插成功!\n");
}
//链表头删
void head_delete(LKNode** LinkNode) {
assert(*LinkNode);
//(1)保存当前头节点的后一个节点
LKNode* temp = (*LinkNode)->next;
//(2)释放原来头节点
free(*LinkNode);
//(3)更新头节点
*LinkNode = temp;
printf("头删成功!\n");
}
//链表尾删
void tail_delete(LKNode** LinkNode) {
assert(*LinkNode);
//需要考虑到只有一个节点的情况
if ((*LinkNode)->next == NULL) {
free(*LinkNode); //释放当前节点
*LinkNode = NULL;
}else {
//找到链表的倒数第二个元素节点
LKNode* temp = *LinkNode;
while (1) {
if (temp->next->next == NULL) { //temp->next:倒数第二个元素节点中的next;temp->next->next:尾节点中next
break;
}
temp = temp->next;
}
//释放尾节点
free(temp->next); //temp:倒数第二个元素节点
temp->next = NULL; //更新尾节点
printf("尾节点删除成功!\n");
}
}
//链表的查找
//思路:找到了,返回当前元素节点的指针;找不到,返回NULL
LKNode* find_item(LKNode* LinkNode, eleT value) {
assert(LinkNode);
LKNode* temp = LinkNode;
while (temp) {
if (temp->val == value) {
//找到了
return temp;
}
temp = temp->next;
}
return NULL;
}
//链表的中间删
void middle_delete(LKNode* LinkNode, eleT value) {
assert(LinkNode);
//查找到删除元素的前一个元素的地址
LKNode* temp = LinkNode;
while (temp) {
if (temp->next->val == value) { //temp:当前节点(删除元素的前一个元素);temp->next:当前节点的下一个节点
//找到了,删除temp->next
LKNode* ptr = temp->next; //保存要删除的节点
temp->next = temp->next->next;
free(ptr); //释放要删除的节点
printf("删除成功!\n");
break;
}
temp = temp->next;
if (temp->next == NULL) {
printf("没有此元素,无法删除!\n");
break;
}
}
}
//链表的中间插
//要求:至少有1个元素
void mid_insert(LKNode* temp, eleT newvalue) {
//(1)创建新节点
LKNode* newnode = (LKNode*)malloc(sizeof(LKNode));
assert(newnode);
//(2)给新节点数据域赋值
newnode->val = newvalue;
//(3)将新节点插入
newnode->next = temp->next;
temp->next = newnode;
printf("插入成功!\n");
}
//链表销毁
void destroy(LKNode** LinkNode) {
//遍历出来一个一个删除,并更新头节点
assert(*LinkNode); //*LinkNode:头节点
while (*LinkNode) {
LKNode* temp = (*LinkNode)->next;
free(*LinkNode);
*LinkNode = temp;
}
*LinkNode = NULL;
printf("销毁成功!");
}