从零开始,快速了解数据结构(纯小白)

 一、宏观了解数据结构

数据结构,就是数据逻辑结构以及存储操作

数据结构没想象的那么复杂,它就教会你一件事:如何有效的存储数据

既然要了解结构数据,那必然要知道:数据结构+算法=程序

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的方法
  1. 根据问题规模n写出表达式f(n)
  2. 如果有常数项,将其置为1    //当f(n)的表达式中只有常数项的时候,例如f(n)=8 ==> O(1)
  3. 只保留最高项,其他项舍去。
  4. 如果最高项系数不为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 *= (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 *= 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 *= (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;

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花田里的泪光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值