数据结构和算法-笔记

数据结构和算法

  • 数据结构 + 算法 = 程序 —— 高纳德
  • 数据结构不是一门研究数据计算的学科,而是研究数据与数据之间的关系的

数据结构

术语
  • 数据:能够输入到计算机的描述客观事物的符号
  • 数据项:描述事物的其中一项指标
  • 数据元素:用于描述一个完整的事物
  • 数据结构:由数据元素和元素关系构成的一个整体
  • 算法:数据结构所具备的功能(解决问题的方法)
4种基本类型的数据结构(逻辑结构)
  • 集合:元素之间没有任何关系

  • 线性表:元素之间存在一对一关系(数组)

      数组,链表,功能受限的表(栈,队列)
    
  • :元素之间存在一对多关系

      普通树,二叉树,完全二叉树,满二叉树,有序二叉树
    
  • :元素之间存在多对多关系

      邻接表,表的遍历(深度优先,广度优先),最短路径
    
数据结构的存储方式(物理结构)
  • 顺序:在一块连续的内存里存储元素与元素之间的关系

    • 优先:速度快,不易产生内存碎片。
    • 缺点:对内存要求高,添加删除不方便
  • 非顺序:元素随机存储在内存空间,元素中存储指向其他元素的地址

    • 优点:对内存要求低,添加删除方便,查找速度慢(只能从头逐个遍历)
    • 缺点:容易产生内存碎片
逻辑结构和存储结构的关系
逻辑结构存储结构
顺序/链式
顺序/链式
混合

每种逻辑结构用什么物理结构存储并没有明确规定,通常是根据难易程度,时间,空间上的要求,选择合适的物理结构存储。

数据结构的运算
  1. 创建
  2. 销毁
  3. 添加元素
  4. 删除元素
  5. 修改元素
  6. 查找元素
  7. 排序
  8. 遍历
  9. 访问

[ 顺序表 ] (存储结构)
  1. 设计数据结构
  2. 分析所具备的算法
  3. 实现算法
  4. 使用算法
[ 链式表 ](存储结构)

每个元素都独立存储在内存中的任意位置,

功能受限的表

[ 栈 ](逻辑结构)

限制为只有一个端口进出元素,就导致先进后出的特性。

  • 顺序栈:

  • 链式栈:

一般用于,表达式解析,内存管理(函数的调用提供支持)。

[ 队列 ](逻辑结构)

限制为有两个端口进出元素,一个进一个出,先进先出。

  • 顺序队列:

  • 链式队列:


作业1:现有两个序列,1是入栈序,判断2是否是栈的出栈序

bool is_pop(int* in,int* out,size_t len);

作业2:使用两个栈实现一个队列的功能

作业3:链表逆序

作业4:显示链表的倒数第k个值

作业5:删除链表中重复的数据,只保留一份


作业6:检查链表中是否有环

作业7:查找环形链表的入口

作业8:检查两个链表是否是环形链表

作业9:合并两个有序链表,结束依然保持有序

通用链表

void* 万能指针,可与任何类型的指针互换。

int* = void*
void* = int*

需要把链表的数据域换成void类型。
注意:由于存储的类型不确定,因此类型的运算规则不确定,当需要使用到关系运算,需要链表的使用者提供运算(提供一个函数供链表调用),这叫回调

标准库中的qsort函数:

void qsort(void *base, size_t nmemb, size_t size,                 int (*compar)(const void *, const void *));           

练习1:仿照qsort函数,封装一个bsort函数

树是一种元素之间存在一对多关系的数据结构,长用于表示组织结构,辅助排序,查找等。

  • 术语:

    • 根:树的最顶层元素,有且只有一个。
    • 父结点(双亲):上一层元素。
    • 子结点(孩子):下一层元素。
    • 叶子结点:没有子结点的元素。
    • 兄弟结点:具有同一夫结点的元素。
    • 度:孩子的数量。
    • 高度:树的层数。
    • 密度:树的元素的个数。
    • 结点:一个元素就是一个结点。
