数据结构(整理常见结构总结到树层级)

大纲()

数据结构、算法(理解)

线性表:顺序表(数组)、链表(单向链表、单向循环链表、双向链表、双向循环链表)、栈(顺序栈、链式栈)、队列(循环队列、链式队列)

树:特性、二叉树(特性、创建、遍历)

排序方法、查询方法(原理、思路)

为什么要学数据结构

  1. C语言如何写程序

学数据结构是为了简洁、高效的写程序

  1. 如果遇到了一个实际问题,需要写代码实现相应功能,需要解决两个方面问题:
  1. 如何表达数据之间的逻辑关系以及存储操作

数据结构:数据的逻辑结构以及存储操作(类似数据的运算)

数据:不是指的单纯的数字,而是类似于集合的概念。

结构:数据之间的关系。

  1. 采用什么方法解决?

采用算法去解决

==>数据结构+算法=程序

问题--> 数据结构 + 算法 = 程序 --> 解决问题

1.数据结构基础知识

1.1 什么是数据结构

数据的逻辑结构以及存储操作

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

1.2 数据

数据:不再是单纯的数字,而是类似于集合的概念。

数据元素:是数据的基本单位,由若干个数据项组成。

数据项:数据的最小单位,描述数据元素有用的信息。

数据元素又叫节点

例如:

计算机处理的对象(数据)已不再是单纯的数值:

图书管理中的数据,如下表所列:

数据:图书

数据元素:每一本书

数据项: 编号、书名、作者、出版社、出版日期等

1.3 逻辑结构

数据元素并不是孤立存在的,它们之间存在着某种关系(或联系、结构)。元素和元素之间的关系:

  • 线性关系

线性结构 ==> 一对一 ==> 线性表: 顺序表、链表、栈、队列

  • 层次关系

树形结构 --> 一对多 --> 树:二叉树

  • 网状关系

图状结构 --> 多对多 --> 图

例题:

田径比赛的时间安排问题

本题概述:

1.4 存储结构

数据的逻辑结构在计算机中的具体实现(数据的运算)

1.4.1 顺序存储

数组:连续存储

特点:内存连续、随机存取、每个元素占用空间较少

1.4.2 链式存储

通过指针存储

特点:内存不连续,通过指针实现。

链表实现:

封装结构体:

复制代码
#include <stdio.h>`

`// 链表节点结构体`
`struct node_t`
`{`
`    int data;            // 数据域:存放节点数据`
`    struct node_t *next; // 指针域:结构体指针指向下一个节点(自身结构体类型指针)`
`};`

`int main(int argc, char const *argv[])`
`{`
`    // 定义3个节点`
`    struct node_t A = {1, NULL};`
`    struct node_t B = {2, NULL};`
`    struct node_t C = {3, NULL};`
    
`    // 连接3个节点`
`    A.next=&B;  //让A和B相连,通过让A节点中的指针域保存B节点的地址`
`    B.next=&C;`

`    printf("%d\n",A.data);`
`    printf("%d\n", A.next->data);`
`    printf("%d\n",A.next->next->data);`

`    return 0;`
`}

1.4.3 索引存储

在存数据的同时,建立一张附加的索引表。

也就是索引存储 = 索引表 + 数据文件

可以提高查找速度,特点检索速度快,但是占用内存多,删除数据文件时要及时更改索引表。

1.4.4 散列存储

数据存储按照和关键码之间的关系进行存取。关系由自己决定,比如关键码是key, 存储的位置也就是关系key+1。获取关键数据,通过元素的关键码方法的返回值来获取。

存的时候按关系存

取的时候按关系取

1.5 操作

增删改查

2.算法基础知识

2.1 什么是算法

算法就是解决问题的思想方法,数据结构就是算法的基础。

数据结构 + 算法 = 程序

2.2 算法的设计

算法的设计:取决于数据逻辑结构

算法的实现:依赖于数据的存储结构

2.3 算法的特点

有穷性:步骤是有限的

确定性:每一个步骤都有明确的含义,无二义性

可行性: 规定时间可以完成

输入

输出

2.4 评价算法的好坏

正确性

易读性

健壮性:容错处理

高效性:执行效率,通过重复执行语句次数来判断,也就是时间复杂度(通过时间处理函数)来判断。

时间复杂度:

语句频度:用时间规模函数表达式

时间规模函数:T(n)=O(f(n))

T(n) //时间规模的函数

O //时间数量级

n //问题规模,例如:int a[100],n=100

f(n) //算法可执行重复语句的次数

称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

渐进时间复杂度用大写O来表示,所以也被称为大O表示法。直白的讲,时间复杂度就是把时间规模函数T(n)简化为一个数量级,如n,n^2,n^3。

例1:

求1+2+3+...+n的和

算法1:

复制代码
int sum=0;for(int` `i=1;i<=n;` `i++)`
`{`
    `sum +=i;`
`}

f(n)=n

==>T(n)=O(n)

算法2:

利用等差数列前n项和公式:Sn=n*a1+n(n-1)d/2 或 Sn=n(a1+an)/2 (d是公差)

复制代码
int sum=(1+n)*n/2;

f(n)=1

==>T(n)=O(1)

例2:

复制代码
int i,j;`
`for(i=0;i<n;i++)`
`{`
`	 for(j=0;j<n;j++)`
`    {`
`	    printf("ok\n");          `
`}

T(n)=O(n^2)

例3:

复制代码
int i,j;`
`for(i=0;i<n;i++)`
`{`
`	 for(j=0;j<=i;j++)`
`    {`
`	    printf("ok\n");   //1 + ` `2 + 3 ... +n`
    `}`
`}

f(n) =1+2+3+...+n = n*(1+n)/2 = n/2 + n^2/2 //只保留最高项n^2/2,除以最高项系数得到n^2

T(n)=O(n^2)

计算大O的方法

  1. 根据问题规模n写出表达式f(n)
  2. 如果有常数项,将其置为1 //当f(n)的表达式中只有常数项,例如f(n)=8 ==>O(1)
  3. 只保留最高项,其他项舍去。
  4. 如果最高项系数不为1,则除以最高项系数。

f(n) = 3*n^4 + 2*n^3 + 6*n^7 +10;

==> O(n^7)

3.线性表

线性表是最基本、最简单、也是最常用的一种数据结构,可以存储逻辑关系为线性的数据。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。

包含:顺序表(数组)、链表(单向链表、单向循环链表、双向链表、双循环链表)、栈(顺序栈、链式栈)、队列(循环队列、链式队列)

逻辑结构:线性结构

存储结构:顺序存储(数组)或链式存储(通过指针)

特点:一对一,每个节点最多一个前驱和一个后继,首节点无前驱,尾节点无后继。

3.1 顺序表

顺序表存储数据的具体实现方案是:将数据全部存储到一整块内存空间中,数据元素之间按照次序挨个存放。

举个简单的例子,将 {1,2,3,4,5} 这些数据使用顺序表存储,数据最终的存储状态如下图所示:

3.1.1 顺序表的特性

特点:内存连续,用数组实现

逻辑结构:线性结构

存储结构: 顺序存储结构

操作:增删改查

3.1.2 操作数组

例题:

int a[100]={1,2,3,4,5,6,7,8};

函数命名规则:

下划线法:create_empty_seqlist

小驼峰法:createEmptySeqList

大驼峰法:CreateEmptySeqList

复制代码
#include <stdio.h>`

`/*  (1)插入元素`
`    功能:向数组的第几个位置插数据`
`    函数:void insertIntoA(int *p,int n, int post, int data);`
`    参数:`
`    int *p: 保存数组首地址`
`    int n: 有效数据元素的个数`
`    int post: 插入元素下标`
`    int data: 数据`
`*/`
`void insertIntoA(int *p, int n, int post, int data) // p=a, n=8, post=4, data=100`
`{`
`    int i;`
`    // 1.把从最后一个元素到插入位置post元素往后移动一个单位(后一个赋值为前一个元素)`
`    for (i = n - 1; i >= post; i--) // i=3  3>=4`
`        p[i + 1] = p[i];            // p[5]=p[4]`
`    // 2.把元素放到指定位置(赋值)`
`    p[post] = data; // p[4]=100  ==> 1 2 3 4 100 5 6 7 8`
`}`

`/* (2) 遍历数组`
`    功能:遍历数组中的有效元素`
`    函数:void showA(int *p,int n);`
`    参数:`
`    int *p:保存数组首地址`
`    int n:有效数据元素的个数`
`*/`
`void showA(int *p, int n)`
`{`
`    for (int i = 0; i < n; i++)`
`        printf("%d ", p[i]);`
`    printf("\n");`
`}`

`/*(3)删除数组元素`
`    函数:void deleteIntoA(int *p,int n, int post);`
`    参数:`
`    int *p: 保存数组首地址`
`    int n: 有效数据元素的个数`
`    int post: 删除元素下标`
`*/`
`void deleteIntoA(int *p, int n, int post) // p=a, n=9, post=4`
`{`
`    int i;`
`    // 1.从删除位置后一个到最后一个有效元素往前移动一个单位(前一个元素赋值为后一个)`
`    for (i = post + 1; i <= n - 1; i++)//i=8 8<=8`
`        p[i - 1] = p[i];  //p[7]=p[8]`
`    //1 2 3 4 5 6 7 8 8`
`}`

`int main(int argc, char const *argv[])`
`{`
`    int a[100] = {1, 2, 3, 4, 5, 6, 7, 8};`
`    insertIntoA(a, 8, 4, 100);`
`    showA(a, 9);`
`    deleteIntoA(a, 9, 4);`
`    showA(a, 8);`
`    return 0;`
`}`
`

3.1. 3 添加全局变量last表示最后一个有效元素下标

复制代码
#include <stdio.h>`
`int last = 7; // last表示最后一个有效元素下标,也就是n-1`

