数据结构——链表和单向链表

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("销毁成功!");

}

相关推荐
6Hzlia34 分钟前
【Hot 100 刷题计划】 LeetCode 24. 两两交换链表中的节点 | C++ 精准指针舞步
c++·leetcode·链表
qiqsevenqiqiqiqi1 小时前
MT2048三连 暴力→数学推导→O (n) 优化
数据结构·c++·算法
码之气三段.1 小时前
十五届山东ccpc省赛补题(update)
数据结构·c++·算法
保持清醒5403 小时前
二叉链表实现
数据结构
paeamecium3 小时前
【PAT甲级真题】- Recover the Smallest Number (30)
数据结构·算法·pat考试·pat
玛丽莲茼蒿3 小时前
Leetcode hot100 在排序数组中查找元素的第一个和最后一个位置【中等】
数据结构·算法
踩坑记录4 小时前
leetcode 92. 反转链表 II 区间反转(不是整条链表反转)
leetcode·链表
寒秋花开曾相惜4 小时前
(学习笔记)4.2 逻辑设计和硬件控制语言HCL(4.2.3 字级的组合电路和HCL整数表达式)
android·网络·数据结构·笔记·学习
发疯幼稚鬼4 小时前
二叉树的广度优先遍历
c语言·数据结构·算法·宽度优先
love在水一方4 小时前
【Voxel-SLAM】Data Structures / 数据结构文档(二)
数据结构·人工智能·机器学习