普通树

孩子的数量无限制。

普通树的顺序存储
        A(0)
      /  |  \
   B(1) C(2) D(3)
      /  |  \
   E(4) F(5) G(6)
  • 对结点存储顺序无要求
下标 数据 父结点下表
 0    A    -1
 1    D    0
 2    C    0
 3    B    0
 4    E    2
 5    F    2
 6    G    2
  • 兄弟结点连续存储
下标 数据 父结点下标 第一个子结点下标
 0    A    -1         1
 1    B     0        -1
 2    C     0         4
 3    D     0        -1
 4    E     2        -1
 5    F     2        -1
 6    G     2        -1
  • 兄弟结点连续存储
下标 数据 父结点下标 第一个子结点下标 最后一个子结点下标
 0    A     -1           1              3
 1    B      0          -1             -1
 2    C      0           4              6
 3    D      0          -1             -1
 4    E      2          -1             -1
 5    F      2          -1             -1
 6    G      2          -1             -1
普通树的链式存储
typedef struct Node
{
    TYPE data;
    struct Node* brother;
    struct Node* child;
}
二叉树

最多只有两个孩子

  • 普通二叉树 :对二叉树的结点没有数量和位置的要求。

  • 完全二叉树 :若设二叉树的深度为h,除第h层外,其他各层的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。

  • 满二叉树 :除最后一层无子节点外,每一层上的所有结点都有两个子结点。

  • 有序二叉树 :左子树的所有结点都比双亲小,右子树的所有结点都比双亲大。

  • 平衡二叉树 :左右高度相差不超过1的有序二叉树,所有结点的左右子树。

相关术语
  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

根据遍历顺序构建二叉树

前中:已知前序和中序构建二叉树
后中:已知后序和中序构建二叉树
层序:空位置用#表示

二叉树的顺序存储:

公式:

2^(h-1) = 第h行的结点个数

2^(h-1)-1 = 前(h-1)行所有结点个数和

2^(h-1)+n-2 = 第h行第n个元素下标

二叉树的链式存储

作业

  1. 将一颗有序二叉树转换成一个有序双向链表

  2. 将一颗二叉树生成它的镜像树,在原树进行调整

  3. 有两颗二叉树A,B,判断B是否是A的子树

  4. 现有一棵树的前序遍历和中序遍历,重构二叉树
    {1,2,4,7,3,5,6,8}{4,7,2,1,5,3,8,6}

  5. 判断一个序列是否是二叉树的后续遍历

    bool is_post(int arr[],Node* root);

  6. 判断以可二叉树是否是平衡二叉树

元素之间存在多对多的关系(线性表的元素之间存在前驱和后继,树的元素之间存在符字关系,图的任意 元素之间都可能存在关系)

是由顶点的有穷非空集合和顶点之间边的集合组成的。

在图形数据结构中,数据被称为顶点,数据之间的关系被称为边。
在图中不允许出现没有点,但可以没有边。

G(V,E)
V表示顶点的集合,E表示边的集合。

各种图的定义
  1. 无向图:顶点与顶点之间没有方向,这种边称为无向边,边用无向序偶对表示(v,v1)

V ={A,B,C,D} E={(A,B),(B,C),(C,D),(D,A),(A,C)}

在无向图中,如果任意两个顶点之间都存在边,这种图称为无向完全图n*(n-1)/2

  1. 有向图:顶点之间有方向,这种边称为有向边,也叫弧,用有序偶对表示<v,v1>,v1叫做弧头,v叫做弧尾。

<A,B> != <B,A>

注意:若不存在顶点到自身的边,也不存在重复出现的边,这种图叫做简单图,数据结构课程中讨论的都是简单图。

在有向图中如果任意两个顶点之间存在方向相反的两条弧,这种图叫做有向完全图

图中有很少边或弧的图叫做稀疏图,反之叫稠密图

