一、宏观了解数据结构
数据结构,就是数据的逻辑结构以及存储操作
数据结构没想象的那么复杂,它就教会你一件事:如何有效的存储数据
既然要了解结构数据,那必然要知道:数据结构+算法=程序
1.1数据是什么?
我们拿图书馆里的书举例:
数据:不再是单纯的数字,而是类似于集合的概念。(图书馆)
数据元素:是数据的基本单位,由若干个数据项组成的。(一本书)
数据项:数据的最小单位,描述数据元素的有用的信息。(一本书的各个具体信息,如书名,价格和编号)
注意:我们在数据结构里,一般将数据元素称为节点。
1.2逻辑结构是什么?
数据元素不是孤立存在的,他们之间存在着某种关系(或者联系,结构)。
元素和元素之间的关系就是我们这里所说的逻辑结构。
常见的逻辑结构有以下三种:
- 线性关系:
线性结构 ==>一对一 ==> 线性表:顺序表、链表、栈、队列
- 层次关系
树形结构 ==> 一对多 ==> 树:二叉树
- 网状关系
图状结构 ==> 多对多 ==> 图
1.3存储操作是什么?
存储操作就是数据逻辑结构在计算机中的具体实现
常见的四种存储结构有:顺序存储,链式存储,索引存储,散列存储
1.3.1顺序存储
数组:内存连续
特点:内存连续、随机存取、每个元素占用空间较少
1.3.2链式存储
通过指针实现
特点:内存不连续,通过指针连接。
链表实现:
结构体:
1.3.3索引存储
在存数据的同时,建立一个附加的索引表。
即索引存储结构=索引表+数据文件
可以提高查找速度,检索速度快,但是占用内存多,删除数据文件要及时更改索引表。
例如:
这样查找一个电话就可以先查找索引表,再查找对应的数据文件,加快了查询的速度。但是如果删除或添加某个数据也要操作对应的索引表。
1.3.4散列存储
数据存储按照和关键码之间的关系进行存取。关系由自己决定,比如关键码是key, 存储位置也就是关系是key+1。获取关键数据,通过元素的关键码方法的返回值来获取。
存的时候按关系存
取的时候按关系取
1.4操作
对数据结构常见的四种操作:增删改查
2、算法
2.1算法是什么
算法是解决问题的思想方法,数据结构是算法的基础。
数据结构+算法=程序
2.2 算法的设计
算法的设计:取决于数据的逻辑结构
算法的实现:依赖于数据的存储结构
2.3 算法的特性
有穷性: 步骤是有限的
确定性:每一个步骤都有明确的含义,无二义性。
可行性:规定时间内能完成
输入
输出
2.4 评价算法的好坏
正确性
易读性
健壮性:容错处理
高效性:执行效率,通过重复执行的次数来判断,也就是可以通过时间复杂度(时间处理函数)来判断。
时间复杂度:
语句频度:用时间规模函数表达
时间规模函数:T(n)=O(f(n))
T(n) //时间规模的时间函数
O //时间数量级
n //问题规模,例如:a[100], n=100
f(n) //算法可执行语句重复执行的次数
称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
渐进时间复杂度用大写O来表示,所以也被称为大O表示法。直白的讲,时间复杂度就是把时间规模函数T(n)简化为一个数量级,如n,n^2,n^3。
计算大O的方法
- 根据问题规模n写出表达式f(n)
- 如果有常数项,将其置为1 //当f(n)的表达式中只有常数项的时候,例如f(n)=8 ==> O(1)
- 只保留最高项,其他项舍去。
- 如果最高项系数不为1,则除以最高项系数。
二、线性表
1、顺序表
1.1顺序表是什么
顺序表存储数据的具体实现方案是:将数据全部存储到一整块内存空间中,数据元素之间按照次序挨个存放。
举个简单的例子,将 {1,2,3,4,5} 这些数据使用顺序表存储,数据最终的存储状态如下图所示:
1.2顺序表的特性
特点:内存连续,大小固定。
逻辑结构:线性结构
存储结构:顺序存储
操作:增删改查
1.3顺序表的实现
1.3.1数组实现顺序表
#include <stdio.h>
int last = 7; //代表最后一个有效元素下标 last=有效元素个数-1
/* (1) 插入数组元素
功能:向数组的第几个位置插数据
函数:void insetIntoA(int *p, int post, int data);
参数:
int *p: 保存数组首地址
int post: 插入元素下标
int data: 数据
*/
void insertIntoA(int *p, int post, int data)
{
int i;
//1. 把最后一个元素p[last]到插入位置元素p[post]向后移动一个单位 //*(p+i)或p[i]
for (i = last; i >= post; i--)
p[i + 1] = p[i];
//2. 插入新数据data到指定位置
p[post] = data;
//3. 让最后一个有效元素下标+1
last++;
}
/* (2) 遍历数组元素
功能:遍历数组中的有效元素
函数:void showA(int *p);
参数:
int *p:保存数组收地址
*/
void showA(int *p)
{
for (int i = 0; i <= last; i++)
printf("%d ", p[i]);
printf("\n");
}
/* (3) 删除数组元素
功能:删除数组中指定元素
函数:void deleteIntoA(int *p,int post);
参数:
int *p: 保存数组首地址
int post: 删除元素下标
*/
void deleteIntoA(int *p, int post)
{
//从删除位置后一个元素p[post+1]到最后一个元素p[last]往前移动一个单位
for(int i=post+1;i<=last;i++)
p[i-1]=p[i];
//让最后一个有效元素下标减一
last--;
}
int main(int argc, char const *argv[])
{
int a[100] = {1, 2, 3, 4, 5, 6, 7, 8};
insertIntoA(a, 4, 100);
showA(a);
deleteIntoA(a,4);
showA(a);
return 0;
}
1.3.2结构体实现顺序表
#include <stdio.h>
#include <stdlib.h>
#define N 10
typedef int datatype;
typedef struct seqlist
{
datatype data[N];
int last; //代表数组中最后一个有效元素的下标
} seqlist_t;
//创建空顺序表
seqlist_t *CreateEpSeqlist()
{
//1. 动态申请一块空间存放顺序表
seqlist_t *p = (seqlist_t *)malloc(sizeof(seqlist_t));
if (NULL == p)
{
perror("CreateEpSeqlist p malloc err");
return NULL;
}
//2. 对last初始化
p->last = -1; //空顺序表,下标初值设为-1,那么之后来第一个有效元素就可以+1之后变成0
return p;
}
//判断顺序表是否为满,满返回1,未满返回0
int IsFullSeqlist(seqlist_t *p)
{
return p->last + 1 == N;
}
//在顺序表中插入数据
int InsertIntoSeqlist(seqlist_t *p, int post, int data)
{
//1. 对post进行容错判断
if (post < 0 || post > p->last + 1 || IsFullSeqlist(p))
{
printf("InsertIntoSeqlist post err!\n");
return -1;
}
//2. 下标为last到post向后移动一个单位
for (int i = p->last; i >= post; i--)
p->data[i + 1] = p->data[i];
//3. 插入数据
p->data[post] = data;
//4. 让last加一
p->last++;
return 0;
}
//遍历顺序表sequence顺序list表
void ShowSeqlist(seqlist_t *p)
{
int i;
for (i = 0; i <= p->last; i++)
printf("%d ", p->data[i]);
printf("\n");
}
//判断顺序表是否为空,为空返回1,不为空返回0
int IsEpSeqlist(seqlist_t *p)
{
return p->last == -1;
}
//删除顺序表中指定位置的数据,post为删除位置
int DeleteIntoSeqlist(seqlist_t *p, int post)
{
int i;
//1. 对post容错判断
if (post < 0 || post > p->last || IsEpSeqlist(p))
{
printf("DeleteIntoSeqlist post err!\n");
return -1;
}
//2. 从下标post+1到last的元素往前移动一个单位
for (i = post; i < p->last; i++)
p->data[i] = p->data[i + 1];
//3. 让last减一
p->last--;
return 0;
}
//清空顺序表(不是销毁内存。清空是访问不到,不是释放。)
void ClearSeqList(seqlist_t *p)
{
p->last = -1;
}
//查找指定数据出现的位置,返回下标,没找到返回-1
int SearchDataSeqList(seqlist_t *p, int data)
{
int i;
for (i = 0; i <= p->last; i++)
{
if (p->data[i] == data)
return i;
}
return -1;
}
//修改指定位置上的数据
int ChangePostSeqList(seqlist_t *p, int post, int data)
{
//1. 对post容错判断
if (post < 0 || post > p->last || IsEpSeqlist(p))
{
printf("ChangePostSeqList post err!\n");
return -1;
}
//2. 修改指定位置数据
p->data[post] = data;
return 0;
}
int main(int argc, char const *argv[])
{
seqlist_t *p = CreateEpSeqlist();
int flag = IsEpSeqlist(p);
if (flag == 1)
printf("Seqlist is Ep\n"); //Seqlist is Ep
printf("%d\n", p->last);
InsertIntoSeqlist(p, 0, 100);
ShowSeqlist(p); //100
InsertIntoSeqlist(p, 1, 20);
ShowSeqlist(p); //100 20
InsertIntoSeqlist(p, 2, 30);
ShowSeqlist(p); //100 20 30
InsertIntoSeqlist(p, 3, 400);
ShowSeqlist(p); //100 20 30 400
DeleteIntoSeqlist(p, 1);
ChangePostSeqList(p, 0, 250);
ShowSeqlist(p); //250 30 400
printf("Search 20:%d\n", SearchDataSeqList(p, 20)); //Search 20:-1
printf("Search 250:%d\n", SearchDataSeqList(p, 250)); //Search 250:0
free(p);
p = NULL;
return 0;
}
2、链表
2.1链表是什么
链表又称单链表、链式存储结构,用于存储逻辑关系为“一对一”的数据。
和顺序表不同同,使用链表存储数据,不强制要求数据在内存中集中存储,各个元素可以分散存储在内存中。
所以需要通过指针来连接各个节点:
所以在链表中,每个数据元素可以配有一个指针用于找到下一个元素即节点,这意味着,链表上的每个“元素”都长下图这个样子:
2.2 链表的特性
逻辑结构: 线性结构
存储结构:链式存储
特点:内存不连续,通过指针实现。
解决顺序表的问题:长度固定,插入删除麻烦。
操作:增删改查:
节点结构体:
struct node
{
int data; //数据域:存放数据
struct node *next; //指针域:保存下一个节点的地址
};
2.3单向链表
有头链表:存在一个头节点,头节点中数据域,指针域有效。
无头链表:每一个节点的数据域和指针域都有效。
2.3.1有头链表的函数操作
插入:
删除:
根据数据删除:
2.3.2有头单向链表的函数操作
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node
{
datatype data;
struct node *next;
} link_node_t, *link_list_t;
//计算链表的长度。
int lengthLinkList(link_node_t *p)
{
int len = 0;
while (p->next != NULL)
{
p = p->next;
len++;
}
return len;
}
//向单向链表的指定位置插入数据
//p保存链表的头指针 post 插入的位置 data插入的数据
int insertIntoPostLinkList(link_node_t *p, int post, datatype data)
{
link_list_t pnew = NULL;
//1.对 post进行容错判断
if (post < 0 || post > lengthLinkList(p))
{
printf("insertIntoPostLinkList err!\n");
return -1;
}
//2. 将指针p移动到插入位置的前一个
for (int i = 0; i < post; i++)
p = p->next;
//3. 新建一个节点,然后初始化节点
pnew = (link_list_t)malloc(sizeof(link_node_t));
if (NULL == pnew)
{
perror("malloc pnew err");
return -1;
}
pnew->data = data;
pnew->next = NULL;
//4. 连接新节点到链表:先连后面再连前面
pnew->next = p->next;
p->next = pnew;
return 0;
}
//创建一个空的有头单项链表
link_list_t createEmptyLinkList()
{
//创建一个节点,作为链表头节点。
link_list_t h = (link_list_t)malloc(sizeof(link_node_t));
if (NULL == h)
{
perror("malloc h err");
return NULL;
}
h->next = NULL;
return h;
}
//遍历单向链表
void showLinkList(link_node_t *p)
{
while (p->next != NULL)
{
p = p->next;
printf("%d ", p->data);
}
printf("\n");
}
//删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(link_node_t *p, int post)
{
//1. 对post容错判断
if (post < 0 || post >= lengthLinkList(p))
{
printf("deletePostLinkList err!\n");
return -1;
}
//2. 将指针p移动到要删除节点的前一个
for (int i = 0; i < post; i++)
p = p->next;
//3. 定义一个pdel指向要被删除节点
link_list_t pdel = p->next;
//4. 跨过pdel要被删除节点
p->next = pdel->next;
//5. 释放被删除节点
free(pdel);
pdel = NULL;
return 0;
}
//判断链表是否为空,为空返回1,不为空返回0.
int isEmptyLinkList(link_node_t *p)
{
return p->next == NULL;
}
//(7)清空单项链表
//思想:循环删除头节点的下一个,直到为空链表。
void clearLinkList(link_node_t *p)
{
while (p->next!=NULL) //只要不为空就删除
{
//1. 定义一个指针pdel指向头的下一个,每次删除的都是头节点的下一个
link_list_t pdel= p->next;
//2. 跨过要删除节点
p->next=pdel->next;
//3. 释放要删除节点
free(pdel);
pdel=NULL;
}
}
//查找指定数据出现的位置,返回节点位置 data被查找的数据 //search 查找
int searchDataLinkList(link_node_t *p, datatype data)
{
int post = 0;
//遍历有头链表
while (p->next!=NULL)
{
p=p->next;
if(p->data == data)
return post;
post++;
}
return -1; //说明数据不存在
}
//修改链表中指定的数据
int changePostLinkList(link_node_t *p, int post, datatype data)
{
//1. 容错判断
if(post<0 || post>= lengthLinkList(p))
{
printf("changePostLinkList err\n");
return -1;
}
//2. 将p移动到要修改的节点
for(int i=0;i<=post;i++)
p=p->next;
//3. 修改数据
p->data = data;
return 0;
}
//删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除。
int deleteDataLinkList(link_node_t *p, datatype data)
{
link_list_t pdel = NULL; // 用于删除节点
//p指针用于一直指向要删除节点的前一个
//1. 定义一个指针q,指向头节点的下一个,那么此时可以将q看作用于遍历无头链表
link_list_t q= p->next;
while (q!=NULL) //用q遍历无头链表
{
if(q->data==data) //判断q所遍历的节点数据域是否和传参进来的data相同,如果相同删除该节点,然后继续向后遍历。
{
//删除操作
//(1)pdel记录删除节点
pdel=q;
//(2)跨过删除节点
p->next=pdel->next;
//(3)释放置空
free(pdel);
pdel=NULL;
//(4)继续向后遍历
q=p->next;
}
else // 如果不相同,q和p都向后走一个
{
q=q->next;
p=p->next;
}
}
}
//转置链表
// 解题思想:
// 1)将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表。
// 2)遍历无头链表的所有节点,将每一个节点当做新节点插入空链表头节点的下一个节点(每次插入的头节点的下一个节点位置)。
void reverseLinkList(link_node_t *p)
{
link_list_t temp=NULL; //用来临时保存q的下一个,以便q可以遍历
//1.断开头节点前定义一个指针q记录头节点的下一个,用于遍历无头链表
link_list_t q = p->next;
//2. 断开头节点
p->next = NULL;
//3. 遍历无头链表
while (q != NULL)
{
//将temp指向q的下一个节点,为了下一次循环能找到
temp = q->next;
//头插: 也就是q插入到p后面,先后再前去连接
q->next = p->next;
p->next = q;
//让q指向temp继续向后遍历然后头插
q = temp;
}
}
int main()
{
int post=0;
link_list_t p = createEmptyLinkList();
insertIntoPostLinkList(p, 0, 1);
insertIntoPostLinkList(p, 1, 2);
insertIntoPostLinkList(p, 2, 3);
insertIntoPostLinkList(p, 3, 1);
insertIntoPostLinkList(p, 4, 2);
insertIntoPostLinkList(p, 5, 3);
reverseLinkList(p);
showLinkList(p);
deleteDataLinkList(p,1);
showLinkList(p);
deletePostLinkList(p, 2);
showLinkList(p);
post=searchDataLinkList(p,2);
printf("post: %d\n",post);
changePostLinkList(p,1,99);
showLinkList(p);
return 0;
}
2.4双向链表
2.4.1特性
逻辑结构:线性结构
存储结构:链式存储
操作:增删改查
2.4.2双向链表的相关操作
创空:
插入:
删除
2.4.3双向链表的函数操作
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct node //双向链表的节点结构体定义
{
datatype data; //数据域
struct node *next; //指向下一个节点的指针
struct node *prior; //指向前一个节点的指针
} link_node_t, *link_list_t;
//将双向链表的头指针和尾指针封装到一个结构体里
//思想上有点像队列
typedef struct doublelinklist
{
link_list_t head; //指向双向链表的头指针
link_list_t tail; //指向双向链表的尾指针
int len; //保存当前双向链表的长度
} double_node_t, *double_list_t;
//创建一个空的双向链表
double_list_t createEmptyDoubleLinkList()
{
//1. 申请空间存放头尾指针结构体
double_list_t p = (double_list_t)malloc(sizeof(double_node_t));
if (NULL == p)
{
perror("p malloc err");
return NULL;
}
//2. 初始化结构体,让头尾指针指向头节点(开辟头节点)
p->len = 0; //长度初值为0
p->tail = p->head = (link_list_t)malloc(sizeof(link_node_t));
if (NULL == p->tail)
{
perror("malloc p->tail err");
return NULL;
}
//3. 初始化头节点了
p->tail->next = NULL;
p->tail->prior = NULL;
return p;
}
//向双向链表的指定位置插入数据 post位置, data数据
int insertIntoDoubleLinkList(double_list_t p, int post, datatype data)
{
int i;
link_list_t temp = NULL; //用来临时保存head或tail的位置
//1. 容错判断
if (post < 0 || post > p->len)
{
printf("insertIntoDoubleLinkList err\n");
return -1;
}
//2. 创建一个新节点,保存插入的数据
link_list_t pnew = (link_list_t)malloc(sizeof(link_node_t));
if (NULL == pnew)
{
perror("pnew malloc err");
return -1;
}
//初始化新节点
pnew->data = data;
pnew->next = NULL;
pnew->prior = NULL;
//3. 将节点插入到链表中
//需要对插入位置分情况讨论,尾插,或者中间插入。
if (post == p->len) //尾插
{
p->tail->next = pnew;
pnew->prior = p->tail;
p->tail = pnew; //移动尾指针
}
else //中间插入,需要分前后半段
{
//(1)将指针移动到插入位置
if (post <= p->len / 2) //前半段,从前向后移动指针
{
temp = p->head; //向后移动指针
for (i = 0; i <= post; i++)
temp = temp->next;
}
else //后半段,从后向前移动指针
{
temp = p->tail; //向前移动指针
for (i = p->len - 1; i > post; i--) //for(i=0;i<p->len-post-1;i++)
temp = temp->prior;
}
//(2)连接 (先前再后)
pnew->prior = temp->prior;
temp->prior->next = pnew;
pnew->next = temp;
temp->prior = pnew;
}
//4. 新插入一个节点,长度加一
p->len++;
return 0;
}
//遍历双向链表
void showDoubleLinkList(double_list_t p)
{
link_list_t temp = NULL;
printf("正向遍历: ");
temp = p->head;
while (temp->next != NULL)
{
temp = temp->next;
printf("%d ", temp->data);
}
printf("\n");
printf("反向遍历: ");
temp = p->tail;
while (temp->prior != NULL)
{
printf("%d ", temp->data);
temp = temp->prior;
}
printf("\n");
}
//删除双向链表指定位置的数据
int deletePostDoubleLinkList(double_list_t p, int post)
{
int i;
link_list_t temp = NULL;
//1. 容错判断
if (post < 0 || post >= p->len)
{
printf("deletePostDoubleLinkList err\n");
return -1;
}
//2. 对位置进行判断,删尾巴还是中间删除
if (post == p->len - 1)
{
// (1) 移动尾指针到前一个单位
p->tail = p->tail->prior;
//(2)释放被删除节点,也就是最后一个节点。
free(p->tail->next);
//(3)将此时最后一个节点next置空
p->tail->next = NULL;
}
else //中间删除,判断前后半段
{
//将指针temp移动到要删除节点
if (post <= p->len / 2)
{
temp = p->head;
for (i = 0; i <= post; i++)
temp = temp->next;
}
else
{
temp = p->tail;
for (i = p->len - 1; i > post; i--)
temp = temp->prior;
}
//进行删除操作了 (跨过要删除节点)
temp->prior->next = temp->next;
temp->next->prior = temp->prior;
//释放节点
free(temp);
temp = NULL;
}
//3. 长度减一
p->len--;
return 0;
}
//查找指定数据出现的位置 data被查找的数据
int searchPostDoubleLinkList(double_list_t p, datatype data)
{
int post = 0;
link_list_t temp = p->head;
while (temp->next != NULL)
{
temp = temp->next;
if (temp->data == data)
return post;
post++;
}
return -1;
}
//修改指定位置的数据,post修改的位置 data被修改的数据
int changeDataDoubleLinkList(double_list_t p, int post, datatype data)
{
int i;
link_list_t temp = NULL;
//1. 容错判断
if (post < 0 || post >= p->len)
{
printf("changeDataDoubleLinkList err\n");
return -1;
}
//2. 将temp移动到要修改处,判断前后半段
if (post <= p->len / 2)
{
temp = p->head;
for (i = 0; i <= post; i++)
temp = temp->next;
}
else
{
temp = p->tail;
for (i = p->len - 1; i > post; i--)
temp = temp->prior;
}
//3. 修改数据
temp->data=data;
return 0;
}
int main(int argc, char const *argv[])
{
double_list_t p = createEmptyDoubleLinkList();
insertIntoDoubleLinkList(p, 0, 1);
insertIntoDoubleLinkList(p, 1, 2);
insertIntoDoubleLinkList(p, 2, 3);
insertIntoDoubleLinkList(p, 3, 4);
showDoubleLinkList(p);
deletePostDoubleLinkList(p, 3);
showDoubleLinkList(p);
printf("search 2 post: %d\n",searchPostDoubleLinkList(p,2));
changeDataDoubleLinkList(p,0,100);
showDoubleLinkList(p);
return 0;
}
3、栈
3.1什么是栈
栈是只能在一端进行插入和删除操作的线性表(又称为堆栈),进行插入和删除操作的一端称为栈顶,另一端称为栈底。
特点:栈是先进后出FILO(First In Last Out)
3.2顺序栈
3.2.1顺序栈特点
逻辑结构:线性结构
存储结构:顺序存储
操作: 创建、入栈、出栈、清空、判空和判满
3.2.2操作图解
创空:
入栈:
出栈:
3.2.3函数操作
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct seqstack
{
datatype *data; //指针,存数组的首地址(数组当作栈中存放数据的空间)
int maxlen; //保存栈的最大长度
int top; //称为栈针,用的时候可以当作之前的顺序表中last,用于表示最后一个有效元素的下标,来记录栈顶的位置。
} seqstack_t;
//创建一个空栈,len代表创建栈时的最大长度。
seqstack_t *createEmptySeqStack(int len)
{
//1. 开辟结构体大小空间
seqstack_t *p = (seqstack_t *)malloc(sizeof(seqstack_t));
if (NULL == p)
{
perror("malloc p err");
return NULL;
}
//2. 初始化
p->top = -1; //类似与之前顺序表的last,表示栈顶也就是最后一个有效元素的下标,此时没有数据入栈,可以置为-1,那么入栈第一个元素之后就可以加一变成0了,因为数组第一个元素下标为0。
p->maxlen = len; //保存栈的最大长度
p->data = (datatype *)malloc(sizeof(datatype) * len); //指针指向需要存数据的数组空间
if (NULL == p->data)
{
perror("p->data malloc err");
return NULL;
}
return p;
}
//判断是否为满,满返回1 未满返回0
int isFullSeqStack(seqstack_t *p)
{
return p->top + 1 == p->maxlen;
}
//入栈,data代表入栈的数据
int pushStack(seqstack_t *p, int data)
{
//1. 判满
if (isFullSeqStack(p))
{
printf("pushStack: isFullSeqStack\n");
return -1;
}
//2. 将top加一往上移动一个单位
p->top++;
//3. 将数据入栈:存入此时top表示位置
p->data[p->top] = data;
return 0;
}
//判断栈是否为空,为空返回1,不为空返回0
int isEmptySeqStack(seqstack_t *p)
{
return p->top == -1;
}
// 获取栈顶数据(注意不是出栈操作,如果出栈,相当于删除了栈顶数据,只是将栈顶的数据获取到,不需要移动栈针)
int getTopSeqStack(seqstack_t *p)
{
//1. 容错判断,判空
if (!isEmptySeqStack(p))
return p->data[p->top]; //如果不为空则返回栈顶数据
return -1; //为空返回1
}
//出栈
int popSeqStack(seqstack_t *p)
{
//1. 判空
if (isEmptySeqStack(p))
{
printf("popSeqStack:isEmptySeqStack\n");
return -1;
}
//2. 往下移动栈针top
p->top--;
//3. 将栈顶数据出栈
return p->data[p->top + 1];
}
//清空栈
void clearSeqStack(seqstack_t *p)
{
p->top = -1;
}
//求栈的长度
int lengthSeqStack(seqstack_t *p)
{
return p->top + 1;
}
int main(int argc, char const *argv[])
{
seqstack_t *p = createEmptySeqStack(6);
for (int i = 1; i <= 7; i++)
pushStack(p, i); //最后满了 pushStack: isFullSeqStack
printf("top is: %d\n", getTopSeqStack(p)); //6
while (!isEmptySeqStack(p))
printf("pop: %d\n", popSeqStack(p));
return 0;
3.3链式栈
3.3.1特性
逻辑结构:线性结构
存储结构:链式存储
顺序栈和链式栈的区别是: 存储结构不同,实现的方式也不同,顺序栈用顺序表实现而链栈用链表实现。
操作:创建、入栈、出栈、清空、获取
3.3.2函数实现
#include <stdio.h>
#include <stdlib.h>
typedef int datatype;
typedef struct linkstack
{
datatype data;
struct linkstack *next;
} linkstack_t;
//创建一个空栈
void createEmptyLinkStac(linkstack_t **ptop) //ptop = &top
{
*ptop = NULL; //*ptop= *&top=top
}
//入栈,data是入栈数据
//参数上之所以采用二级指针,因为我们随着入栈添加新的节点作为头,top需要永远指向当前链表的头。那么修改main函数中的top,我们能采用地址传递。
int pushLinkStack(linkstack_t **ptop, datatype data)
{
//1.新建一个节点,来保存入栈数据
linkstack_t *pnew = (linkstack_t *)malloc(sizeof(linkstack_t));
if (NULL == pnew)
{
perror("pushLinkStack pnew malloc err");
return -1;
}
pnew->data = data;
pnew->next = NULL;
//2.将新节点连入老链表(新的连接老的)
pnew->next = *ptop; //*ptop就是函数外的栈针top,因为ptop是&top
//3.移动栈针到新节点, 保证栈针top永远指向栈顶节点
*ptop = pnew;
return 0;
}
//判断栈是否为空
int isEmptyLinkStack(linkstack_t *top)
{
return top==NULL;
}
//出栈
datatype popLinkStack(linkstack_t **ptop)
{
//1. 判空
if(isEmptyLinkStack(*ptop))
{
printf("popLinkStack:isEmptyLinkStack\n");
return 0;
}
//2. 定义一个pdel指针指向栈顶节点也就是要删除的节点
linkstack_t *pdel=*ptop;
//3. 定义一个临时变量,保存要出栈数据,也就是栈顶节点中的数据
datatype m=(*ptop)->data;
//4. 跨过栈顶节点,也就是将栈针向后移动一个单位
*ptop=(*ptop)->next;
//5. 释放删除节点
free(pdel);
//6. 返回出栈数据
return m;
}
//清空栈
void clearLinkStack(linkstack_t **ptop) //用二级指针,是因为清空后需要将main函数中的top变为NULL
{
while (!isEmptyLinkStack(*ptop))
{
printf("%d ",popLinkStack(ptop));
}
printf("\n");
}
//获取栈顶数据,不是出栈,不需要移动main函数中的top,所以用一级指针
datatype getTopLinkStack(linkstack_t *top)
{
if(!isEmptyLinkStack(top))
return top->data;
return -1;
}
//求长度
int lengthLinkStack(linkstack_t *top) //用一级指针,是因为只是求长度,不需要修改main函数中top指针的指向
{
int len=0;
while (top!=NULL)
{
top=top->next;
len++;
}
return len;
}
int main(int argc, char const *argv[])
{
linkstack_t *top;
createEmptyLinkStac(&top);
pushLinkStack(&top,1);
pushLinkStack(&top,2);
pushLinkStack(&top,3);
printf("top is:%d, len is:%d\n",getTopLinkStack(top),lengthLinkStack(top));
printf("%d\n",popLinkStack(&top));
clearLinkStack(&top);
printf("is empty? %d\n",isEmptyLinkStack(top));
return 0;
}
4.队列
4.1特性
队列是只允许再两端进行插入和删除操作的线性表,在队尾插入,在队头删除,插入的一段被称为“队尾”,删除的一端被称为“队头”。队列包括顺序队列(循环队列)、链式队列。
结构:先进先出FIFO
操作:创建、入列、出列、判空满、清空
注意:为了避免假溢出问题,即队列前面还有空闲,但是队尾已经出现越界,所以在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进,需要引入循环队列。
循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
4.2循环队列
4.2.1特性
逻辑结构:线性结构
存储结构:顺序存储
操作:创建、入列、出列、判空满、清空
4.2.2函数实现
#include <stdio.h>
#include <stdlib.h>
#define N 6
typedef int datatype;
typedef struct
{
datatype data[N]; //循环队列的数组
int rear; //存数据端 rear 后面
int front; //取数据端 front 前面
} sequeue_t;
//创建一个空的队列
sequeue_t *createEmptySequeue()
{
//1.开辟队列结构体大小空间
sequeue_t *p = (sequeue_t *)malloc(sizeof(sequeue_t));
if (NULL == p)
{
perror("p malloc err");
return NULL;
}
//2.对空间初始化
p->front = 0; //使用时是表示数组下标
p->rear = 0;
return p;
}
//判断队列是否为满
//思想:舍去一个数组的存储空间为了判满,因为如果动存入数据则和空区分不了,所以用上一个状态当成满。
int isFullSequeue(sequeue_t *p)
{
//用rear的下一个位置判断是否等于front,满足则为满
return (p->rear + 1) % N == p->front;
}
//入列 data代表入列的数据
int inSequeue(sequeue_t *p, datatype data)
{
//1.判满
if (isFullSequeue(p))
{
printf("isFullSequeue !\n");
return -1;
}
//2.将数据入列
p->data[p->rear] = data;
//3.将队尾向后移动一个单位
p->rear = (p->rear + 1) % N;
return 0;
}
//判断队列是否为空
int isEmptySequeue(sequeue_t *p)
{
return p->rear == p->front;
}
//出列
datatype outSequeue(sequeue_t *p)
{
//1. 判空
if (isEmptySequeue(p))
{
printf("outSequeue err:\n");
return -1;
}
//2. 定义一个临时变量保存要出列数据
datatype ret = p->data[p->front];
//3. 移动队头
p->front = (p->front + 1) % N;
//4. 将数据出列
return ret;
}
//求队列的长度
int lengthSequeue(sequeue_t *p)
{
//长度情况分为 rear >= front 和 rear < front
//rear == front 就是空队列长度为0
if (p->rear >= p->front)
return p->rear - p->front;
else
return p->rear - p->front + N;
//或者一个公式: return (p->rear - p->front + N) % N;
}
//清空队列函数
void clearSequeue(sequeue_t *p)
{
while (!isEmptySequeue(p))
printf("%d ",outSequeue(p));
printf("\n");
}
int main(int argc, char const *argv[])
{
sequeue_t *p = createEmptySequeue();
for(int i=1;i<=6;i++)
inSequeue(p,i); //只能装5个数,最后会报isFullSequeue !
printf("len is: %d\n", lengthSequeue(p));
printf("%d\n", outSequeue(p)); //1
clearSequeue(p); //2 3 4 5
return 0;
}
4.3链式队列
4.3.1特性
逻辑结构: 线性结构
存储结构:链式存储
操作:创建、入列、出列、判空、清空
4.3.2函数实现
#include<stdio.h>
#include<stdlib.h>
typedef int datatype;
typedef struct node
{
datatype data;
struct node *next;
}linkqueue_node_t, * linkqueue_list_t;
typedef struct queue //把头尾指针封装到一个结构体里
{
linkqueue_list_t front; //相当于队列的头指针
linkqueue_list_t rear; //相当于队列的尾指针
//有了链表的头指针和尾指针,那么我们就可以操控这个链表。
}linkqueue_t;
//创建一个空的队列,用有头链表。
linkqueue_t *createEmptyLinkQueue()
{
//1. 开辟队列结构体大小空间
linkqueue_t *p = (linkqueue_t *)malloc(sizeof(linkqueue_t));
if(NULL==p)
{
perror("createEmptyLinkQueue malloc p err");
return NULL;
}
//2. 初始化队列空间,将头尾指针都要指向头节点,所以要开辟一个头节点。
p->front = p->rear = (linkqueue_list_t)malloc(sizeof(linkqueue_node_t));
if(NULL==p->front)
{
perror("malloc p->front err");
return NULL;
}
//3. 对头节点进行初始化
p->front->next=NULL;
return p;
}
//入列 data代表入列的数据
int inLinkQueue(linkqueue_t *p, datatype data)
{
//1. 创建一个新的节点,用来保存即将入队的数据。然后初始化新节点。
linkqueue_list_t pnew = (linkqueue_list_t)malloc(sizeof(linkqueue_node_t));
if(NULL==pnew)
{
perror("pnew malloc err");
return -1;
}
pnew->data=data;
pnew->next=NULL;
//2. 将新节点链接到链表的尾巴。(通过尾指针)
p->rear->next=pnew;
//3. 移动尾指针到新节点,保证尾指针永远指向当前链表的尾巴。
p->rear=pnew;
return 0;
}
//判断队列是否为空
int isEmptyLinkQueue(linkqueue_t *p)
{
return p->front==p->rear;
}
//出列
//思想:每次释放front所指节点,然后移动front到后一个节点返回当前节点数据
datatype outLinkQueue(linkqueue_t *p)
{
//1. 判空
if(isEmptyLinkQueue(p))
{
printf("outLinkQueue:isEmptyLinkQueue!\n");
return -1;
}
//2. 定义一个pdel指针保存头指针,也就是头节点的地址
linkqueue_list_t pdel = p->front;
//3. 移动头指针到后一个单位
p->front = p->front->next;
//4. 释放此时头节点(需要做一个新的头节点出来)
free(pdel);
pdel=NULL;
//5. 返回此时头指针所指节点数据(返回之后,此节点变成头节点了,因为数据域已经无效了)
return p->front->data;
}
//求队列长度的函数
int lengthLinkQueue(linkqueue_t *p)
{
int len=0;
linkqueue_list_t temp = p->front; //将链表头指针的地址保存给temp,因为不能移动头指针。
while (temp->next !=NULL) //求长度,相当于遍历有头单向链表
{
temp=temp->next;
len++;
}
return len;
}
//清空队列
void clearLinkQueue(linkqueue_t *p)
{
while (!isEmptyLinkQueue(p))
{
printf("%d ",outLinkQueue(p));
}
printf("\n");
}
int main(int argc, char const *argv[])
{
linkqueue_t *p=createEmptyLinkQueue();
for(int i=1;i<6;i++)
inLinkQueue(p,i);
printf("len=%d\n",lengthLinkQueue(p));
clearLinkQueue(p); //1 2 3 4 5
return 0;
}