`/*  (1)插入元素`
`    功能:向数组的第几个位置插数据`
`    函数:void insertIntoA(int *p,int post, int data);`
`    参数:`
`    int *p: 保存数组首地址`
`    int post: 插入元素下标`
`    int data: 数据`
`*/`
`void insertIntoA(int *p, int post, int data)`
`{`
`    int i;`
`    // 1.把从最后一个元素到插入位置post元素往后移动一个单位(后一个赋值为前一个元素)`
`    for (i = last; i >= post; i--)`
`        p[i + 1] = p[i];`
`    // 2.把元素放到指定位置(赋值)`
`    p[post] = data;`
`    // 3.最后一个有效元素下标last加一`
`    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)`
`{`
`    int i;`
`    // 1.从删除位置后一个到最后一个有效元素往前移动一个单位(前一个元素赋值为后一个)`
`    for (i = post + 1; i <= last; i++)`
`        p[i - 1] = p[i];`
`    // 2.最后一个有效元素下标last减一`
`    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;`
`}

3.1.4 顺序表编程实现

封装顺序表结构

复制代码
#define N 10    //顺序表长度`

`//封装顺序表结构体`
`typedef struct seqlist`
`{`
`    int data[N]; //存数据的数组`
`    int last;   //表示数组中最后一个有效元素下标`
`} seqlist_t, *seqlist_p;`
`//给struct seqlist类型重定义为seqlist_t`
`//同时给struct seqlist *类型重定义为seqlist_p`
`// 此时:`
`// seqlist_t 等同于 struct seqlist`
`// seqlist_p 等同于 struct seqlist*

代码实现:

复制代码
#include <stdio.h>`
`#include <stdlib.h>`

`#define N 10 // 顺序表长度`
`// 封装顺序表结构体`
`typedef struct seqlist`
`{`
`    int data[N]; // 存数据的数组`
`    int last;    // 表示数组中最后一个有效元素下标`
`} seqlist_t, *seqlist_p;`

`// 创建一个空顺序表 create创造 empty空的 seqlist顺序表`
`seqlist_p CreateEpSeqlist()`
`{`
`    // 1.开辟一个顺序表结构体大小空间`
`    seqlist_p p = (seqlist_p)malloc(sizeof(seqlist_t));`
`    if (NULL == p) // 容错处理,开辟空间失败就会报错误提示`
`    {`
`        perror("create malloc err"); // 打印上一个函数的错误信息`
`        return NULL;                 // 错误就结束函数, 因为是指针函数所以返回NULL`
`    }`
`    // 2.对结构体空间初始化`
`    p->last = -1; // 下标等于个数-1,此时有效元素个数为0所以下标是0-1=-1`

`    // 3.返回结构体空间的地址`
`    return p;`
`}`

`// 判断顺序表是否为满,满返回1,未满返回0  full:满了`
`int IsFullSeqlist(seqlist_p p)`
`{`
`    return p->last == N - 1;`
`}`

`// 向顺序表的指定位置插入数据   insert:插入`
`int InsertIntoSeqlist(seqlist_p p, int post, int data) // 第一次调用:post=0, data=1`
`{`
`    // 容错判断`
`    if (IsFullSeqlist(p) || post < 0 || post > p->last + 1)`
`    {`
`        printf("InsertIntoSeqlist err\n");`
`        return -1; // 错误退出`
`    }`
`    // 从最后一个元素开始到插入位置元素往后移动一个单位`
`    for (int i = p->last; i >= post; i--) // i=-1,-1>=0`
`        p->data[i + 1] = p->data[i];`
`    // 放入数据到post元素`
`    p->data[post] = data; // p->data[0]=1;`
`    // 让最后一个有效元素下标加一`
`    p->last++;`
`    return 0; // 正常退出`
`}`

`// 遍历顺序表sequence顺序表`
`void ShowSeqlist(seqlist_p p)`
`{`
`    for (int i = 0; i <= p->last; i++)`
`        printf("%d ", p->data[i]);`
`    printf("\n");`
`}`

`// 判断顺序表是否为空,为空返回1,不为空返回0`
`int IsEpSeqlist(seqlist_p p)`
`{`
`    return p->last == -1;`
`}`

`// 删除顺序表中指定位置的数据,post为删除位置`
`int DeleteIntoSeqlist(seqlist_p p, int post)`
`{`
`    // 1.容错判断`
`    if (IsEpSeqlist(p) || post < 0 || post > p->last)`
`    {`
`        printf("DeleteIntoSeqlist err\n");`
`        return -1;`
`    }`
`    // 2.从删除位置后一个开始到最后一个元素往前移动一个单位`
`    for (int i = post + 1; i <= p->last; i++) // i=4,4<=3`
`        p->data[i - 1] = p->data[i];`
`    // 3.让最后一个有效元素下标减一`
`    p->last--;`
`    return 0;`
`}`

`// 查找指定数据出现的位置,返回下标,未找到返回-1`
`int SearchDataSeqList(seqlist_p p, int data)`
`{`
`    for (int i = 0; i <= p->last; i++)`
`    {`
`        if (p->data[i] == data)`
`            return i;`
`    }`
`    return -1; // 如果没找到返回-1`
`}`

`// 修改指定位置上数据`
`int ChangePostSeqList(seqlist_p p, int post, int data)`
`{`
`    // 1.容错判断`
`    if (IsEpSeqlist(p) || post < 0 || post > p->last)`
`    {`
`        printf("ChangePostSeqList err\n");`
`        return -1;`
`    }`
`    // 2.修改post数据`
`    p->data[post] = data;`

`    return 0; // 正常退出`
`}`

`// 清空顺序表 (清空是变为空顺序表,而不是销毁内存)`
`void ClearSeqList(seqlist_p p)`
`{`
`    p->last = -1;`
`}`

`int main(int argc, char const *argv[])`
`{`
`    seqlist_p p = CreateEpSeqlist();`
`    InsertIntoSeqlist(p, 0, 1);`
`    InsertIntoSeqlist(p, 1, 2);`
`    InsertIntoSeqlist(p, 2, 3);`
`    InsertIntoSeqlist(p, 1, 100);`
`    ShowSeqlist(p);`
`    DeleteIntoSeqlist(p, 3);`
`    ShowSeqlist(p);`
`    printf("10 post is: %d\n", SearchDataSeqList(p, 10));`
`    ChangePostSeqList(p, 1, 20);`
`    ShowSeqlist(p);`
`    ClearSeqList(p);`
`    if (IsEpSeqlist(p))`
`    {`
`        printf("is empty!\n");`
`    }`
`    return 0;`
`}

3.1.5 顺序表分文件编程

seqlist.h

复制代码
#ifndef _SEQLIST_H_`
`#define _SEQLIST_H_`

`#define N 10 // 顺序表长度`
`// 封装顺序表结构体`
`typedef struct seqlist`
`{`
`    int data[N]; // 存数据的数组`
`    int last;    // 表示数组中最后一个有效元素下标`
`} seqlist_t, *seqlist_p;`

`// 创建一个空顺序表 create创造 empty空的 seqlist顺序表`
`seqlist_p CreateEpSeqlist();`

`// 判断顺序表是否为满,满返回1,未满返回0  full:满了`
`int IsFullSeqlist(seqlist_p p);`

`// 向顺序表的指定位置插入数据   insert:插入`
`int InsertIntoSeqlist(seqlist_p p, int post, int data); // 第一次调用:post=0, data=1`

`// 遍历顺序表sequence顺序表`
`void ShowSeqlist(seqlist_p p);`

`// 判断顺序表是否为空,为空返回1,不为空返回0`
`int IsEpSeqlist(seqlist_p p);`

`// 删除顺序表中指定位置的数据,post为删除位置`
`int DeleteIntoSeqlist(seqlist_p p, int post);`

`// 查找指定数据出现的位置,返回下标,未找到返回-1`
`int SearchDataSeqList(seqlist_p p, int data);`

`// 修改指定位置上数据`
`int ChangePostSeqList(seqlist_p p, int post, int data);`

`// 清空顺序表 (清空是变为空顺序表,而不是销毁内存)`
`void ClearSeqList(seqlist_p p);`

`#endif

seqlist.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "seqlist.h"`

`// 创建一个空顺序表 create创造 empty空的 seqlist顺序表`
`seqlist_p CreateEpSeqlist()`
`{`
`    // 1.开辟一个顺序表结构体大小空间`
`    seqlist_p p = (seqlist_p)malloc(sizeof(seqlist_t));`
`    if (NULL == p) // 容错处理,开辟空间失败就会报错误提示`
`    {`
`        perror("create malloc err"); // 打印上一个函数的错误信息`
`        return NULL;                 // 错误就结束函数, 因为是指针函数所以返回NULL`
`    }`
`    // 2.对结构体空间初始化`
`    p->last = -1; // 下标等于个数-1,此时有效元素个数为0所以下标是0-1=-1`

`    // 3.返回结构体空间的地址`
`    return p;`
`}`

`// 判断顺序表是否为满,满返回1,未满返回0  full:满了`
`int IsFullSeqlist(seqlist_p p)`
`{`
`    return p->last == N - 1;`
`}`

`// 向顺序表的指定位置插入数据   insert:插入`
`int InsertIntoSeqlist(seqlist_p p, int post, int data) // 第一次调用:post=0, data=1`
`{`
`    // 容错判断`
`    if (IsFullSeqlist(p) || post < 0 || post > p->last + 1)`
`    {`
`        printf("InsertIntoSeqlist err\n");`
`        return -1; // 错误退出`
`    }`
`    // 从最后一个元素开始到插入位置元素往后移动一个单位`
`    for (int i = p->last; i >= post; i--) // i=-1,-1>=0`
`        p->data[i + 1] = p->data[i];`
`    // 放入数据到post元素`
`    p->data[post] = data; // p->data[0]=1;`
`    // 让最后一个有效元素下标加一`
`    p->last++;`
`    return 0; // 正常退出`
`}`

`// 遍历顺序表sequence顺序表`
`void ShowSeqlist(seqlist_p p)`
`{`
`    for (int i = 0; i <= p->last; i++)`
`        printf("%d ", p->data[i]);`
`    printf("\n");`
`}`

`// 判断顺序表是否为空,为空返回1,不为空返回0`
`int IsEpSeqlist(seqlist_p p)`
`{`
`    return p->last == -1;`
`}`

`// 删除顺序表中指定位置的数据,post为删除位置`
`int DeleteIntoSeqlist(seqlist_p p, int post)`
`{`
`    // 1.容错判断`
`    if (IsEpSeqlist(p) || post < 0 || post > p->last)`
`    {`
`        printf("DeleteIntoSeqlist err\n");`
`        return -1;`
`    }`
`    // 2.从删除位置后一个开始到最后一个元素往前移动一个单位`
`    for (int i = post + 1; i <= p->last; i++) // i=4,4<=3`
`        p->data[i - 1] = p->data[i];`
`    // 3.让最后一个有效元素下标减一`
`    p->last--;`
`    return 0;`
`}`