如果图中的边或弧有相关的数据,数据称为权,这种图也叫,(带权图)。

如果G(v,E)G1(v1,E1)存在V>=V1E>=E1,那么G1是G的子图

顶点与图的关系
  • 顶点的度:指的是顶点相关联的边或弧的条目数。有向图又分为出度和入度

    • 入度:其他顶点到该顶点的弧的数量
    • 出度:从该出发点到其他顶点的弧的条目数
  • 顶点序列:从一个顶点到另一个顶点的路径,路径长度是指路径上的边或者弧的条目数。

连通图的相关术语
  • 连通图:

在无向图中,在顶点vv1之间有路径,则称vv1之间是连通的,如果任意两个顶点都是连通的,那么这种图称为连通图。

  • 连通分量:

无向图中的极大连通子图称为连通分量:

  1. 必须是子图
  2. 子图必须是连通的
  3. 连通子图含有极大的顶点数
  • 强连通图:

在有向图中,任意顶点之间都存在路径,则称其为强连通图。

  • 强连通分量:

有向图中的极大连通子图称为有向的强连通分量。

  • 有向树:

在有向图图中如果有一个顶的入度为0,其他顶点的入度均为1,则是一颗有向树。

图的存储结构

图的存储主要是两个方面:顶点,边

邻接矩阵

一个一维数组(顶点)和一个二维数组(边,弧)

    A--B-C      
    |   /|
    |  E |
    | / /
    D---
ABCDE
A011
B101
C1011
D1101
E110

二维数组[i][i]都是0,如果是无向图则数组对称(左上到右下的对角线为轴)

  • 优点
    1. 非常容易判定两顶点之间是否有边
    2. 非常容易计算任意顶的入度和出度
    3. 非常容易统计邻接点
  • 缺点
    • 如果存储稀疏图,非常浪费存储空间
邻接表

由顶点表(顶点 邻接点的地址/下标),边表组成

顶点表边表
顶点下一个邻接点的地址顶点下标下一个邻接点的地址
A->[1]->[3]->NULL
B->[2]->NULL
C->[4]->[3]->NULL
D->
E->
  • 优点

    1. 节省存储空间
    2. 非常容易计算出度
  • 缺点

    • 不方便计算入度
十字链表

由于邻接表不能同时兼顾出度和入度,因此我们修改了邻接表的边表结构,使它既存储入度也存储出度。

邻接多重表

由于遍历表时需要一些删边操作,而邻接表在删除边时非常麻烦,因此就设计出邻接多重表。

边集数组

由两个以为数组构成,一个存储顶点信息,另一个存储边的信息(它的每个数据元素都由一条边的起点到终点的下标和权组成),这种存储结构更侧重于边的相关操作(路径,路径长度,最短路径),而统计顶的度需要扫描整个数组,效率不高。

图的遍历

注意:图的深度优先和广度优先都不是唯一的。

   A
  /|\
 C | B
  \|
   D
箭头从上到下

深度优先
类似树的前序遍历

广度优先
类似树的层序遍历,与树一样也需要


算法

数据结构中的算法,指的是数据结构所具备的功能
解决特定的问题,的方法,他是前辈们的一些优秀的经验总结

分治

把一个大而复杂的问题,分解成很多个小儿简单的问题,利用计算机的强大计算能力来解决问题。
实现分治的方法有:循环,递归。

递归

是函数自己调用自己的一种行为,可以形成循环调用,进而实现分治算法

什么时候使用递归

  • 问题过于复杂,无法拆分成循环语句。
  • 问题非线性,而函数的递归,由于借助线内存(函数的每一次调用,就会把数据重新押入到栈空间,它的数据都会保留下来。)可以解决非线性的问题(二叉树的相关算法,汉诺塔问题)。
  • 在单线程问题下,自能同时执行一个函数,当函数自己调用自己时(子级),会先执行子级的代码,然后子级执行完成后再返回到上一级继续执行。

