Data_structures_and_Algorithms
本文代码采用C语言编写,用到了部分Cpp语法,详见{% btn regular::C_NOTE::mikumikudaifans.github.io/Displace.gi… fa-play-circle %} 的Cpp引用部分
由于每种数据结构各种操作需要相互协同工作才能起作用,总体代码长度过长,不方便阅读,故此我决定将不同操作分开讲解,并在章节最后附上整体的代码 (其实就是合到一起)
时间复杂度&空间复杂度
时间复杂度(time complexity)
时间复杂度是指算法执行所需时间的量度,通常以输入规模 的函数形式表示。它反映了算法的运行时间如何随着输入规模的增加而变化。
- :常数时间复杂度,与输入规模无关,执行时间固定。
- :线性时间复杂度,时间与输入规模成正比,如遍历数组。
- :对数时间复杂度,适合分治法(如二分查找)。
- :平方时间复杂度,常见于嵌套循环
通常我们观察一段代码的运行次数来判断它的时间复杂度
def example(arr):
for i in range(len(arr)):
for j in range(len(arr)):
print(arr[i] + arr[j])
外层循环运行 次,内层循环也运行 次,因此时间复杂度为
空间复杂度(space complexity)
常见情况
- :仅使用常数空间,通常为变量或指针的操作,如简单循环。
- :需要存储所有输入元素的大小空间,如线性数组。
- :需要矩阵或二维表结构的空间,如图论算法。
计算空间复杂度的步骤
- 识别固定存储:如函数内的常量。
- 计算辅助结构:例如数组、栈、队列。
- 加和取上限:得到整体复杂度。
高效算法应在降低时间复杂度的前提下尽量减少空间开销
线性表
线性表(Linear List)是计算机科学中一种常见的数据结构,用来表示元素按顺序排列的一组数据。在线性表中,数据元素之间存在一对一的线性关系,即每个数据元素有且仅有一个直接前驱和一个直接后继(第一个和最后一个元素除外)。线性表可用于各种场景,包括队列、栈、链表等
顺序表(Sequence List)
顺序表 (Sequnce List) 使用一块连续的内存空间来存储线性表的数据元素。每个元素的存储位置可以通过一个下标直接访问,其本质是一个数组。
- 优点:
- 支持快速的随机访问,可以通过下标直接访问任何位置的元素。
- 空间利用率较高,不需要额外的存储空间。
- 缺点:
-
插入和删除元素的操作效率较低,涉及大量元素的移动。
-
顺序存储的大小固定,无法动态扩展。
-
1. 顺序表的静态定义 :
包含一个数组 和 一个用来记录当前顺序表长度的int变量
typedef int ElemType; //将顺序表的值类型另命名,方便后续修改数据类型类型
//定义顺序表
typedef struct
{
ElemType data[MaxSize]; //线性表本质是一个数组,被人为的添加了逻辑顺序和排序长度
int length; //当前顺序表中有多少个元素
}SqList;
2.插入 :
从插入位置开始,将后续元素依次向后移动,腾出插入位置
// 在排序表第i个位置添加元素 //排序表从1开始 //索引表从0开始
bool ListInsert(SqList& L, int i, ElemType element)
{
//参数归一化,方便后续操作
int i_index = i - 1; //将参数从排序表转换为索引表
int length_index = L.length - 1; //将参数从排序表长度转换为末端索引值
if (i_index < 0 || i_index > length_index) //判断i是否合法,
{
printf("the position of i is illegal , change it to less than L.length\n");
return false;
}
if (length_index == MaxSize) //存储空间满了则无法继续插入值
{
printf("MaxSize cannot insert value\n");
return false;
}
L.length++; //因为要加入一个新元素,故长度加一
length_index = L.length - 1; //由于L.length发生了变化,需更新length_index
for (int j = length_index; j >= i_index; j--) //根据索引
{
L.data[j] = L.data[j - 1];
}
L.data[i_index] = element;
}
3.删除 :
从删除位置开始,将后续元素向前移动
//指定元素值删除首个符合条件的元素 //若想删除所有的同一元素,则只需循环调用此函数即可
bool DelteElement_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i < L.length-1; i++)//获取第一个符合目标元素值的索引值
{
if (element == L.data[i])
{
j = i;
break;
}
}
// 若未找到元素,返回 false
if (j == -1) {
printf("Element not found.\n");
return false;
}
for (int i = j; i < L.length - 1; i++)
{
L.data[i] = L.data[i + 1];
}
L.length--;
return true;
}
//指定排序位置删除元素
bool DelteElement_by_position(SqList& L, int i)
{
//参数归一化,方便后续操作
int i_index = i - 1; //将参数从排序表转换为索引表
int length_index = L.length - 1; //将参数从排序表长度转换为末端索引值
if (i_index < 0 || i_index > length_index) //判断i是否合法,
{
printf("the position of i is illegal , change it to less than L.length\n");
return false;
}
for (int i = i_index; i < L.length - 1; i++)
{
L.data[i] = L.data[i + 1];
}
L.length--;
return true;
}
4.查找 :
直接通过下标访问目标位置元素
//根据值查找该值所在的首个排序位置
int SerchFirstPosition_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i < L.length - 1; i++)//获取第一个符合目标元素值的排序位置
{
if (element == L.data[i])
{
j = i + 1;
break;
}
}
// 若未找到元素,返回 false
if (j == -1)
{
printf("Element not found.\n");
}
printf("the first position of value:%d is %d\n", element,j);
return j;
}
//根据值查找该值所在的所有排序位置
int SerchAllPositions_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i <= L.length-1; i++)//获取第一个符合目标元素值的排序位置
{
if (element == L.data[i])
{
j = i + 1;
printf("the position of value:%d is %d\n", element, j);
}
}
// 若未找到元素,返回 false
if (j == -1)
{
printf("The Element of %d is not found.\n", element);
}
return 0;
}
总体代码
{% folding blue::标准代码架构 %}
#include <stdio.h>
#define MaxSize 50
typedef int ElemType; //将顺序表的值类型另命名,方便后续修改数据类型类型
//定义顺序表
typedef struct
{
ElemType data[MaxSize]; //线性表本质是一个数组,被人为的添加了逻辑顺序和排序长度
int length; //当前顺序表中有多少个元素
}SqList;
// 在排序表第i个位置添加元素 //排序表从1开始 //索引表从0开始
bool ListInsert(SqList& L, int i, ElemType element)
{
//参数归一化,方便后续操作
int i_index = i - 1; //将参数从排序表转换为索引表
int length_index = L.length - 1; //将参数从排序表长度转换为末端索引值
if (i_index < 0 || i_index > length_index) //判断i是否合法,
{
printf("the position of i is illegal , change it to less than L.length\n");
return false;
}
if (length_index == MaxSize) //存储空间满了则无法继续插入值
{
printf("MaxSize cannot insert value\n");
return false;
}
L.length++; //因为要加入一个新元素,故长度加一
length_index = L.length - 1; //由于L.length发生了变化,需更新length_index
for (int j = length_index; j >= i_index; j--) //根据索引
{
L.data[j] = L.data[j - 1];
}
L.data[i_index] = element;
}
//指定元素值删除首个符合条件的元素 //若想删除所有的同一元素,则只需循环调用此函数即可
bool DelteElement_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i < L.length-1; i++)//获取第一个符合目标元素值的索引值
{
if (element == L.data[i])
{
j = i;
break;
}
}
// 若未找到元素,返回 false
if (j == -1) {
printf("Element not found.\n");
return false;
}
for (int i = j; i < L.length - 1; i++)
{
L.data[i] = L.data[i + 1];
}
L.length--;
return true;
}
//指定排序位置删除元素
bool DelteElement_by_position(SqList& L, int i)
{
//参数归一化,方便后续操作
int i_index = i - 1; //将参数从排序表转换为索引表
int length_index = L.length - 1; //将参数从排序表长度转换为末端索引值
if (i_index < 0 || i_index > length_index) //判断i是否合法,
{
printf("the position of i is illegal , change it to less than L.length\n");
return false;
}
for (int i = i_index; i < L.length - 1; i++)
{
L.data[i] = L.data[i + 1];
}
L.length--;
return true;
}
//根据值查找该值所在的首个排序位置
int SerchFirstPosition_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i < L.length - 1; i++)//获取第一个符合目标元素值的排序位置
{
if (element == L.data[i])
{
j = i + 1;
break;
}
}
// 若未找到元素,返回 false
if (j == -1)
{
printf("Element not found.\n");
}
printf("the first position of value:%d is %d\n", element,j);
return j;
}
//根据值查找该值所在的所有排序位置
int SerchAllPositions_by_value(SqList& L, ElemType element)
{
int j = -1; //初始化j,否则无法作为左操作数输入将其他变量的值赋予j
for (int i = 0; i <= L.length-1; i++)//获取第一个符合目标元素值的排序位置
{
if (element == L.data[i])
{
j = i + 1;
printf("the position of value:%d is %d\n", element, j);
}
}
// 若未找到元素,返回 false
if (j == -1)
{
printf("The Element of %d is not found.\n", element);
}
return 0;
}
//打印列表
void PrintList(SqList L)
{
for (size_t i = 0; i < L.length; i++)
{
printf("%3d", L.data[i]);
}
printf("\n-------length=%d---------\n", L.length);
printf("\n");
}
int main()
{
SqList L;
L.data[0] = 1; //向顺序表中添加数值
L.data[1] = 7;
L.data[2] = 2;
L.data[3] = 3;
L.data[4] = 5;
L.data[5] = 7;
L.length = 6; //设置长度
PrintList(L);
//ListInsert(L, 3, 99);
//PrintList(L);
//ListInsert(L, 7, 90);
//PrintList(L);
//DelteElement_by_value(L,7);
//PrintList(L);
//DelteElement_by_position(L, 4);
//PrintList(L);
//SerchFirstPosition_by_value(L, 7);
SerchAllPositions_by_value(L, 88);
return 0;
}
{% endfolding %}
单链表(Single Linked List)
单链表是一种链式数据结构,用于存储一组节点。每个节点包含数据域和指针域,其中:
- 数据域(Data Field):存储节点的数据。
- 指针域(Next Field):存储指向下一个节点的指针。
注意,这里构造的单链表头节点内无值,但因visual studio2022会自动在输出时为空的值域填充随机值,故用我们这里NULL即0填充,在实际使用链表时头节点会被填充数据,写入链表长度或其他数值信息
我改主意了,头节点数据填入链表长度
在链表中,节点的概念是逻辑性的虚拟的,实际代码层面只存在包含于构造体中的指针和数据
1.创建单链表
包含数据域 和 指针域,指针用于指向下一个结构体(节点)
typedef int ElementType;
typedef struct LNode{
ElementType data; //数据域
LNode* next; //指向结构体LNode的指针
}LNode,*LinkList; //定义链表的名字和头指针 //*LinkList表示LinkList是一个指针变量,指向结构体LNode的首地址
//LNode* 和 LinkList 都可以表示结构体LNode的首地址
2.头插法添加节点