`// 查找指定数据出现的位置,返回下标,未找到返回-1`
`int SearchDataSeqList(seqlist_p p, int data)`
`{`
`    for (int i = 0; i <= p->last; i++)`
`    {`
`        if (p->data[i] == data)`
`            return i;`
`    }`
`    return -1; // 如果没找到返回-1`
`}`

`// 修改指定位置上数据`
`int ChangePostSeqList(seqlist_p p, int post, int data)`
`{`
`    // 1.容错判断`
`    if (IsEpSeqlist(p) || post < 0 || post > p->last)`
`    {`
`        printf("ChangePostSeqList err\n");`
`        return -1;`
`    }`
`    // 2.修改post数据`
`    p->data[post] = data;`

`    return 0; // 正常退出`
`}`

`// 清空顺序表 (清空是变为空顺序表,而不是销毁内存)`
`void ClearSeqList(seqlist_p p)`
`{`
`    p->last = -1;`
`}`
`

main.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include"seqlist.h"`

`int main(int argc, char const *argv[])`
`{`
`    seqlist_p p = CreateEpSeqlist();`
`    InsertIntoSeqlist(p, 0, 1);`
`    InsertIntoSeqlist(p, 1, 2);`
`    InsertIntoSeqlist(p, 2, 3);`
`    InsertIntoSeqlist(p, 1, 100);`
`    ShowSeqlist(p);`
`    DeleteIntoSeqlist(p, 3);`
`    ShowSeqlist(p);`
`    printf("10 post is: %d\n", SearchDataSeqList(p, 10));`
`    ChangePostSeqList(p, 1, 20);`
`    ShowSeqlist(p);`
`    ClearSeqList(p);`
`    if (IsEpSeqlist(p))`
`    {`
`        printf("is empty!\n");`
`    }`
`    return 0;`
`}

makefile(可以快速编译你的文件)

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=seqlist.o main.o`

`seqlist:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) seqlist *.o

顺序表总结:

  1. 顺序表内存空间连续
  2. 查找和修改效率高,增删设计到元素的移动效率相对低。
  3. 顺序表长度固定
  4. 空间占用相对较少

3.2 顺序栈

3.2.1 什么是栈 stack

栈是只能在一端进行插入和删除操作的线性表(又称为堆栈),进行插入和删除操作的一端称为栈顶,另一端称为栈底。

特点:先进后出(first in last out) FILO

或者后进先出LIFO

3.2.2 顺序栈的特点

逻辑结构:线性结构

存储结构:顺序存储结构

操作:创建、入栈、出栈、判空、判满

3.2.3 代码实现

封装结构体:其中包含指向数组空间的指针data、数组最大长度maxlen、栈顶节点下标top(类似于之前的last)

复制代码
typedef int datatype;  //封装存入数据的数据类型`
`typedef struct seqstack`
`{`
`    datatype *data;  // 指向数组第一个元素的指针`
`    int maxlen; // 保存栈的最大长度(数组元素个数)`
`    int top;    // 栈顶节点的下标,也可以叫栈针,和之前学的普通顺序表中的last是一样。`
`} seqstack_t, *seqstack_p;

创空:

入栈:

seqstack.h

复制代码
#ifndef _SEQSTACK_H_`
`#define _SEQSTACK_H_`
`typedef int datatype; // 封装存入数据的数据类型`
`typedef struct seqstack`
`{`
`    datatype *data; // 指向数组第一个元素的指针`
`    int maxlen;     // 保存栈的最大长度(数组元素个数)`
`    int top;        // 栈顶节点的下标,也可以叫栈针,和之前学的普通顺序表中的last是一样。`
`} seqstack_t, *seqstack_p;`

`// 1.创建一个空栈,len代表创建栈时的最大长度。`
`seqstack_p createEmptySeqStack(int len);`
`// 2.判断是否为满,满返回1 未满返回0`
`int isFullSeqStack(seqstack_p p);`
`// 3.入栈,data代表入栈的数据`
`int pushStack(seqstack_p p, int data);`
`// 4.判断栈是否为空`
`int isEmptySeqStack(seqstack_p p);`
`// 5.出栈,返回出栈数据`
`int popSeqStack(seqstack_p p);`

`#endif

seqstack.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "seqstack.h"`

`// 创建一个空栈,len代表创建栈时的最大长度。`
`seqstack_p createEmptySeqStack(int len)`
`{`
`    // 1.开辟结构体大小空间`
`    seqstack_p p = (seqstack_p)malloc(sizeof(seqstack_t));`
`    if (NULL == p)`
`    {`
`        perror("create malloc err");`
`        return NULL;`
`    }`
`    // 2.初始化结构体空间`
`    p->data = (datatype *)malloc(sizeof(datatype) * len); // 创建一个数组空间,并且把数组的地址赋值给data`
`    if (NULL == p->data)`
`    {`
`        perror("p->data malloc err");`
`        return NULL;`
`    }`
`    p->maxlen = len; // len是数组最大长度`
`    p->top = -1;     // 类似于之前的last,因为此时有效元素个数为0,所以下标是0-1=-1`

`    return p;`
`}`

`// 判断是否为满,满返回1 未满返回0`
`int isFullSeqStack(seqstack_p p)`
`{`
`    return p->top == p->maxlen - 1;`
`}`

`// 入栈,data代表入栈的数据`
`int pushStack(seqstack_p p, int data)`
`{`
`    // 1.容错判断:判满`
`    if (isFullSeqStack(p))`
`    {`
`        printf("push err\n");`
`        return -1;`
`    }`
`    // 2.将栈针向上移动一个单位`
`    p->top++;`

`    // 3.将数据存入`
`    p->data[p->top]=data;`

`    return 0;`
`}`

`//判断栈是否为空`
`int isEmptySeqStack(seqstack_t *p)`
`{`
`    return p->top == -1;`
`}`

`//出栈,返回出栈数据`
`int popSeqStack(seqstack_p p)`
`{`
`    //1.容错判断:判空`
`    if(isEmptySeqStack(p))`
`    {`
`        printf("pop err\n");`
`        return -1;`
`    }`
`    //2.将栈针向下移动一个单位`
`    p->top--;`
`    //3.返回要出栈数据`
`    return p->data[p->top+1];`
`}`
`

main.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "seqstack.h"`

`int main(int argc, char const *argv[])`
`{`
`    seqstack_p p = createEmptySeqStack(6);`
`    for (int i = 1; i <= 7; i++)`
`        pushStack(p, i); // 最后一次会报push err, 因为栈满了`

`    while (!isEmptySeqStack(p))`
`    {`
`        printf("%d ", popSeqStack(p)); //6 5 4 3 2 1 后进先出`
`    }`
`    printf("\n");`

`    return 0;`
`}`
`

3. 3 链表

链表又称单链表、链式存储结构,用于存储逻辑关系为"一对一"的数据。

和顺序表不同同,使用链表存储数据,不强制要求数据在内存中集中存储,各个元素可以分散存储在内存中。

链表存储数据间逻辑关系的实现方案是:为每一个元素配置一个指针,每个元素的指针都指向自己的直接后继元素,如下图所示:

所以在链表中,每个数据元素可以配有一个指针用于找到下一个元素即节点,这意味着,链表上的每个"元素"都长下图这个样子:

3. 3 .1 链表的特性

逻辑结构: 线性结构

存储结构:链式存储结构

特点:内存不连续,通过指针连接。

解决顺序表长度固定以及插入和删除效率低的问题。

操作: 增删改查

复制代码
struct node_t`
`{`
    `int` `data;//数据域存数据`
    `struct` `node_t *next;` `//指针域:存放下一个节点的地址`
`};

3. 3 .2 单向链表

有头链表:存在一个头节点,头节点的指针域有效,数据域无效。

无头链表: 每一个节点的数据域和指针域都有效。

遍历单向无头链表
复制代码
#include <stdio.h>`
`typedef struct node_t`
`{`
`    int data;            // 数据域:存数据`
`    struct node_t *next; // 指针域:存放下一个节点的地址`
`} link_node_t, *link_node_p;`

`int main(int argc, char const *argv[])`
`{`
`    // 1.定义4个节点`
`    link_node_t A = {1, NULL};`
`    link_node_t B = {2, NULL};`
`    link_node_t C = {3, NULL};`
`    link_node_t D = {4, NULL};`

`    // 2.连接4个节点`
`    A.next = &B;`
`    B.next = &C;`
`    C.next = &D;`

`    // 3.定义一个头指针指向第一个节点,用于遍历无头单向链表`
`    link_node_p p = &A;`
`    // 4.遍历无头单向链表`
`    while (p != NULL)`
`    {`
`        printf("%d ", p->data);`
`        p = p->next;`
`    }`
`    printf("\n");`

`    return 0;`
`}`
`
遍历有头单向链表
复制代码
#include <stdio.h>`

`typedef struct node_t`
`{`
`    int data;            // 数据域:存数据`
`    struct node_t *next; // 指针域:存放下一个节点的地址`
`} link_node_t, *link_node_p;`

`int main(int argc, char const *argv[])`
`{`
`    // 1.定义4个节点`
`    link_node_t A = {1, NULL};`
`    link_node_t B = {2, NULL};`
`    link_node_t C = {3, NULL};`
`    link_node_t D = {4, NULL};`

`    // 2.连接4个节点`
`    A.next = &B;`
`    B.next = &C;`
`    C.next = &D;`

`    // 3.建立一个头节点`
`    link_node_t H;`
`    H.next = &A; // 将头节点连接第一个节点A`

`    // 4.定义一个头指针,指向头节点`
`    link_node_p p = &H;`