如何安全实现递归

  • 使用递归非常有可能造成死循环,非常耗费资源。
  1. 先写出口,考虑如何调用无限的调用停止下来。
    if() return
  2. 解决一个小问题
  3. 把剩下的问题交给我的下一级(参数发生变化)

练习1:使用递归计算前n项斐波那契数列

练习2:使用递归解决汉诺塔问题n层盘子的移动过程

递归的优点:代码简单,容易理解
递归的缺点:容易形成死循环,耗费内存,执行效率低(参数入栈,出栈,局部变量的定义,销毁)。


查找

顺序查找

从头到尾逐一比较,对于要查找的数据每有要求,方法简单,在小规模的数据中比较常用。

int order_find(int* arr,size_t len, int key)

二分查找

int binary_find(int* arr,size_t len, int key)

前提是数据必须有序,然后从数据的中间位置找起。重复。
循环和递归都能实现

块查找,权重查找

适用于特殊条件下,需要对数据进行排序,分析,总结,归纳。


排序

排序算法的稳定性:当序列中有相等的数据时,算法会不会改变着两个数据的前后位置。

冒泡排序

是一种稳定排序,在排序过程中可以检测到数据是否已经有序(对数据的有序性非常敏感),可以立即停止,如果待排序的数据基本有序,则冒泡排序的效率是非常高的。

void bubble_sort(int* arr,size_t len)

插入排序

当一列数已经有序,再有加入的数据时,适合使用插入排序。

void insert_sort(int* arr,size_t len)

选择排序

是冒泡排序的一种变种,但是它没有冒泡排序对数据有序性的敏感,但它再排序过程中比冒泡少了很多数据交换,因此比冒泡排序快。数据较混乱时使用

void select_sort(int* arr,size_t len)

快速排序

一种基于交换,平均块

void quick_sort(int* arr,size_t len)

堆排序

首先把数组构当作完全二叉树,然后保障根节点最大,然后把根节点与最后一个元素交换,然后再调整二叉树(逐渐减小数组),让根依然保持最大,重复操作

void heap_sort(int* arr,size_t len)

归并排序

不交换数据,但需要借助额外的临时存储空间,所以速度可能比快速和堆更快

void merge_sort(int* arr, size_t len)


算法的时间复杂度

注意,时间复杂度并不指算法运行所需的时间,而是算法执行的次数。

排序算法平均时间复杂度最好最坏空间稳定性
冒泡   O ( n 2 ) \ O(n^2)  O(n2)   O ( n ) \ O(n)  O(n)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)稳定
直接插入   O ( n 2 ) \ O(n^2)  O(n2)   O ( n ) \ O(n)  O(n)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)稳定
直接选择   O ( n 2 ) \ O(n^2)  O(n2)   O ( n 2 ) \ O(n^2)  O(n2)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)不稳定
快速   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n 2 ) \ O(n^2)  O(n2)$\ $不稳定
  O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( 1 ) \ O(1)  O(1)不稳定
归并   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)$\ $稳定

merge_sort(int* arr, size_t len)`


算法的时间复杂度

注意,时间复杂度并不指算法运行所需的时间,而是算法执行的次数。

排序算法平均时间复杂度最好最坏空间稳定性
冒泡   O ( n 2 ) \ O(n^2)  O(n2)   O ( n ) \ O(n)  O(n)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)稳定
直接插入   O ( n 2 ) \ O(n^2)  O(n2)   O ( n ) \ O(n)  O(n)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)稳定
直接选择   O ( n 2 ) \ O(n^2)  O(n2)   O ( n 2 ) \ O(n^2)  O(n2)   O ( n 2 ) \ O(n^2)  O(n2)   O ( 1 ) \ O(1)  O(1)不稳定
快速   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n 2 ) \ O(n^2)  O(n2)$\ $不稳定
  O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( 1 ) \ O(1)  O(1)不稳定
归并   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)   O ( n l o g 2 n ) \ O(nlog_2n)  O(nlog2n)$\ $稳定
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值