头插法是一种在链表头部插入节点的方式,适用于倒序建立链表。步骤如下:
- 为新节点分配内存。
- 设置新节点的数据值。
- 将新节点的
next指针指向当前的头节点。 - 更新头指针,使其指向新节点。
//头插法,将新结点插入到链表的头部
void List_head_insert(LinkList &L)
{
LNode* s; //定义一个指针变量s,指向新结点
ElementType x = 0; //定义一个变量x,用于输入数据
//创建头结点
L = (LinkList)malloc(sizeof(LNode)); //分配内存给链表的头结点,malloc申请内存后返回首地址给到L,则L为结构体LNode的首地址(即头指针)
L->data = x; //头结点的data域置0
L->next = nullptr;
//头插法,将新结点插入到链表的头部
scanf_s("%d", &x);//首次输入数据
while(x != 9999)
{
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = L->next; //新结点的指针域指向旧头结点
L->next = s; //将头结点的指针域指向新结点
scanf_s("%d", &x);//再次输入数据,直到输入9999结束
}
}
int main()
{
LinkList L; //定义链表的头指针.L为结构体类型的指针变量,指向结构体LNode的头节点地址
}
3.尾插法添加节点

尾插法是将新节点添加到链表尾部,适用于顺序建立链表。步骤如下:
- 为新节点分配内存。
- 设置新节点的数据值。
- 将当前尾节点的
next指针指向新节点。 - 更新尾节点,使其指向新节点
//尾插法,将新结点插入到链表的尾部
void List_tail_insert(LinkList& L)
{
LNode* s,*r; //定义一个指针变量s,指向新结点
ElementType x = 0; //定义一个变量x,用于输入数据
//创建头结点
L = (LinkList)malloc(sizeof(LNode)); //分配内存给链表的头结点,malloc申请内存后返回首地址给到L,则L为结构体LNode的首地址(即头指针)
L->data = x; //头结点的data域置0
L->next = nullptr;
r = L; //r指向头结点
scanf_s("%d", &x);
//尾插法,将新结点插入到链表的尾部
while(x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x; //给新结点赋值
r->next =s; //将r的指针域指向新结点,在第一次循环时,相当于让L的指针域指向新结点,以为上文定义了*r =L
r = s; //r指向新结点
scanf_s("%d", &x);//输入数据
}
r->next = nullptr; //最后一个结点的指针域置空
}
int main()
{
LinkList L; //定义链表的头指针.L为结构体类型的指针变量,指向结构体LNode的头节点地址
}
5.单链表查询
- 定义计数器变量
- 考虑非法参数并给出返回值
- 用while循环从头节点开始遍历链表,由计数器控制步数
- 考虑查找失败结果
//按位置查找结点
LNode* List_search_position(LinkList L, int serch_position)
{
int i = 0; //定义一个变量i,用于记录当前结点的位置
if (serch_position < 1) //如果serch_position小于1,则返回空指针
{
printf("serch_position is less than 1,i will return head pointer,it means the serch_position is 1\n");
return L;
}
if (serch_position > L->data) //如果serch_position大于链表的长度,则返回空指针
{
printf("serch_position is greater than the length of the list\n");
return nullptr;
}
while (L != nullptr && i != serch_position)//当L不为空且i不等于serch_position时,遍历链表
{
L = L->next; //L指向下一个结点
i++;
}
if(i != serch_position) //如果循环完毕后,i不等于serch_position,则说明没有找到serch_position位置的结点
{
printf("The serch_position is not found,it may because the serch_position is greater than the length of the list\n");
return nullptr;
}
return L; //返回目标结点的指针
}
//按值查找结点
LNode* List_search_value(LinkList L, ElementType x)
{
while (L != nullptr && L->data != x) //当L不为空且L的data域不等于x时,循环
{
L = L->next; //L指向下一个结点
}
if (L != nullptr) //如果L不为空,则返回L
{
return L; //当循环完毕后,如果L不为空且L的data域等于x,则说明找到了x值对应的结点 //返回找到的结点
}
else //如果L为空,则说明没有找到x值对应的结点
{
printf("The value is not found\n");
return nullptr;
}
}
int main()
{
LinkList Serch_pointer;//定义指针变量,指向要查找的结点
}
6.按位置插入节点
- 定义一个指针变量s,用于指向新结点
- 考虑在第一个位置插入的结点的情况,此时直接应用头插法算法
- 通过
List_search_position(L, insert_position - 1)获取目标结点的前一个结点的指针域的值 - 判断p是否为空,决定是否执行插入算法
//按位置插入结点到第insert_position个位置
bool List_insert_position(LinkList L, int insert_position, ElementType x)
{
LinkList s; //定义一个指针变量s,用于指向新结点
if (insert_position == 1) {
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = L->next; //新结点的指针域指向旧头结点
L->next = s; //将头结点的指针域指向新结点
return true;
}
LinkList p = List_search_position(L, insert_position - 1);//p指向insert_position位置的结点,即p的值为目标结点的前一个结点的指针域的值
if (p == nullptr) //如果s为空,则说明insert_position大于链表的长度,插入到尾结点之后
{
printf("insert_position is greater than the length of the list,you can't insert it\n");
return false;
}
else
{
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = p->next; //新结点的指针域指向旧结点的下一个结点
p->next = s; //将旧结点的指针域指向新结点
}
}
7.单链表删除节点
- 通过
List_search_position(L, insert_position - 1)获取目标结点的前一个结点的指针域的值 - 判断p是否为空,决定是否执行删除算法
//按位置删除指定结点
bool List_delete_position(LinkList &L, int delete_position)
{
LinkList p = List_search_position(L, delete_position - 1); //p指向要删除的结点
if (p == nullptr) //如果p为空,则说明delete_position大于链表的长度,删除失败
{
printf("delete_position is greater than the length of the list,you can't delete it\n");
return false;
}
else
{
LinkList q; //定义一个指针变量q,用于储存被删除结点的地址
q = p->next; //q获得了目标节点的指针域的值
p->next = q->next; //将p的指针域指向q的下一个结点
free(q); //释放要删除的结点
return true;
}
}
单链表总体代码
{% folding blue::单链表总体代码 %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef int ElementType;
typedef struct LNode{
ElementType data; //数据域
LNode* next; //指向结构体LNode的指针
}LNode,*LinkList; //定义链表的名字和头指针 //*LinkList表示LinkList是一个指针变量,指向结构体LNode的首地址
//LNode* 和 LinkList 都可以表示结构体LNode的首地址
//头插法,将新结点插入到链表的头部
void List_head_insert(LinkList &L)
{
LNode* s; //定义一个指针变量s,指向新结点
ElementType x = 0; //定义一个变量x,用于输入数据
//创建头结点
L = (LinkList)malloc(sizeof(LNode)); //分配内存给链表的头结点,malloc申请内存后返回首地址给到L,则L为结构体LNode的首地址(即头指针)
//L->data = x; //头结点的data域置0
L->data = 99; //头结点的data域置为链表长度,注意这里的99是随便设置的,具体取值要根据链表实际长度来确定
L->next = nullptr;
//头插法,将新结点插入到链表的头部
scanf_s("%d", &x);//首次输入数据
while(x != 9999)
{
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = L->next; //新结点的指针域指向旧头结点
L->next = s; //将头结点的指针域指向新结点
scanf_s("%d", &x);//再次输入数据,直到输入9999结束
}
}
//尾插法,将新结点插入到链表的尾部
void List_tail_insert(LinkList& L)
{
LNode* s,*r; //定义一个指针变量s,指向新结点 r,指向最后一个结点
ElementType x = 0; //定义一个变量x,用于输入数据
//创建头结点
L = (LinkList)malloc(sizeof(LNode)); //分配内存给链表的头结点,malloc申请内存后返回首地址给到L,则L为结构体LNode的首地址(即头指针)
//L->data = x; //头结点的data域置0
L->data = 99; //头结点的data域置为链表长度,注意这里的99是随便设置的,具体取值要根据链表实际长度来确定
L->next = nullptr;
r = L; //r指向头结点
scanf_s("%d", &x);
//尾插法,将新结点插入到链表的尾部
while(x != 9999)
{
s = (LinkList)malloc(sizeof(LNode));
s->data = x; //给新结点赋值
r->next =s; //将r的指针域指向新结点,在第一次循环时,相当于让L的指针域指向新结点,以为上文定义了*r =L
r = s; //r指向新结点
scanf_s("%d", &x);//输入数据
}
r->next = nullptr; //最后一个结点的指针域置空
}
//按位置查找结点
LNode* List_search_position(LinkList L, int serch_position)
{
int i = 0; //定义一个变量i,用于记录当前结点的位置
if (serch_position < 1) //如果serch_position小于1,则返回空指针
{
printf("serch_position is less than 1,i will return head pointer,it means the serch_position is 1\n");
return L;
}
if (serch_position > L->data) //如果serch_position大于链表的长度,则返回空指针
{
printf("serch_position is greater than the length of the list\n");
return nullptr;
}
while (L != nullptr && i != serch_position)//当L不为空且i不等于serch_position时,遍历链表
{
L = L->next; //L指向下一个结点
i++;
}
if(i != serch_position) //如果循环完毕后,i不等于serch_position,则说明没有找到serch_position位置的结点
{
printf("The serch_position is not found,it may because the serch_position is greater than the length of the list\n");
return nullptr;
}
return L; //返回目标结点的指针
}
//按值查找结点
LNode* List_search_value(LinkList L, ElementType x)
{
while (L != nullptr && L->data != x) //当L不为空且L的data域不等于x时,循环
{
L = L->next; //L指向下一个结点
}
if (L != nullptr) //如果L不为空,则返回L
{
return L; //当循环完毕后,如果L不为空且L的data域等于x,则说明找到了x值对应的结点 //返回找到的结点
}
else //如果L为空,则说明没有找到x值对应的结点
{
printf("The value is not found\n");
return nullptr;
}
}
//按位置插入结点到第insert_position个位置
bool List_insert_position(LinkList L, int insert_position, ElementType x)
{
LinkList s; //定义一个指针变量s,用于指向新结点
if (insert_position == 1) {
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = L->next; //新结点的指针域指向旧头结点
L->next = s; //将头结点的指针域指向新结点
return true;
}
LinkList p = List_search_position(L, insert_position - 1);//p指向insert_position位置的结点,即p的值为目标结点的前一个结点的指针域的值
if (p == nullptr) //如果s为空,则说明insert_position大于链表的长度,插入到尾结点之后
{
printf("insert_position is greater than the length of the list,you can't insert it\n");
return false;
}
else
{
s = (LinkList)malloc(sizeof(LNode)); //分配内存给新结点
s->data = x; //给新结点赋值
s->next = p->next; //新结点的指针域指向旧结点的下一个结点
p->next = s; //将旧结点的指针域指向新结点
}
}
//按位置删除指定结点
bool List_delete_position(LinkList &L, int delete_position)
{
LinkList p = List_search_position(L, delete_position - 1); //p指向要删除的结点
if (p == nullptr) //如果p为空,则说明delete_position大于链表的长度,删除失败
{
printf("delete_position is greater than the length of the list,you can't delete it\n");
return false;
}
else
{
LinkList q; //定义一个指针变量q,用于储存被删除结点的地址
q = p->next; //q获得了目标节点的指针域的值
p->next = q->next; //将p的指针域指向q的下一个结点
free(q); //释放要删除的结点
return true;
}
}
//打印链表
void List_print(LinkList L)
{
while(L != nullptr)
{
printf("%d ", L->data);
L = L->next;
}
printf("\n");
}
int main()
{
LinkList L; //定义链表的头指针.L为结构体类型的指针变量,指向结构体LNode的头节点地址
LinkList Serch_pointer;//定义指针变量,指向要查找的结点
List_head_insert(L);
//List_tail_insert(L);
List_print(L);
Serch_pointer = List_search_position(L, 1); //查找第3个结点
printf("The data of the serch_pointer is %d\n", Serch_pointer->data);
//Serch_pointer = List_search_value(L, 10); //查找值为10的结点
//printf("The data of the serch_pointer is %d and the value is %d\n", L, Serch_pointer->data);
////打印指针没有实际意义,只是为了测试指针是否存在,层序每次运行的结果都不一样,所以指针的值也是随机的
//List_insert_position(L, 1, 100); //在第3个位置插入值为100的结点
//List_print(L);
List_delete_position(L, 1); //删除第3个结点
List_print(L);
return 0;
}
{% endfolding %}
栈 (Stack)
栈是一种**后进先出(LIFO)**的数据结构。栈中元素只能在一端添加或移除,这一端被称为“栈顶”。在栈的操作中,常用的操作有:
- push:将元素压入栈顶。
- pop:将栈顶元素弹出。
- peek:查看栈顶元素,但不移除它。
栈的应用:广泛用于递归算法、表达式求值、括号匹配、函数调用等场景。
栈的本质为人为控制进出规则的数组,通过控制索引来控制数组中的数据进出,栈可以顺序定义也可链式定义
顺序栈(squencial stack)
有顺序表发展而来的数据结构,其在内存中需要连续的空间
1.顺序栈结构定义
顺序栈由一个数组和一个用于标记数组索引的整型值组成
#define MAX_SIZE 100
typedef int ElementType;
//顺序栈(squencial stack)结构定义
typedef struct Stack
{
ElementType data[MAX_SIZE];//栈体
int top;//栈顶索引
}SqStack;
int main()
{
SqStack S;
}
2.初始化栈
void InitStack(SqStack& S)
{
S.top = -1;//栈顶索引初始化为-1,表示栈为空 同时意味着栈索引从0开始
}
3.入栈操作(Push)
- 判断是否可继续入栈
- 修改索引,填值
//判断栈是否为满
bool IsStackFull(SqStack S)
{
if (S.top == MAdata_x_SIZE - 1)
{
printf("Stack is full!\n");
return false;
}
else
return true;
}
//入栈操作(Push)
bool Push(SqStack& S, ElementType data_x)
{
if (IsStackFull(S))
{
//S.top++;
//S.data[S.top] = data_x; //该代码可被优化
S.data[++S.top] = data_x; //优化后代码 其中[++S.top]表示S.data索引自增1,然后将data_x值存入栈顶
return true;
}
else
{
return false;
}
}
4.出栈操作(Pop)
- 判断栈内是否有值
- 取值,修改索引
//出栈操作(Pop)(获取栈顶元素并删除栈内的栈顶元素)
bool Pop(SqStack& S, ElementType& data_x)
{
if (S.top == -1)
{
printf("Stack is empty,can not pop!\n");
return false;
}
else
{
//data_x = S.data[S.top];
//S.top--;
data_x = S.data[S.top--]; //优化后代码 其中[S.top--]表示S.data先调用索引为[S.top]的值后,再将索引自减1,然后取出栈顶元素值
return true;
}
}//出栈本质为索引向前移动
5.读取栈顶元素(Peek)
- 判断栈内是否有值
- 取值
//读取栈顶元素(Peek)
bool Peek(SqStack& S, ElementType& data_x)
{
if (S.top == -1)
{
printf("Stack is empty,can not get top!\n");
return false;
}
else
{
data_x = S.data[S.top];
return true;
}
}
顺序栈总体代码
{% folding blue::顺序栈总体代码 %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAdata_x_SIZE 8
typedef int ElementType;
//顺序栈(squencial stack)结构定义
typedef struct Stack
{
ElementType data[MAdata_x_SIZE];
int top; //栈顶索引
}SqStack;
//初始化栈
void InitStack(SqStack& S)
{
S.top = -1;//栈顶索引初始化为-1,表示栈为空 同时意味着栈索引从0开始
}
//判断栈是否为满
bool IsStackFull(SqStack S)
{
if (S.top == MAdata_x_SIZE - 1)
{
printf("Stack is full!\n");
return false;
}
else
return true;
}
//入栈操作
bool Push(SqStack& S, ElementType data_x)
{
if (IsStackFull(S))
{
//S.top++;
//S.data[S.top] = data_x; //该代码可被优化
S.data[++S.top] = data_x; //优化后代码 其中[++S.top]表示S.data索引自增1,然后将data_x值存入栈顶
return true;
}
else
{
return false;
}
}
//出栈操作(获取栈顶元素并删除栈内的栈顶元素)
bool Pop(SqStack& S, ElementType& data_x)
{
if (S.top == -1)
{
printf("Stack is empty,can not pop!\n");
return false;
}
else
{
//data_x = S.data[S.top];
//S.top--;
data_x = S.data[S.top--]; //优化后代码 其中[S.top--]表示S.data先调用索引为[S.top]的值后,再将索引自减1,然后取出栈顶元素值
return true;
}
}//出栈本质为索引向前移动
//读取栈顶元素
bool GetTop(SqStack& S, ElementType& data_x)
{
if (S.top == -1)
{
printf("Stack is empty,can not get top!\n");
return false;
}
else
{
data_x = S.data[S.top];
return true;
}
}
//输出栈
void PrintStack(SqStack S)
{
if (S.top == -1)
{
printf("Stack is empty,nothing to print!\n");
return;
}
for (int i = 0; i <= S.top; i++)
{
printf("%d ", S.data[i]);
}
printf("\n");
}
int main()
{
SqStack S;
InitStack(S);
Push(S, 1);
Push(S, 2);
Push(S, 3);
Push(S, 4);
Push(S, 5);
Push(S, 6);
Push(S, 7);
Push(S, 8);
Push(S, 9);
Push(S, 10);
//测试栈
for (int i = 0; i < MAdata_x_SIZE; i++)
{
if(S.top == -1)
break;
PrintStack(S);
ElementType y;
GetTop(S,y);
printf("Top element is %d\n", y);
Pop(S, y);
printf("Pop element is %d\n", y);
printf("After pop, stack is:\n");
PrintStack(S);
printf("\n");
}
return 0;
}
{% endfolding %}
链栈(Link Stack)
由链表发展而来,使用零散内存空间
1. 链栈定义
完全继承链表定义形式
#define ElemType int
typedef struct LinkStack
{
ElemType data;
struct LinkStack* next;
}*LS;
int main()
{
LS L;
}
2. 链栈初始化
由于链表仅能由头结点开始遍历的特性,考虑到后续操作,链栈最佳初始化方案为头插法
void createStack(LS &L)
{
LS p;
ElemType x = 0;
//头结点
L = (LS)malloc(sizeof(struct LinkStack));
L->data = x;
L->next = nullptr;//后续第一个进入栈的节点将指向空,当栈为空时,next指向空
}
3.栈的状态
链栈没必要设置上限,除非是大数据流进入,否则零散利用内存的分配方式几乎不可能将内存占满
bool isEmpty(LS L)//当栈为空时,next指向空
{
if (L->next == nullptr)
return true;
else
return false;
}
4. 入栈
算法与头插法创建链表相同
void push(LS& L, ElemType x)
{
LS p;
p = (LS)malloc(sizeof(struct LinkStack));
p->data = x;
p->next = L->next;
L->next = p;
}
5.出栈
调用并删除链表中第一个结点
bool pop(LS& L, ElemType& x)
{
LS p;
if (L->next == nullptr)
{
printf("Stack is empty!\n");
return false;
}
p = L->next;
x = p->data;
L->next = p->next;
free(p);
return true;
}
6. 获取栈顶数据
bool getTop(LS L, ElemType& x)
{
if (L->next == nullptr)
{
printf("Stack is empty!\n");
return false;
}
x = L->next->data;
return true;
}
链栈总体代码
{% folding blue::链栈总体代码 %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ElemType int
typedef struct LinkStack
{
ElemType data;
struct LinkStack* next;
}*LS;
void createStack(LS &L)
{
LS p;
ElemType x = 0;
//头结点
L = (LS)malloc(sizeof(struct LinkStack));
L->data = x;
L->next = nullptr;//后续第一个进入栈的节点将指向空,当栈为空时,next指向空
}
bool isEmpty(LS L)
{
if (L->next == nullptr)
return true;
else
return false;
}
void push(LS& L, ElemType x)
{
LS p;
p = (LS)malloc(sizeof(struct LinkStack));
p->data = x;
p->next = L->next;
L->next = p;
}
bool pop(LS& L, ElemType& x)
{
LS p;
if (L->next == nullptr)
{
printf("Stack is empty!\n");
return false;
}
p = L->next;
x = p->data;
L->next = p->next;
free(p);
return true;
}
bool getTop(LS L, ElemType& x)
{
if (L->next == nullptr)
{
printf("Stack is empty!\n");
return false;
}
x = L->next->data;
return true;
}
void printStack(LS L)
{
while (L->next!= nullptr)
{
printf("%d ", L->next->data);
L = L->next;
}
printf("\n");
}
int main()
{
LS L;
createStack(L);
push(L, 1);
push(L, 2);
push(L, 3);
push(L, 4);
printStack(L);
ElemType x;
pop(L, x);
printf("poped element is %d\n", x);
printStack(L);
getTop(L, x);
printf("top element is %d\n", x);
printStack(L);
return 0;
}
{% endfolding %}
队列(Queue)
队列是一种**先进先出(FIFO)**的数据结构。队列中元素只能从一端进入(队尾),从另一端移除(队头)。常用操作包括:
- enqueue:将元素加入队尾。
- dequeue:将队头元素移除。
- peek:查看队头元素。
队列的应用:广泛用于任务调度、广度优先搜索、数据缓冲等场景。
循环(顺序)队列(SquenceQueue)
1. 创建队列
循环队列又一个自定义的数组和队首与队尾两个逻辑指针构成
#define MAX_SIZE 5
#define ElementType int
typedef struct
{
ElementType data[MAX_SIZE];
int front, rear;
} SquenceQueue;
//在循环队列中,data[SQ.rear]中的SQ.rear的值
2. 队列初始化
规定队列首尾标相等时为空
void InitQueue(SquenceQueue& SQ)
{
SQ.front = SQ.rear = 0;
}
int main()
{
SquenceQueue SQ;
}
3. 入队
循环队列核心算法 :
(SQ.rear + 1) % MAX_SIZE算法实现了索引循环的功能,且SQ.rear + 1避免了在数组被占满时尾标与头标重合的情况,即组下标越界,使得队列满时队首和队尾重合,再插入元素时会覆盖队首元素
该算法规定队首和队尾相等时为空,(SQ.rear + 1) % MAX_SIZE的存在SQ.rear和SQ.front的取值范围为[0,MAX_SIZE-1],构造体中的数组最后一位的被索引跳过,该存储单元值为0,且不会被调用
bool EnQueue(SquenceQueue& SQ,ElementType value)
{
if ((SQ.rear + 1) % MAX_SIZE == SQ.front) //队尾标加1并向队列长度取余,等于队头,表示队列已满
{
printf("Queue is full!\n");
return false;
}
SQ.data[SQ.rear] = value;
SQ.rear = (SQ.rear + 1) % MAX_SIZE; //当队尾标移动到倒数第二个位置时表示队列已满,向队列长度取余,使队尾标归零
return true;
}
4. 出队
与入队原理相同
bool DeQueue(SquenceQueue& SQ, ElementType& value)
{
if (SQ.front == SQ.rear) //队列为空,队头和队尾重合
{
printf("Queue is empty!\n");
return false;
}
value = SQ.data[SQ.front];
SQ.front = (SQ.front + 1) % MAX_SIZE; //队头向队列长度取余,使队头标归零
return true;
}
5. 查看队首元素
bool peek(SquenceQueue SQ, ElementType& value)
{
if (SQ.front == SQ.rear) //队列为空,队头和队尾重合
{
printf("Queue is empty!\n");
return false;
}
return false;
value = SQ.data[SQ.front];
return true;
}
循环队列总体代码
{% folding blue::循环队列总体代码 %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_SIZE 5
#define ElementType int
typedef struct
{
ElementType data[MAX_SIZE];
int front, rear;
} SquenceQueue;
//在循环队列中,data[SQ.rear]中的SQ.rear的值
void InitQueue(SquenceQueue& SQ)
{
SQ.front = SQ.rear = 0;
}
bool EnQueue(SquenceQueue& SQ,ElementType value)
{
if ((SQ.rear + 1) % MAX_SIZE == SQ.front) //队尾标加1并向队列长度取余,等于队头,表示队列已满
{
printf("Queue is full!\n"); //
return false;
}
SQ.data[SQ.rear] = value;
SQ.rear = (SQ.rear + 1) % MAX_SIZE; //当队尾标移动到倒数第二个位置时表示队列已满,向队列长度取余,使队尾标归零
return true;
}
bool DeQueue(SquenceQueue& SQ, ElementType& value)
{
if (SQ.front == SQ.rear) //队列为空,队头和队尾重合
{
printf("Queue is empty!\n");
return false;
}
value = SQ.data[SQ.front];
SQ.front = (SQ.front + 1) % MAX_SIZE; //队头向队列长度取余,使队头标归零
return true;
}
void PrintQueue(SquenceQueue SQ)
{
if (SQ.front == SQ.rear) //队列为空,队头和队尾重合
{
printf("Queue is empty!\n");
return;
}
for (int i = SQ.front; i != SQ.rear; i = (i + 1) % MAX_SIZE)
{
printf("%d ", SQ.data[i]);
}
printf("\n");
}
bool peek(SquenceQueue SQ, ElementType& value)
{
if (SQ.front == SQ.rear) //队列为空,队头和队尾重合
{
printf("Queue is empty!\n");
return false;
}
return false;
value = SQ.data[SQ.front];
return true;
}
int main_SQ()
{
SquenceQueue SQ;
InitQueue(SQ);
EnQueue(SQ, 11);
EnQueue(SQ, 22);
EnQueue(SQ, 33);
EnQueue(SQ, 44);
EnQueue(SQ, 55);
PrintQueue(SQ);
ElementType testvalue;
DeQueue(SQ, testvalue);
printf("DeQueue value is %d\n", testvalue);
PrintQueue(SQ);
DeQueue(SQ, testvalue);
printf("DeQueue value is %d\n", testvalue);
PrintQueue(SQ);
EnQueue(SQ, 66);
PrintQueue(SQ);
return 0;
}
{% endfolding %}
链队列(LinkQueue)
链队列由一个单链表和分别指代首尾的两个指针构成,用两个构造体分别构造单链表和首尾指针组
1. 构建链队列
#define ElementType int
typedef struct LinkQueueNode
{
ElementType data;
LinkQueueNode *next;
} LQN;
typedef struct LinkQueuePointers
{
LQN *front;
LQN *rear;
} LQP;
1. 初始化链队列
由于构造体LinkQueuePointers包含了用于指向LinkQueueNode的指针,因此只需创LinkQueuePointers的实例即可通过内置的指针来向内存申请空间
注意 : (LQN*)malloc(sizeof(struct LinkQueuePointers));这行代码的含义是向内存空间申请一个大小为两个指针空间,且这连个指针式用于指向构造体LinkQueuePointers的
void InitQueue(LQP &P)
{
P.front = P.rear = (LQN*)malloc(sizeof(struct LinkQueuePointers));
P.front->next = NULL;
}
int main()
{
LQP P;
}
1. 入队
单链表尾插法入队,链表在内存空间足够大的情况下无需考虑长度上限问题
void EnQueue(LQP& P, ElementType x)
{
LQN *newp = (LQN*)malloc(sizeof(struct LinkQueuePointers));
newp->data = x;
P.rear->next = newp;//尾指针的next指向新节点
P.rear = newp; //尾指针向后移动
newp->next = nullptr; //此时尾节点更新,需设置为节点next为空
}
1. 出队
- 判断队列是否为空
- 拿到第一个结点的数据并断链和释放内存
- 考虑当取出最后一个结点元素时,尾指针与头指针复位的问题
bool DeQueue(LQP& P, ElementType& x)
{
if (P.front == P.rear) //判断队列是否为空
{
printf("Queue is Empty,nothing can DeQueue");
return false;
}
LQN* dep = P.front->next; //获取队首地址
x = dep->data; //获取队首数据
P.front->next = dep->next; //队首指针指向取出元素的节点的后面的节点
if (P.rear == dep) //当取出的元素是最后一个元素时,使rear指针指向front指针,即队列为空
{
P.rear = P.front;
}
free(dep);
return true;
}
1. 查看队首元素
bool PeekQueue(LQP P, ElementType &x)
{
if (P.front == P.rear) //判断队列是否为空
{
printf("Queue is Empty,nothing can DeQueue");
return false;
}
LQN* peekp = P.front->next; //获取队首地址
x = peekp->data; //获取队首数据
return true;
}
链式队列总体代码
{% folding blue::链式队列总体代码 %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ElementType int
typedef struct LinkQueueNode
{
ElementType data;
LinkQueueNode *next;
} LQN;
typedef struct LinkQueuePointers
{
LQN *front;
LQN *rear;
} LQP;
//采用尾插法建立链队列,规 定链尾为rear指针,链头为front指针
//入队时,将新结点p插入rear指针后面,然后将rear指针指向新结点p
//出队时,将front指针指向front指针后面的结点,并返回其数据
//判空时,若front指针为空,则队列为空,否则不为空
void InitQueue(LQP &P)
{
P.front = P.rear = (LQN*)malloc(sizeof(struct LinkQueuePointers));
P.front->next = nullptr;
}
void EnQueue(LQP& P, ElementType x)
{
LQN *newp = (LQN*)malloc(sizeof(struct LinkQueuePointers));
newp->data = x;
P.rear->next = newp;//尾指针的next指向新节点
P.rear = newp; //尾指针向后移动
newp->next = nullptr; //此时尾节点更新,需设置为节点next为空
}
bool DeQueue(LQP& P, ElementType& x)
{
if (P.front == P.rear) //判断队列是否为空
{
printf("Queue is Empty,nothing can DeQueue");
return false;
}
LQN* dep = P.front->next; //获取队首地址
x = dep->data; //获取队首数据
P.front->next = dep->next; //队首指针指向取出元素的节点的后面的节点
if (P.rear == dep) //当取出的元素是最后一个元素时,使rear指针指向front指针,即队列为空
{
P.rear = P.front;
}
free(dep);
return true;
}
bool PeekQueue(LQP P, ElementType &x)
{
if (P.front == P.rear) //判断队列是否为空
{
printf("Queue is Empty,nothing can DeQueue");
return false;
}
LQN* peekp = P.front->next; //获取队首地址
x = peekp->data; //获取队首数据
return true;
}
void PrintQueue(LQP P)
{
LQN* p = P.front->next;
while (p!= NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
int main()
{
LQP P;
InitQueue(P);
EnQueue(P, 1);
EnQueue(P, 2);
EnQueue(P, 3);
EnQueue(P, 4);
EnQueue(P, 5);
PrintQueue(P);
ElementType x = 0;
DeQueue(P, x);
printf("dequeue is %d\n", x);
PrintQueue(P);
DeQueue(P, x);
printf("dequeue is %d\n", x);
PrintQueue(P);
EnQueue(P, 6);
PrintQueue(P);
PeekQueue(P, x);
printf("front of queue is %d\n", x);
return 0;
}
{% endfolding %}