`#if 0`
`    // 方法一:`
`    //  将头指针向后移动一个单位,相当于跨越头节点`
`    p = p->next;`
`    // 相当于遍历无头单向链表`
`    while (p != NULL)`
`    {`
`        printf("%d ", p->data);`
`        p = p->next;`
`    }`
`    printf("\n");`
`#else`
`    // 方法二:`
`    while (p->next != NULL)`
`    {`
`        p = p->next;`
`        printf("%d ", p->data);`
`    }`
`    printf("\n");`
`#endif`
`    return 0;`
`}`
`
链表尾插法练习

写一个有头单向链表,用于保存输入的学生成绩,实现一输入学生成绩就创建一个新的节点,将成绩保存起来。再将该节点链接到链表的尾,直到输入-1结束。

要求:每个链表的节点由动态内存分配得到 , 也就是用malloc。

过程:

  1. malloc申请空间link_node_t大小作为头节点
  2. 将新节点放到链表尾部
复制代码
#include <stdio.h>`
`#include <stdlib.h>`

`typedef struct node_t`
`{`
`    int data;`
`    struct node_t *next;`
`} link_node_t, *link_node_p;`

`int main(int argc, char const *argv[])`
`{`
`    link_node_p pnew = NULL;  // 创建pnew指针用于指向新建的节点`
`    link_node_p ptail = NULL; // 创建ptail指针用于指向尾节点`

`    int score;`
`    // 1.创建头节点,创建头指针p指向头节点`
`    link_node_p p = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == p)`
`    {`
`        perror("p malloc err");`
`        return -1;`
`    }`
`    // 初始化头节点`
`    p->next = NULL;`

`    // 2.让尾指针指向头节点`
`    ptail = p;`

`    // 3.循环输入学生成绩,创建节点保存成绩,将新节点连接到链表尾部。直到输入-1结束。`
`    while (1)`
`    {`
`        scanf("%d", &score);`
`        if (score == -1)`
`            break;`
`        //(1)malloc创建新节点,让pnew指向新节点`
`        pnew = (link_node_p)malloc(sizeof(link_node_t));`
`        if (NULL == pnew)`
`        {`
`            perror("pnew malloc err");`
`            return -1;`
`        }`
`        //(2)初始化新节点,也就是让新节点保存学生成绩并且指针域置空`
`        pnew->data = score;`
`        pnew->next = NULL;`
`        //(3)将新节点连接到链表尾部`
`        ptail->next = pnew;`
`        //(4)移动尾指针到新节点`
`        ptail = pnew;`
`    }`

`    // 4.遍历有头单向链表`
`    while (p->next != NULL)`
`    {`
`        p = p->next;`
`        printf("%d ", p->data);`
`    }`
`    printf("\n");`

`    return 0;`
`}`
`
有头链表的函数操作

创空

链表插入:

链表删除:

链表清空:

按数据删除:

思想:利用两个指针,保证p一直在q之前,q先指向头的后一个,相当于遍历无头链表,相当于即遍历又删除的功能。删除操作时p用来跨过q,删除完别忘了让q继续向后遍历。

转置:

//解题思想:

//(1) 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表

//(2) 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表头节点的下一个节点(每次插入的头节点的下一个节点位置)

代码实现:

linklist.h

复制代码
#ifndef __LINKLIST_H__`
`#define __LINKLIST_H__`

`typedef int datatype;`
`typedef struct node_t`
`{`
`    datatype data;`
`    struct node_t *next;`
`} link_node_t, *link_node_p;`

`// 1.创建一个空的有头单向链表`
`link_node_p createEmptyLinkList();`
`// 2.链表指定位置插入数据`
`int insertIntoPostLinkList(link_node_p p, int post, datatype data);`
`// 3.计算链表的长度。`
`int lengthLinkList(link_node_p p);`
`// 4.遍历链表`
`void showLinkList(link_node_p p);`
`// 5.链表指定位置删除数据`
`int deletePostLinkList(link_node_p p, int post);`
`// 6.判断链表是否为空`
`int isEmptyLinkList(link_node_p p);`
`// 7.清空单向链表`
`void clearLinkList(link_node_p p);`
`// 8.修改指定位置的数据 post 被修改的位置 data修改成的数据`
`int changePostLinkList(link_node_p p, int post, datatype data);`
`// 9.查找指定数据出现的位置 data被查找的数据 //search 查找`
`int searchDataLinkList(link_node_p p, datatype data);`
`// 10.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除`
`int deleteDataLinkList(link_node_p p, datatype data);`
`// 11.转置链表`
`void reverseLinkList(link_node_p p);`
`#endif`
`

linklist.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "linklist.h"`

`// 创建一个空的有头单向链表`
`link_node_p createEmptyLinkList()`
`{`
`    // 1.创建头节点`
`    link_node_p p = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == p)`
`    {`
`        perror("p malloc err");`
`        return NULL;`
`    }`
`    // 2.初始化头节点`
`    p->next = NULL;`
`    // 3.返回头节点的地址`
`    return p;`
`}`

`// 计算链表长度 length:长度`
`int lengthLinkList(link_node_p p)`
`{`
`    int len = 0;`
`    while (p->next != NULL)`
`    {`
`        p = p->next;`
`        len++;`
`    }`
`    return len;`
`}`

`// 链表指定位置插入数据`
`int insertIntoPostLinkList(link_node_p p, int post, datatype data)`
`{`
`    // 1.容错判断`
`    if (post < 0 || post > lengthLinkList(p))`
`    {`
`        printf("insert err\n");`
`        return -1;`
`    }`
`    // 2.新建一个节点`
`    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == pnew)`
`    {`
`        perror("pnew err");`
`        return -1;`
`    }`
`    // 3.初始化节点`
`    pnew->data = data;`

`    // 4.将指针p移动到插入位置的前一个节点`
`    for (int i = 0; i < post; i++)`
`        p = p->next;`

`    // 5.将新节点连接到链表(先连后面再连前面)`
`    pnew->next = p->next;`
`    p->next = pnew;`

`    return 0;`
`}`

`// 遍历链表`
`void showLinkList(link_node_p p)`
`{`
`    while (p->next != NULL)`
`    {`
`        p = p->next;`
`        printf("%d ", p->data);`
`    }`
`    printf("\n");`
`}`

`// 判断链表是否为空`
`int isEmptyLinkList(link_node_p p)`
`{`
`    return p->next == NULL;`
`}`

`// 链表指定位置删除数据`
`int deletePostLinkList(link_node_p p, int post)`
`{`
`    // 1.容错判断`
`    if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p))`
`    {`
`        printf("delete err\n");`
`        return -1;`
`    }`

`    // 2.将指针p遍历到删除节点的前一个节点`
`    for (int i = 0; i < post; i++)`
`        p = p->next;`

`    // 3.设指针pdel指向要删除节点`
`    link_node_p pdel = p->next;`

`    // 4.前后跨过要删除节点`
`    p->next = pdel->next; // p->next=p->next->next;`

`    // 5.释放要删除节点`
`    free(pdel);`
`    pdel = NULL;`

`    return 0;`
`}`

`// 清空单向链表`
`// 思想: 每次都删除头节点的后一个节点,直到链表为空`
`void clearLinkList(link_node_p p)`
`{`
`    link_node_p pdel = NULL;`
`    while (p->next != NULL)`
`    {`
`        // 指针pdel指向头节点的后一个节点`
`        pdel = p->next;`
`        //  前后跨过pdel所指节点`
`        p->next = pdel->next;`
`        // 释放pdel`
`        free(pdel);`
`    }`
`}`

`// 修改指定位置的数据 post 被修改的位置 data修改成的数据`
`int changePostLinkList(link_node_p p, int post, datatype data)`
`{`
`    // 1.容错判断`
`    if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p))`
`    {`
`        printf("change err\n");`
`        return -1;`
`    }`
`    // 2.将指针遍历要修改的位置`
`    for (int i = 0; i <= post; i++)`
`        p = p->next;`
`    // 3.修改指定位置数据`
`    p->data = data;`

`    return 0;`
`}`

`// 查找指定数据出现的位置 data被查找的数据 //search 查找`
`int searchDataLinkList(link_node_p p, datatype data)`
`{`
`    int post = 0; // 记录查找节点的位置`
`    while (p->next != NULL)`
`    {`
`        p = p->next;`
`        if (p->data == data)`
`            return post;`
`        post++;`
`    }`
`    return -1;`
`}`

`// 删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除`
`int deleteDataLinkList(link_node_p p, datatype data)`
`{`
`    link_node_p q = p->next; // 让q指向头节点的后一个节点`
`    // 相当于q遍历无头单向链表`
`    // 遍历一个节点就判断一个,判断成功就删除,不成功就继续向后遍历`
`    while (q != NULL)`
`    {`
`        if (q->data == data) // 判断成功就删除节点并向后遍历`
`        {`
`            //(1)删除节点`
`            // 前后跨过要删除节点`
`            p->next = q->next;`
`            // 释放要删除节点`
`            free(q);`
`            //(2)让q向后遍历`
`            q = p->next;`
`        }`
`        else`
`        {`
`            // q和p指针都向后移动一个单位`
`            p = p->next;`
`            q = q->next;`
`        }`
`    }`
`    return 0;`
`}`

`// 转置链表`
`void reverseLinkList(link_node_p p)`
`{`
`    link_node_p t = NULL; // 设一个临时指针,用于保存q的下一个节点(不然链表就找不到了)`
`    // 让q保存头节点的下一个节点`
`    link_node_p q = p->next;`
`    // 将头节点断开,也就是头节点指针域置空`
`    p->next = NULL;`
`    // 相当于用q无头单向链表`
`    // 遍历一个节点就头插一个节点`
`    while (q != NULL)`
`    {`
`        // 让指针t保存q的下一个节点(不然链表就找不到了)`
`        t = q->next;`
`        // 头插:让q插入到p后面,也是先连后面再连前面`
`        q->next = p->next;`
`        p->next = q;`
`        // 让q指向遍历的下一个(t)`
`        q = t;`
`    }`
`}

main.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "linklist.h"`

`int main(int argc, char const *argv[])`
`{`
`    link_node_p p = createEmptyLinkList();`
`    insertIntoPostLinkList(p, 0, 1);`
`    insertIntoPostLinkList(p, 1, 2);`
`    insertIntoPostLinkList(p, 2, 3);`
`    showLinkList(p);`
`    reverseLinkList(p);`
`    showLinkList(p);`
`    // insertIntoPostLinkList(p, 0, 1);`
`    // insertIntoPostLinkList(p, 1, 2);`
`    // insertIntoPostLinkList(p, 2, 3);`
`    // insertIntoPostLinkList(p, 1, 100);`

`    // showLinkList(p);`

`    // deletePostLinkList(p, 1);`
`    // showLinkList(p);`

`    // changePostLinkList(p, 1, 50);`
`    // showLinkList(p);`
`    // printf("post is:%d\n", searchDataLinkList(p, 500));`
`    // insertIntoPostLinkList(p, 3, 1);`
`    // showLinkList(p);`
`    // deleteDataLinkList(p, 1);`
`    // showLinkList(p);`

`    // clearLinkList(p);`

`    // if (isEmptyLinkList(p))`
`    //     printf("empty!\n");`

`    return 0;`
`}`
`

makefile

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=linklist.o main.o`

`linklist:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) linklist *.o`
`

3.4 单向循环链表

约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,...,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。

用解决约瑟夫环问题进行杀猴子:

思想:用头指针移动到要杀的猴子的前一个,然后跨过指向猴子的节点。

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include <unistd.h>`

`typedef` `struct` `node_t`
`{`
	`int data;`
	`struct` `node_t` `*next;`
`}link_node_t,*link_node_p;`

`int` `main(int argc,` `const` `char` `*argv[])`
`{`
	`int i;`
`	link_node_p pdel =` `NULL;//用于指向被删除节点`
`	link_node_p ptail =` `NULL;//永远指向当前链表的尾 `
`	link_node_p pnew =` `NULL;//永远指向新创建的节点`
`	link_node_p h =` `NULL;`
	`int all_num =` `7;//猴子总数 `
	`int start_num =` `2;` `//从几号猴子开始数`
	`int kill_num =` `3;//数到几杀死猴`
	`printf("请您入猴子总数 起始号码 数到几杀死:\n");`
	`scanf("%d%d%d",&all_num,&start_num,&kill_num);`
	`//1.创建出一个单向循环链表`
	`//(1)创建有all_num个节点的单向链表`
`	h =` `(link_node_p)malloc(sizeof(link_node_t));`
	`if(NULL` `== h)`
	`{`
		`perror("malloc failed");`
		`return` `-1;`
	`}`
`	h->data =` `1;`
`	h->next =` `NULL;`
`	ptail = h;//尾指针指向当前的第一个节点`
	`for(i =` `2; i <= all_num; i++)`
	`{`
		`//创建新的节点`
`		pnew =` `(link_node_p)malloc(sizeof(link_node_t));`
		`if(NULL` `== pnew)`
		`{`
			`perror("malloc failed");`
			`return` `-1;`
		`}`
		`//将新节点装上数据`
`		pnew->data = i;`
`		pnew->next =` `NULL;`
		`//将新节点链接到链表尾 `
`		ptail->next = pnew;//链接到链表的尾`
`		ptail = pnew;//尾指针继续指向当前链表的尾 `
	`}`
	`//(2)将头指针保存到链表的尾形成单向循环链表`
`	ptail->next = h;//形成单向循环链表 `
`#if 0 //用于调试程序`
	`while(1)`
	`{`
		`printf("%d\n",h->data);`
`		h = h->next;`
		`sleep(1);`
	`}`
`#endif`
	`//2.开始杀猴子 `
	`//(1)将头指针移动到开始猴子的号码处 `
	`for(i =` `1; i < start_num; i++)`
`		h = h->next;`
        `printf("start :%d\n",h->data);`
	`//(2)循环进行杀猴子`
	`while(h != h->next)//条件不成的时候,就剩一个猴子,只有一个节点`
	`{`
		`//将头指针移动到即将删除节点的前一个节点`
		`for(i =` `1; i < kill_num-1; i++)`
`			h = h->next;`

`		pdel = h->next;`
		`//跨过删除节点`
`		h->next = pdel->next;`
		`printf("kill is -------------%d\n",pdel->data);`
		`free(pdel);`
`		pdel =` `NULL;`
		`//杀死猴子猴,从下一个节点开始继续开始数,将头指针移动到开始数的地方`
`		h = h->next;`
	`}`
	`printf("king is=================== %d\n",h->data);`
	`return` `0;`
`}`	
`

总结:顺序表和单向链表比较

  1. 顺序表在内存当中连续存储的,通过数组实现;但是链表在内存当中是不连续存储的,通过指针将数据链接在一起。
  2. 顺序表节点占用空间相对较小; 链表节点中不光保存数据还有一个指针域保存下一个节点的地址所以占用空间相对较大。
  3. 顺序表的长度是固定的;链表长度不固定。
  4. 顺序表查找和修改可以根据下标找到对应元素效率对比链表高,但是插入和删除涉及到元素移动效率低;链表,插入和删除方便,查和改效率低。

看以下程序有什么问题:

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`void get_mem(int *q) //q=NULL`
`{`
`    q = (int *)malloc(sizeof(int)); //修改q不会影响到主函数的p`
`    //开辟空间不够`
`}`

`int main(int argc, char const *argv[])`
`{`
`    int *p = NULL;`
`    get_mem(p); //函数调用结束p还是指向NULL `
`    for (int i = 0; i < 10; i++)`
`        p[i] = i;`
`    for (int i = 0; i < 10; i++)`
`        printf("%d\n", p[i]);`
`    free(p);`
`    return 0;`
`}` `

错误原因:1. 开辟空间不够 2.相当于值传递,函数内修改q不会影响到主函数的p。所以函数调用结束后主函数的p不变,还是指向NULL所以后面发生段错误。

修改:可以传递二级指针或者通过返回值

复制代码
#include <stdio.h>`
`#include <stdlib.h>`

`void get_mem(int **q) //q=&p`
`{`
`    *q = (int *)malloc(sizeof(int)*10); //*q= *&p = p`
`    //所以修改*q就等于修改主函数的指针p`
`}`

`int main(int argc, char const *argv[])`
`{`
`    int *p = NULL;`
`    get_mem(&p); `
`    for (int i = 0; i < 10; i++)`
`        p[i] = i;`
`    for (int i = 0; i < 10; i++)`
`        printf("%d\n", p[i]);`
`    free(p);`
`    return 0;`
`}

3.5 链式栈

3.5.1 特性

逻辑结构:线性结构

存储结构:链式存储

顺序栈和链式栈的区别:存储结构不同,那么实现的方式也不同,顺序栈是顺序存储用数组也就是顺序表实现,内存连续,长度固定。链式栈是链式存储用指针实现也就是用链表实现,内存不连续,长度不固定。

操作:创建、入栈、出栈、判空。

3.5.2 代码实现

入栈:

出栈:

linkstack.h

复制代码
#ifndef __LINKSTACK_H__`
`#define __LINKSTACK_H__`
`typedef int datatype;`
`typedef struct linkstack`
`{`
`    datatype data;`
`    struct linkstack *next;`
`} linkstack_t, *linkstack_p;`

`// 1.创建一个空的栈`
`void createEmptyLinkStack(linkstack_t **p);`
`// 2.入栈,p是传入的栈针的地址,data是入栈的数据`
`int pushLinkStack(linkstack_t **p, datatype data);`
`// 3.判断栈是否为空`
`int isEmptyLinkStack(linkstack_t *top);`
`// 4.出栈`
`datatype popLinkStack(linkstack_t **p);`
`// 5.清空栈`
`void clearLinkStack(linkstack_t **p);`
`// 6.求栈的长度`
`int lengthLinkStack(linkstack_t *top);`

`#endif

linkstack.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "linkstack.h"`

`// 创建一个空的栈`
`void createEmptyLinkStack(linkstack_t **p) //&top`
`{`
`    *p = NULL; //*p=(*&top = top=)NULL`
`}`

`// 入栈,p是传入的栈针的地址,data是入栈的数据`
`// 参数上之所以采用二级指针因我们我们在函数内真的要改变主函数的栈针top`
`// 因为top要永远指向栈顶节点,所以需要传递top的地址,然后函数内通过top的地址也就是p访问到top并修改,就需要地址传递。`
`int pushLinkStack(linkstack_t **p, datatype data)`
`{`
`    // 新建一个节点,存入数据`
`    linkstack_p pnew = (linkstack_p)malloc(sizeof(linkstack_t));`
`    if (NULL == pnew)`
`    {`
`        perror("pnew malloc err");`
`        return -1;`
`    }`
`    pnew->data = data;`
`    // 连接新节点到链表(新节点连老栈顶节点,因为要实现后进先出)`
`    pnew->next = *p; //*p就是栈针top`
`    // 移动栈针到新节点`
`    *p = pnew;`
`    return 0;`
`}`

`// 判断栈是否为空`
`int isEmptyLinkStack(linkstack_t *top)`
`{`
`    return top == NULL;`
`}`

`// 出栈`
`datatype popLinkStack(linkstack_t **p)`
`{`
`    // 1.判空`
`    if (isEmptyLinkStack(*p))`
`    {`
`        printf("pop err\n");`
`        return -1;`
`    }`
`    // 2.用指针pdel指向要删除节点`
`    linkstack_p pdel = *p;`
`    // 3.设临时变量temp保存要出栈数据`
`    datatype temp = (*p)->data;`
`    // 4.将栈针向后移动一个单位`
`    *p = (*p)->next; //*p就是top`
`    // 5.释放要删除节点`
`    free(pdel);`
`    // 6.返回要出栈数据也就是temp`
`    return temp;`
`}`

`// 清空栈`
`void clearLinkStack(linkstack_t **p)`
`{`
`    while (!isEmptyLinkStack(*p))`
`    {`
`        printf("%d ", popLinkStack(p));`
`    }`
`    printf("\n");`
`}`

`// 求栈的长度`
`int lengthLinkStack(linkstack_t *top)`
`{`
`    int len = 0;`
`    while (top != NULL)`
`    {`
`        len++;`
`        top = top->next;`
`    }`
`    return len;`
`}`
`

main.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "linkstack.h"`

`int main(int argc, char const *argv[])`
`{`
`    linkstack_p top;`
`    createEmptyLinkStack(&top); // 相当于top=NULL;`
`    pushLinkStack(&top, 1);`
`    pushLinkStack(&top, 2);`
`    pushLinkStack(&top, 3);`
`    printf("len=%d\n", lengthLinkStack(top));`

`    // while (!isEmptyLinkStack(top))`
`    // {`
`    //     printf("%d ", popLinkStack(&top)); //3 2 1  后进先出`
`    // }`
`    // printf("\n");`
`    clearLinkStack(&top);`

`    return 0;`
`}`
`

makefile

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=linkstack.o main.o`

`linkstack:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) linkstack *.o

总结:

顺序栈和链式的区别?

  1. 顺序栈是顺序存储,内存连续,用顺序表实现; 链式栈是链式存储,内存不连续,用链表实现。
  2. 顺序栈长度固定; 链式栈长度不固定。

3.6 队列 Queue

3.6.1 特性

队列是只允许再两端进行插入和删除操作的线性表,在队尾插入,在队头删除,插入的一端被称为"队尾",删除的一端被称为"队头"。队列包括顺序队列(循环队列)和链式队列。

结构:先进先出 FIFO

3.6.2 循环队列

逻辑结构:线性结构

存储结构:顺序存储结构

操作:创建、入列、出列、判空、判满

创空:

入列:

出队:

长度:

代码实现:

sequeue.h

复制代码
#ifndef _SEQUEUE_H_`
`#define _SEQUEUE_H_`
`#define N 6`
`typedef int datatype;`
`typedef struct`
`{`
`    datatype data[N]; // 循环队列存数据的数组`
`    int rear;         // 入数据的一端队尾  rear后面`
`    int front;        // 出数据的一端队头  front前面`
`} sequeue_t, *sequeue_p;`

`// 数组元素个数是N的话,队列中最多能存N-1,因为牺牲了一个元素空间用于判满了。`

`// 1.创建空队列`
`sequeue_p createEmptySequeue();`
`// 2.判断队列是否为满`
`int isFullSequeue(sequeue_p p);`

`// 3.入列 data代表入列的数据`
`int inSequeue(sequeue_p p, datatype data);`

`// 4.判断队列是否为空`
`int isEmptySequeue(sequeue_p p);`

`// 5.出列`
`datatype outSequeue(sequeue_p p);`

`//6.求队列长度`
`int lengthSequeue(sequeue_p p);`
`#endif

sequeue.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "sequeue.h"`

`// 创建一个空队列`
`sequeue_p createEmptySequeue()`
`{`
`    // 1.开辟结构体大小空间`
`    sequeue_p p = (sequeue_p)malloc(sizeof(sequeue_t));`
`    if (NULL == p)`
`    {`
`        perror("p malloc err");`
`        return NULL;`
`    }`
`    // 2.初始化`
`    p->front = 0; // 使用的时候就是数组的下标`
`    p->rear = 0;`

`    // 3.返回结构体空间地址`
`    return p;`
`}`

`// 判断队列是否为满`
`int isFullSequeue(sequeue_p p)`
`{`
`    // 尾的后一个和头相等就是满`
`    return (p->rear + 1) % N == p->front;`
`}`

`// 入列 data代表入列的数据`
`int inSequeue(sequeue_p p, datatype data)`
`{`
`    // 1.判满`
`    if (isFullSequeue(p))`
`    {`
`        printf("in err\n");`
`        return -1;`
`    }`
`    // 2.将数据存入队列`
`    p->data[p->rear] = data;`
`    // 3.将队尾向后移动一个单位`
`    p->rear = (p->rear + 1) % N; // 为了让下标循环起来`

`    return 0;`
`}`

`// 判断队列是否为空`
`int isEmptySequeue(sequeue_p p)`
`{`
`    return p->rear == p->front;`
`}`

`// 出列`
`datatype outSequeue(sequeue_p p)`
`{`
`    // 1.判空:头==尾`
`    if (isEmptySequeue(p))`
`    {`
`        printf("out err\n");`
`        return -1;`
`    }`
`    // 2.设临时变量temp保存一下要出队数据`
`    datatype temp = p->data[p->front];`
`    // 3.将头向后移动一个单位`
`    p->front = (p->front + 1) % N;`
`    // 4.返回保存的出队数据了`
`    return temp;`
`}`

`// 求队列长度`
`int lengthSequeue(sequeue_p p)`
`{`
`    return (p->rear - p->front + N) % N;`
`}` 
`

main.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "sequeue.h"`

`int main(int argc, char const *argv[])`
`{`
`    sequeue_p p = createEmptySequeue();`
`    for (int i = 1; i <= 6; i++)`
`    {`
`        inSequeue(p, i); // 最后一次调用报错in err 因为队列已经满了`
`    }`

`    // int len = lengthSequeue(p);`
`    printf("len=%d\n", lengthSequeue(p));`

`    while (!isEmptySequeue(p))`
`        printf("%d\n", outSequeue(p));`
`    // 1 2 3 4 5先进先出`
`    return 0;`
`}`
`

makefile

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=sequeue.o main.o`

`sequeue:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) sequeue *.o`

`

循环队列,如果数据的元素个数为N个,那么队列中最多能够存储多少个数据? N-1个

为什么?

答:rear 后面 队尾,在插入的时候,插入之前需要先判断 rear+1,也就是他的下一个为位置是否等于 front 来判断队列是否为满,会造成浪费一个存储位置。

3.6.3 链式队列

逻辑结构:线性结构

存储结构:链式存储

操作:创建、入列、出列、判空

可以通过把头指针和尾指针封装结构体实现,此时需要两个结构体,一个还是链表节点的结构体,另一个是头尾指针封装的结构体。有了头指针和尾指针就可以操作整个链式队列。

创空:

入列:

出列:

linkqueue.h

复制代码
#ifndef _LINKQUEUE_H_`
`#define _LINKQUEUE_H_`
`typedef int datatype;`
`// 链表节点结构体`
`typedef struct node_t`
`{`
`    datatype data;`
`    struct node_t *next;`
`} link_node_t, *link_node_p;`

`// 队列结构体:包含头尾指针`
`typedef struct`
`{`
`    link_node_p front;`
`    link_node_p rear;`
`} linkqueue_t, *linkqueue_p;`

`// 创建一个空链式队列,用有头链表`
`linkqueue_p createEmptyLinkQueue();`
`// 入列 data代表入列的数据`
`int inLinkQueue(linkqueue_t *p, datatype data);`
`//判断队列是否为空`
`int isEmptyLinkQueue(linkqueue_t *p);`
`// 出列`
`datatype outLinkQueue(linkqueue_t *p);`
`//求队列长度的函数`
`int lengthLinkQueue(linkqueue_t *p);`

`#endif

linkqueue.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "linkqueue.h"`

`// 创建一个空链式队列,用有头链表`
`linkqueue_p createEmptyLinkQueue()`
`{`
`    // 1.开辟队列结构体空间`
`    linkqueue_p p = (linkqueue_p)malloc(sizeof(linkqueue_t));`
`    if (NULL == p)`
`    {`
`        perror("p malloc err");`
`        return NULL;`
`    }`
`    // 2.初始化队列结构体空间`
`    // 让头尾指针都指向一个开辟的头节点空间`
`    p->front = p->rear = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == p->front)`
`    {`
`        perror("front err");`
`        return NULL;`
`    }`
`    // 3.初始化头节点`
`    p->front->next = NULL;`

`    // 4.返回队列结构体空间地址`
`    return p;`
`}`

`// 入列 data代表入列的数据`
`int inLinkQueue(linkqueue_t *p, datatype data)`
`{`
`    // 1.新建一个节点`
`    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == pnew)`
`    {`
`        perror("in err");`
`        return -1;`
`    }`
`    // 2. 初始化新节点`
`    pnew->data = data;`
`    pnew->next = NULL;`
`    // 3.连接新节点到链表尾部`
`    p->rear->next = pnew;`
`    // 4.移动尾指针到新节点`
`    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("out er\n");`
`        return -1;`
`    }`
`    // 2.将pdel指向要释放的节点也就是老的头节点`
`    link_node_p pdel = p->front;`
`    // 3.将头指针向后移动一个单位`
`    p->front = p->front->next;`
`    // 4.释放pdel`
`    free(pdel);`
`    // 5.返回此时头指针所指节点的数据(让此时头指针所指的变成新的头节点)`
`    return p->front->data;`
`}`
`// 求队列长度的函数`
`int lengthLinkQueue(linkqueue_t *p)`
`{`
`    int len = 0;`
`    link_node_p h = p->front; // 将链表的头指针保存给h,如果指直接用头指针那么求完长度之后头指针真的移动了,所以用h代替头指针遍历链表。这样移动的是h就可以保持头指针不变了。`
`    // 相当于用h去遍历有头单向链表`
`    while (h->next != NULL)`
`    {`
`        h = h->next;`
`        len++;`
`    }`
`    return len;`
`}`
`

main.c

复制代码
#include <stdio.h>`
`#include "linkqueue.h"`

`int main(int argc, char const *argv[])`
`{`
`    linkqueue_p p = createEmptyLinkQueue();`

`    inLinkQueue(p, 1);`
`    inLinkQueue(p, 2);`
`    inLinkQueue(p, 3);`
`    printf("len=%d\n", lengthLinkQueue(p));`
`    while (!isEmptyLinkQueue(p))`
`    {`
`        printf("%d\n", outLinkQueue(p));`
`        // 1 2 3先进先出`
`    }`

`    return 0;`
`}

makefile

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=linkqueue.o main.o`

`linkqueue:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) linkqueue *.o

3.7 双向链表

3.7.1 特性

逻辑结构:线性结构

存储结构:链式存储结构

操作:增删改查

建立双向链表结构体:

创空:

插入:

删除:

按数据删除:

思想:从头节点后节点开始用指针temp遍历,相当于遍历无头链表,遇到需要删除节点的就用pdel指向它然后删除,如果不需要删除则temp继续往后走一个单位。这里因为是双向链表可以找到前驱,所以不需要每次指向被删除节点的前一个然后跨过了。

3.7.2 代码实现

doublelinklist.h

复制代码
#ifndef _DOUBLELINKLIST_H_`
`#define _DOUBLELINKLIST_H_`
`typedef int datatype;`
`// 双向链表节点的结构体`
`typedef struct node_t`
`{`
`    datatype data;        // 数据域`
`    struct node_t *next;  // 指向下一个节点的指针`
`    struct node_t *prior; // 指向前一个节点的指针`
`} link_node_t, *link_node_p;`

`// 将双向链表的头指针和尾指针以及长度封装到一个结构体里面`
`// 思想上有点像之前学的链式队列`
`typedef struct doublelinklist`
`{`
`    link_node_p head; //指向双向链表的头指针`
`    link_node_p tail; //指向双向链表的尾指针`
`    int len;        //用来保存双向链表的长度`
`} double_list_t, *double_list_p;`

`//1.创建一个空的双向链表`
`double_list_p createEmptyDoubleLinkList();`
`//2.向双向链表的指定位置插入数据 post位置, data数据`
`int insertIntoDoubleLinkList(double_list_p p, int post, datatype data);`
`//3.遍历双向链表`
`void showDoubleLinkList(double_list_p p);`
`//4.删除双向链表指定位置数据`
`int deletePostDoubleLinkList(double_list_p p, int post);`
`//5.查找指定数据出现的位置 data被查找的数据`
`int searchPostDoubleLinkList(double_list_p p, datatype data);`
`//6.修改指定位置的数据,post修改的位置 data被修改的数据`
`int changeDataDoubleLinkList(double_list_p p, int post, datatype data);`
`#endif

doublelinklist.c

复制代码
#include <stdio.h>`
`#include <stdlib.h>`
`#include "doublelinklist.h"`

`// 创建一个空的双向链表`
`double_list_p createEmptyDoubleLinkList()`
`{`
`    // 1.开辟头尾指针结构体大小空间`
`    double_list_p p = (double_list_p)malloc(sizeof(double_list_t));`
`    if (NULL == p)`
`    {`
`        perror("p malloc err");`
`        return NULL;`
`    }`
`    // 2.初始化结构体空间`
`    p->len = 0;`
`    p->head = p->tail = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == p->head)`
`    {`
`        perror("p->head err");`
`        return NULL;`
`    }`
`    // 3.初始化头节点`
`    p->head->next = NULL;`
`    p->head->prior = NULL;`

`    return p;`
`}`

`// 向双向链表的指定位置插入数据 post位置, data数据`
`int insertIntoDoubleLinkList(double_list_p p, int post, datatype data)`
`{`
`    // 1.容错判断`
`    if (post < 0 || post > p->len)`
`    {`
`        printf("insert err\n");`
`        return -1;`
`    }`
`    // 2.新建一个节点,并初始化新节点`
`    link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));`
`    if (NULL == pnew)`
`    {`
`        perror("pnew err");`
`        return -1;`
`    }`
`    pnew->data = data;`
`    pnew->next = pnew->prior = NULL;`

`    // 3.将新节点连接到链表,分情况:尾插还是中间插入`
`    if (post == p->len) // 尾插`
`    {`
`        //(1)将新节点连接到链表`
`        p->tail->next = pnew;`
`        pnew->prior = p->tail;`
`        //(2)移动尾指针到新节点`
`        p->tail = pnew;`
`    }`
`    else // 中间插入`
`    {`
`        //(1)将指针temp移动到插入节点位置,分前半段还是后半段`
`        link_node_p temp;`
`        if (post <= p->len / 2) // 前半段`
`        {`
`            temp = p->head;`
`            // 让temp从前往后移动到post处`
`            for (int i = 0; i <= post; i++)`
`                temp = temp->next;`
`        }`
`        else // 后半段`
`        {`
`            temp = p->tail;`
`            // 让temp从后往前移动到post处`
`            for (int i = p->len - 1; i > post; i--)`
`                temp = temp->prior;`
`        }`
`        //(2)将新节点连接到链表(先连前面再连后面)`
`        pnew->prior = temp->prior;`
`        pnew->prior->next = pnew;`
`        pnew->next = temp;`
`        temp->prior = pnew;`
`    }`
`    // 3.让长度加一`
`    p->len++;`
`    return 0;`
`}`

`// 遍历双向链表`
`void showDoubleLinkList(double_list_p p)`
`{`
`    link_node_p 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 != p->head)`
`    {`
`        printf("%d ", temp->data);`
`        temp = temp->prior;`
`    }`
`    printf("\n");`
`}`

`// 判断链表是否为空`
`int isEmptyDoubleLinkList(double_list_p p)`
`{`
`    return p->len == 0;`
`}`

`// 删除双向链表指定位置数据`
`int deletePostDoubleLinkList(double_list_p p, int post)`
`{`
`    // 1.容错判断`
`    if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)`
`    {`
`        printf("delete 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 // 中间删除`
`    {`
`        link_node_p temp;`
`        //(1)将指针遍历到要删除节点位置,分前后半段`
`        if (post <= p->len / 2) // 前半段`
`        {`
`            temp = p->head;`
`            for (int i = 0; i <= post; i++)`
`                temp = temp->next;`
`        }`
`        else // 后半段`
`        {`
`            temp = p->tail;`
`            for (int i = p->len - 1; i > post; i--)`
`                temp = temp->prior;`
`        }`
`        //(2)前后跨过要删除节点`
`        temp->next->prior = temp->prior;`
`        temp->prior->next = temp->next;`
`        //(3)释放要删除节点`
`        free(temp);`
`    }`
`    // 3.让长度减一`
`    p->len--;`

`    return 0;`
`}`

`// 查找指定数据出现的位置 data被查找的数据`
`int searchPostDoubleLinkList(double_list_p p, datatype data)`
`{`
`    link_node_p temp = p->head;`
`    int post = 0;`
`    while (temp->next != NULL)`
`    {`
`        temp = temp->next;`
`        if (temp->data == data)`
`            return post;`
`        post++;`
`    }`
`    return -1;`
`}`

`// 修改指定位置的数据,post修改的位置 data被修改的数据`
`int changeDataDoubleLinkList(double_list_p p, int post, datatype data)`
`{`
`    // 1.容错判断`
`    if (isEmptyDoubleLinkList(p) || post < 0 || post >= p->len)`
`    {`
`        printf("change err\n");`
`        return -1;`
`    }`
`    // 2.设指针移动到要修改节点位置`
`    link_node_p temp;`
`    if (post <= p->len / 2) // 前半段`
`    {`
`        temp = p->head;`
`        for (int i = 0; i <= post; i++)`
`            temp = temp->next;`
`    }`
`    else // 后半段`
`    {`
`        temp = p->tail;`
`        for (int i = p->len - 1; i > post; i--)`
`            temp = temp->prior;`
`    }`
`    // 3.修改节点数据`
`    temp->data = data;`
`    return 0;`
`}`

`// 删除双向链表中的指定数据 data代表删除所有出现的data数据`
`void deleteDataDoubleLinkList(double_list_p p, datatype data)`
`{`
`    link_node_p temp = p->head->next;`
`    while (temp != NULL) // 相当于遍历无头链表`
`    {`
`        if (temp->data == data) // 判断成功就进行删除操作`
`        {`
`            // 删除操作:分情况讨论尾删还是中间删除`
`            if (temp == p->tail) // 尾删`
`            {`
`                // 将尾指针向前移动一个单位`
`                p->tail = p->tail->prior;`
`                // 将最后一个节点释放`
`                free(p->tail->next);`
`                // 将此时尾指针所指节点的next置空`
`                p->tail->next = NULL;`
`                // 将temp置空(否则temp是野指针循环就出不去了)`
`                temp = NULL;`
`            }`
`            else // 中间删除`
`            {`
`                // 设指针pdel记录要删除节点`
`                link_node_p pdel = temp;`
`                // 前后跨过要删除节点`
`                temp->next->prior = temp->prior;`
`                temp->prior->next = temp->next;`
`                // 将temp向后移动一个单位`
`                temp = temp->next;`
`                // 释放pdel`
`                free(pdel);`
`            }`
`            // 让长度减一`
`            p->len--;`
`        }`
`        else // 将temp向后继续遍历`
`        {`
`            temp = temp->next;`
`        }`
`    }`
`}`
`

main.c

复制代码
#include <stdio.h>`
`#include "doublelinklist.h"`

`int main(int argc, char const *argv[])`
`{`
`    double_list_p p = createEmptyDoubleLinkList();`
`    insertIntoDoubleLinkList(p, 0, 1);`
`    insertIntoDoubleLinkList(p, 1, 2);`
`    insertIntoDoubleLinkList(p, 2, 3);`
`    showDoubleLinkList(p);`
`    printf("post is:%d\n", searchPostDoubleLinkList(p, 10));`
`    deletePostDoubleLinkList(p, 1);`
`    showDoubleLinkList(p);`
`    changeDataDoubleLinkList(p, 1, 200);`
`    showDoubleLinkList(p);`
`    insertIntoDoubleLinkList(p, 2, 1);`
`    showDoubleLinkList(p);`
`    deleteDataDoubleLinkList(p, 1);`
`    showDoubleLinkList(p);`

`    return 0;`
`}`
`

makefile

复制代码
CC=gcc`
`GFLAGS=-c -g -Wall`
`OBJS=doublelinklist.o main.o`

`doublelinklist:$(OBJS)`
`	$(CC) $^ -o $@`
`%.o:%.c`
`	$(CC) $(GFLAGS) $< -o $@`
`.PHONY:clean`
`clean:`
`	$(RM) doublelinklist *.o

3.7. 3 双向循环链表

复制代码
#include <stdio.h>`
`#include <stdlib.h>`

`typedef int datatype;`
`typedef struct node_t`
`{`
`	datatype data;`
`	struct node_t * prior;`
`	struct node_t * next;`
`}link_node_t,*link_node_p;`

`typedef struct doublelinklist`
`{`
`	link_node_p head;`
`	link_node_p tail;`
`}double_list_t,*double_list_p;`


`int main(int argc, const char *argv[])`
`{`
`	int i;`
`	int all_num = 8;//猴子总数`
`	int start_num = 3;//从3号猴子开始数`
`	int kill_num = 3;//数到几杀死猴子 `
`	link_node_p h = NULL;`
`	link_node_p pdel = NULL;//用来指向被杀死猴子的节点`
`	printf("请您输入猴子的总数,开始号码,出局号码:\n");`
`	scanf("%d%d%d",&all_num,&start_num,&kill_num);`
`	//1.创建一个双向的循环链表`
`	double_list_p p = (double_list_p)malloc(sizeof(double_list_t));//申请头指针和尾指针`
`	if(NULL == p)`
`	{`
`		perror("malloc failed");`
`		return -1;`
`	}`
`	p->head = p->tail = (link_node_p)malloc(sizeof(link_node_t));`
`	if(NULL == p->tail)`
`	{`
`		perror("p->tail malloc failed");`
`		return -1;`
`	}`
`	p->head->data = 1;`
`	p->head->prior = NULL;`
`	p->head->next = NULL;`
`	//将创建n个新的节点,链接到链表的尾`
`	for(i = 2; i <= all_num; i++)`
`	{`
`		link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));`
`		if(NULL == pnew)`
`		{`
`			perror("pnew malloc failed");`
`			return -1;`
`		}`
`		pnew->data = i;`
`		pnew->prior = NULL;`
`		pnew->next = NULL;`
`		//(1)将新的节点链接到链表的尾`
`		p->tail->next = pnew;`
`		pnew->prior = p->tail;`
`		//(2)尾指针向后移动,指向当前链表的尾`
`		p->tail = pnew;`
`	}`
`	//(3)形成双向循环链表 `
`	p->tail->next = p->head;`
`	p->head->prior = p->tail;`
`	//调试程序 `
`#if 0`
`	while(1)`
`	{`
`		printf("%d\n",p->head->data);`
`		p->head = p->head->next;`
`		sleep(1);`
`	}`
`#endif`
`	//2.循环进行杀死猴子`
`	h = p->head;`
`	//(1)先将h移动到start_num处,也就是开始数数的猴子号码处`
`	for(i = 1; i < start_num; i++)`
`		h = h->next;`
`        printf("start is:%d\n",h->data);`
`	while(h->next != h)//当h->next == h 就剩一个节点了,循环结束`
`	{`
`		//(2)将h移动到即将杀死猴子号码的位置`
`		for(i = 1; i < kill_num; i++)`
`			h = h->next;`
`		//(3)进行杀死猴子,经过上面的循环后,此时的h指向即将杀死的猴子`
`		h->prior->next = h->next;`
`		h->next->prior = h->prior;`
`		pdel = h;//pdel指向被杀死猴子的位置`
`		printf("kill is -------%d\n",pdel->data);`
`		h = h->next;//需要移动,从杀死猴子后的下一个位置开始数`
`		free(pdel);`
`	}`
`	printf("猴王是%d\n",h->data);`
`	return 0;`
`}	`
`

4.树

4.1 特性

4.1.1 什么是树?

树(Tree)是(n>=0)个节点的有限集合T,它满足两个条件:

(1) 有且仅有一个特定的称为根(Root)的节点。

  1. 其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、......、Tm,其中每一个集合又是一棵树,并称为其根的子树(Subtree)。

树的特性: 层次关系,一对多,每个节点最多有一个前驱,但是可以有多个后继(根节点无前驱,叶节点无后继)。

关于树的节点: 和链表类似,树存储结构中也将存储的各个元素称为 "结点"。

4.1.2 关于树的一些术语

度数:一个节点的子树的个数 (一个节点有几个孩子为该节点度数)

树度数:树中节点的最大度数

叶节点或终端节点: 度数为零的节点

分支节点:度数不为零的节点

内部节点:除根节点以外的分支节点 (去掉根和叶子)

节点层次: 根节点的层次为1,根节点子树的根为第2层,以此类推

树的深度或高度: 树中所有节点层次的最大值

4.2 二叉树

最多只有俩孩子的树,并且分为左孩子和右孩子。

4.2.1什么是二叉树

二叉树(Binary Tree)是n(n≥0)个节点的有限集合,它或者是空集(n=0), 或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。

二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。

4.2.2 二叉树性质(重点)

  1. 二叉树第k(k>=1)层上的节点最多为2的k-1次幂个。//2^(k-1)
  2. 深度为k(k>=1)的二叉树最多有2的k次幂-1个节点。//满二叉树的时候

做多的节点数 2^k-1

  1. 在任意一棵二叉树中,树叶的数目比度数为2的节点的数目多一。

设度数为0的节点数为n0,度数为1的节点数为n1以及度数为2的节点数为n2,则:

总节点数为各类节点数之和:n = n0 + n1 + n2

总节点数为所有子节点数加一:n = n0*0 + n1*1 + n2*2 + 1

==> 0 = -n0 + n2 + 1 ==> n0 = n2 + 1

4.2.3 满二叉树和完全二叉树

满二叉树: 深度为k(k>=1)时节点数为2^k - 1(2的k次幂-1)

完全二叉树: 只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。(先挂树的左边向右, 从上向下挂)

补充(面试可能会遇到):

哈夫曼树 Huffman

哈夫曼树又称为最优树.

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

先明确以下概念:

1、路径和路径长度

在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。

2、结点的权及带权路径长度

若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

3、树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。(

Weighted Path Length of Tree)

WPL=2*2 + 5*2 + 7*1 = 21

哈夫曼树的构造:

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、...、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、...,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

例如:对 2,3,4,8 这四个数进行构造:

第一步:

第二步:

第三步:

4.2.4 二叉树的存储结构

  1. 二叉树的顺序存储结构

顺序存储结构 :完全二叉树节点的编号方法是从上到下,从左到右,根节点为1号节点。设完全二叉树的节点数为n,某节点编号为i

  • 当i>1(不是根节点)时,有父节点,其父节点编号为i/2;
  • 当2*i<=n时,有左孩子,其编号为2*i ,否则没有左孩子,本身是叶节点;
  • 当2*i+1<=n时,有右孩子,其编号为2*i+1 ,否则没有右孩子;

有n个节点的完全二叉树可以用有n+1 个元素的数组进行顺序存储,节点号和数组下标一一对应,下标为零的元素不用。

利用以上特性,可以从下标获得节点的逻辑关系。不完全二叉树通过添加虚节点构成完全二叉树,然后用数组存储,

这要浪费一些存储空间。

看图:

  1. 二叉树的遍历(重点)

前序:根 --> 左 --> 右

中序: 左 --> 根 --> 右

后序:左 --> 右 --> 根

例如:

前序:A B C D E F G H K

中序:B D C A E H G K F

后序:D C B H K G F E A

练习:

已知遍历结果如下,试画出对应的二叉树。

前序: A B C E H F I J D G K

中序:A H E C I F J B D K G

先通过前序找根,然后通过中序判断是否右左右子,再回到前序找左子根或者右子根,以此类推。

4 .3 二叉树的链式存储

用链表实现,基于完全二叉树规律来构建树,按照完全二叉树的编号方法,从上到下,从左到右。

第i个节点:

左子节点编号:2*i (2*i<=n)

右子节点编号:2*i+1(2*i+1<=n)

可以根据左右节点编号来判断二叉树是否构建完成

复制代码
#include <stdio.h>`
`#include <stdlib.h>`

`typedef struct tree_node_t`
`{`
`    int data;                   // 数据域`
`    struct tree_node_t *lchild; // 左子`
`    struct tree_node_t *rchild; // 右子`
`} bitree_node_t, *bitree_node_p;`

`// 创建二叉树,用递归函数`
`// i根节点的编号,n节点数`
`bitree_node_p CreateBitree(int n, int i)`
`{`
`    // 开辟节点大小空间`
`    bitree_node_p r = (bitree_node_p)malloc(sizeof(bitree_node_t));`
`    if (NULL == r)`
`    {`
`        perror("r err");`
`        return NULL;`
`    }`

`    // 初始化节点`
`    r->data = i;`
`    if (2 * i <= n) // 判断是否有左子`
`        r->lchild = CreateBitree(n, 2 * i);`
`    else`
`        r->lchild = NULL; // 没有左子就置空`

`    if (2 * i + 1 <= n) // 判断是否有右子`
`        r->rchild = CreateBitree(n, 2 * i + 1);`
`    else`
`        r->rchild = NULL;`

`    return r;`
`}`
`// 前序`
`void PreOrder(bitree_node_p r)`
`{`
`    if (NULL == r)`
`        return;`
`    printf("%d ", r->data); // 根`
`    if (r->lchild != NULL)  // 左`
`        PreOrder(r->lchild);`
`    if (r->rchild != NULL) // 右`
`        PreOrder(r->rchild);`
`}`

`// 中序`
`void InOrder(bitree_node_p r)`
`{`
`    if (NULL == r)`
`        return;`
`    if (r->lchild != NULL) // 左`
`        InOrder(r->lchild);`

`    printf("%d ", r->data); // 根`

`    if (r->rchild != NULL) // 右`
`        InOrder(r->rchild);`
`}`

`// 后序`
`void PostOrder(bitree_node_p r)`
`{`
`    if (NULL == r)`
`        return;`
`    if (r->lchild != NULL) // 左`
`        PostOrder(r->lchild);`

`    if (r->rchild != NULL) // 右`
`        PostOrder(r->rchild);`

`    printf("%d ", r->data); // 根`
`}`

`int main(int argc, char const *argv[])`
`{`
`    bitree_node_p root = CreateBitree(5, 1);`
`    PreOrder(root);`
`    printf("\n");`
`    InOrder(root);`
`    printf("\n");`
`    PostOrder(root);`
`    printf("\n");`
`    return 0;`
`}`
`

4.4 层次遍历

层次遍历(队列思想)一定要懂

排序算法与时间复杂度关系表:

十大经典排序算法感兴趣可以去看一下

https://www.runoob.com/w3cnote/ten-sorting-algorithm.htmlhttps://www.runoob.com/w3cnote/ten-sorting-algorithm.html

相关推荐
ximu_polaris2 小时前
设计模式(C++)-结构型模式-外观模式
c++·设计模式·外观模式
谢谢 啊sir2 小时前
L1-120 智慧文本编辑器 - java
java·开发语言
睡觉就不困鸭2 小时前
第10天 删除有序数组中的重复项
数据结构·算法
weisian1512 小时前
进阶篇-LangChain篇-15--高级Agent架构—复杂任务拆解(Plan-and-Execute架构)和多智能体协作(LangGraph)
java·架构·langchain·langgraph·planexecute架构
凤年徐2 小时前
自动化构建工具:make 与 Makefile
android·java·linux·自动化
Xiu Yan2 小时前
Java 转 C++ 系列:STL常用函数
java·开发语言·c++·stl·visual studio
.ZGR.2 小时前
【全栈实战】搭建属于你的AI图像生成平台:从Java Swing 到 Web 应用
java·人工智能·node.js
:1212 小时前
java面试基础
java·开发语言
沫璃染墨2 小时前
C++ std::list 深度解析:迭代器、splice 核心接口与排序效率全解
开发语言·c++