数据结构与算法笔记

数据结构与算法简介

如何更好的编程,系统性的编程?
如何学好数据结构:
    1. 代码量 较多  多动手写  

数据结构与算法: 


    数据结构:  描述 从现实中 抽象出 数据模型   即 数据关系的抽象 
        逻辑结构: 数据之间的 逻辑关系   
            表: 线性结构 队列,栈,  数据之间是线性关系  一一对应             
            树: 非线性结构  1对多  
            图: 非线性结构  多对多 
        存储结构:  对数据 以及 数据关系的 存储 
            连续存储:  将所有数据放到一起  数组 
            离散存储:  数据可能分布在内存的 不同位置 
    对数的操作(算法): 
        不是简单的数值操作 而是一些 逻辑操作 
        增: 增加数据 
        删: 删除数据 
        改: 修改数据 
        查: 查找或遍历数据  


    算法        


算法: 就是对 某个动作的 操作流程 或步骤              
    算法特性: 
        1. 可行性    可以实现  每个计算步骤能够在有限的时间内完成
        2. 确定性    没有歧义  步骤唯一且确定 
        3. 有穷性    有明确的目标且可到达 步骤是有限的  
        4. 有一个或多个输入 
        5. 有一个或多个输出 
        
求两正整数m、n的最大公因子的算法如下:

① 输入m,n;
② m/n(整除),余数→r (0≤r≤n);
③ 若r=0,则当前n=结果,输出n,算法停止;否则,转④;
④ n→m,r->n; 转②。

如初始输入 m=10,n=4,则m,n,r 在算法中的变化如下:
m   n   r
10  4   2
4   2   0(停止)
即10和4 的最大公因子为2。
        余
m   n   r
20 15   5
15  5   0
        r
15  20  15
20  15  

求 1+2+3+...n = ???
算法1:                       语句频度T      量级 O  
for(i=1;i<=n;i++) sum+= i;      n+1           O(n)    
算法2:
sum = (1+n)*n/2                  1            O(c)   常数量级 
    
    算法优劣评判标准: 
    时间复杂度:  语句执行的频度   与算法程序执行的时间 没有必然关系 
        执行的频度:  算法中 语句执行的次数 与 输入问题规模的 比值 
        度量一个算法的时间复杂度  通常使用 其 量级表示  
    
冒泡排序:                             语句频度 
    for(i=1;i<n;i++)                 n
        for(j=1; j<n-i-1; j++)       (n)(n / 2)
            if( a[j] > a[j-1] )      n(n/2) 
                {交换a[j] a[j-1]}

                                    n^2 + n     量级 O(n^2)
    
常见的 时间复杂度的  量级 
    最好的  O(1)  常量级     算法语句执行次数 与 输入问题规模 无关 
    
    较好
           O(log n)  对数级        
           O(n)      线性级
    一般   O(n*log n)
    较差   O(n^2)    平方级 
    差       O(2^n)    次方级 
           O(n!)     阶乘级  
        
    空间复杂度:  占用内存的多少
        交换两个数   
杨辉三角:第i行j列的值         
    A(i,j) =  i!/(j!*(i-j)!) 

常见数据数据: 

数据结构——表    

1. 表结构   线性结构 
        数据逻辑组织为表形式,逻辑上,数据是连续排列的
        每个节点(除头尾外),都有且仅有一个 前驱 和 一个后继
        头节点  只有后继 没有前驱 
        尾节点    只有前驱 没有后继 
    
    节点: 数据逻辑的最小单元  
    
    C中 数组 就是 表结构 
    
    存储结构: 
        顺序存储:  数组存储  
            顺序表 :存储上连续存储,逻辑上是表结构 
        离散存储:  链式存储  
            链表:   存储上使用离散存储, 逻辑上是表结构 

顺序表: 


    C语言程序模型:
    表结构定义:
//1 定义节点类型  假设为int 
typedef int  Data_t;

//2 定义顺序表 结构
//动态长度
typedef struct list_t
{
    int max_len; //存储顺序表的最大长度
    int cnt;     //存储当前表中节点个数
    Data_t *data;//存储节点的数组动态分配长度
};

//固定长度 
define  MAXLEN  20
typedef struct list_t
{
    int cnt;     //存储当前表中节点个数
    Data_t data[MAXLEN]; //静态分配数组长度
};

struct list_t list; 

    顺序表的操作:
    创建表:   动态长度表为例      
int    create_list(struct list_t *head ,int max_len);
    
    销毁表
int del_list(struct list_t *head);

...    
    增: 增加节点   插入数据 
int insert_list(struct list_t *head, int index, Data_t data);
    
作业    删: 删除节点
    
    改: 修改节点值  
    
    查: 遍历   获取某个节点数据 

    判空
    判满
    获得长度 
    表A += 表B:  将b表中的元素 添加A表末尾;
    ....
    

链表


    表的 离散存储  链表 :
    
    逻辑结构 表结构  通过指针方式实现 逻辑上的链接 
        
//1. 定义数据类型  假设为int
typedef int Data_t;

//2. 定义节点类型
typedef struct node_t 
{
    Data_t data;        //数据域:存储数据本身的
    struct node_t *next;//指针域:存储逻辑关系
}node_t;    

链表中的一些情况的表示: 
    链表结尾节点 表示:   指针域 == NULL 
    空链表   表示    :   使用一个 不存储数据的 节点 作为头节点. 
                        当头节点的 指针域 == NULL 表示空链表  
    
    创建空链表: 
//返回值 失败返回 NULL 
//成功 返回 链表头节点地址
node_t * create_link_list(void);

    插入数据: 
int insert_data(node_t *head, int index, Data_t data);

    删除数据:   
    1.给定一个下标, 删除该节点 
    int del_data(node_t *head, int index);
    2.给定节点的地址, 删除该节点 
作业:int del_data_byaddr(node_t *head, node_t* delp);

    改:
    修改下标位置的值 
作业:int change_data(node_t* head, int index , Data_t data);
    
    链表拼接 : 
    int connect_link_list(node_t* head1, node_t* head2);
    
    链表翻转: 倒叙
    int link_list_Reverse(node_t* head);
    
    1. 头 空链表   身体 
    2. 遍历身体 头插法 到头中 
    
作业:    
    查: 
    给定一个下标 index 给出其 节点地址或值 
    给定一个数据data  查询该数据 所在的节点下标 或 节点地址 

链表与顺序表的 优劣:
     链表根据节点长度动态分配,没有长度限制
    链表的插入和删除节点,时间复杂度 比顺序表优秀  不会存在大片数据整体移动的情况
    
    顺序表:  
        存储密度高  不会有指针域 浪费空间 
        顺序表的 随机访问效率高 
        
链表应用:
    约瑟夫环问题: 
    参考示例 约瑟夫环问题.c

静态链表:  链表的节点 事先已经开辟了(节点数组), 动态下标或指针域来了 建立逻辑关系 

  数据结构——队列  

  
队列:  是一种特殊的线性表 
    规定了 表的入口和出口  同时 先进先出 
    多用于 缓冲 
        
队列的实现:
    顺序存储的队列:  循环队列 
    离散存储的队列:  链式队列 
    
    离散方法的链式队列:  双向循环链表 
        作业:  参考 模型图
    
循环队列:实现

    1. 创建空队列 
    2. 销毁队列 
    3. 入队 
    4. 出队
    5. 置空队列 
    6. 判空判满 
    7. 获得队列长度    
        

数据结构——栈


栈:
    一种特殊 的 线性表, 
    只能在表的 一端 进行插入和删除 
    后进先出  
    
    顺序栈 :  数组   从数组尾部进行插入和删除 
    链式栈 :  链表   从头部插入和删除

顺序栈: 

   
4种类型的栈:     
    增栈 : 入栈时  栈顶指针 向大地址方向移动 
    减栈 : 入栈时  栈顶指针 向小地址方向移动 
    空栈 : 栈顶指针 指向的位置数据 无效(空) 
    满栈 : 栈顶指针 指向的位置数据 有效(满)
    
    空增栈:
    满增栈:
    空减栈:
    满减栈:
    
哈希表:     不是线性表   方便查找而设计的 
    门牌号:  407 
    张三
    张 z --> s --> 顺序查找  
    章 z
    
链表的思考题: 
    已知一个单向链表的某个节点地址 p  如何判断 这个链表中是否有环 
    使用一个辅助表, 将变列过的节点地址 放入该表 
    每次移动节点指针p时 对该表进行比较 若p出现在该表中 则存在环 
    
    表 去除重复数据
    
    已知一个单向链表的某个节点地址 p  p不是最后一个节点  
    问 在不知道头节点地址的 情况下 能否删除该节点  若能 什么方法?
    将后一个节点数据 移动到 p节点数据域     p->data = p->next->data;
    备份q   q = p->next;
    修改指向  p->nxext = q->next;
    释放 p的后一个节点  free(q);
    

数据结构——树


树: 
    一个节点 有0个或多个 直接后继 节点 
    有1个(子节点)或 0个(根节点) 直接前驱  
    不能有环结构
    
    目录结构 典型数结构 
    
根节点:        树的起始位置  一棵树 一定有根节点 
子节点:        树结构中 除根节点外 其他节点都称作 子节点 
叶子节点:    特殊的子节点, 没有后继的 子节点 

树的度:        以树中 直接后继最多的节点 个数 称该树的 度 
树的深度:    从根开始 到每一个叶子节点 最长的经过的节点 个数 
            即树的层数

树的路径:   从根节点开始  到目标节点 经过的节点顺序  

子树:       一颗树 中的 一部分节点 构成的一颗 树  称该树是 其子树 
    

二叉树


数据结构中 主要研究  二叉树:

二叉树: 一个度为2的树  
    1. 二叉树 有0个或1个或2个 直接后继节点  且 严格区分左右  即使只有一个子节点也要区分左右。
    2. 一个二叉树 其 子树 也一定是二叉树 
    
二叉树(Binary Tree )是n(n≥0)个节点的有限集合,它或者是空集(n=0),
或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。
二叉树与普通有序树不同,二叉树严格区分左孩子和右孩子,
即使只有一个子节点也要区分左右。
    
二叉树的特例:
满二叉树:  除叶子节点外 其他有所节点 都有两个 直接后继  且 叶子节点都在最后一层
 
完全二叉树: 只有最下面两层有度数小于2的节点,
            且最下面一层的叶节点集中在最左边的若干位置上。

    除最底层 没有拍满外  其他层 均 盘满  且 每一层都需要按照先左后右的顺序排列 

带权二叉树: 
    二叉树的 路径上 带有 权值 
    
应用:
    3+3-5*0 
    
二叉树的一些特性: 
    二叉树的性质:
  1.在二叉树的第i层上至多有2的i-1次方个结点  2^(n-1)
  2.深度为k的二叉树至多有2的k次方-1个结点    2^k -1
  3.对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点n2,那么n0=n2+1
        叶子节点的 个数  始终比 度为2的节点个数多 1个
   
  4.具有n个结点的完全二叉树的深度为 = (log2n)+1  取整数 
        第n号节点  是  (log2n)+1  取整  行
        
  5.对一棵有n个结点的完全二叉树的结点按层序编号(从第一层到最后一层,每层从左至右),
    对任一结点i(1<i<n)有:
     a.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点i/2 取整;
     b.如果2i>n,则结点i无左孩子(即为叶子结点);否则其左孩子结点是2i;(偶数个结点,最后一个叶子结点一定是左)
     c.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1;(奇数个结点,最后一个叶子结点一定是右)

二叉树的存储: 
    1. 顺序存储:    将二叉树 补全为 完全二叉树  按 从左到右 从上到下
                    依次进行编号, 这个编号即数组下标  
                    
    2. 离散存储:   
    
构造节点: 
    
    
二叉树的遍历: 
    1. 层次遍历 广度遍历 
        使用一个临时队列 辅助
    2. 深度遍历  
        先序遍历    先根 后 左右     判断根节点     前 
        中序遍历    先左 根 右         判断左右子树   中
        后序遍历    先左 右 根       判断根节点     后 

    先序或后序遍历 可以确定根节点   先序第一个就是根节点, 后续最后一个是根节点 
    中序遍历 可以确定左右子树 

练习: 
    参考示例代码  tree.c 
    创建 
    删除 树
    遍历 
思考:     
    查找 节点  
    改节点值   给节点地址  
    插入节点   给节点地址 l r  要插入的数据 
    
    删除节点 

作业:  
    1. 已知一个二叉树 
        中序遍历:B D C G F A H I 
        后序遍历:D G F C B I H A 
        
    画出这个二叉树结构
        求先序遍历:
        
思考: 
    二叉树的层次遍历
    
二叉树的一些应用:
赫夫曼(Huffman)树,又称最优树,是带权路径长度最短的树

    情景:  "helloworld"  通过电报发送   问题 如何编码 可以使得这句话 发送的01最少 
    等长编码:
        ASCII:  10字节  80bit 
        4bit编码:
        h:0000
        e:0001
        l:0010
        ....
        d:
                        40bit
    变长编码:   二叉树 
        赫夫曼编码      27bit 


 数据结构——图     

 
图: 网结构 
    是一种比表更复杂的数据结构 
    树 是 特殊的图  
    图  有多个直接前驱 有多个 直接后继 
专业术语:
顶点:  图(网)中 存储数据的节点     
边/弧:  顶点与顶点间的关系  没有方向称边  有方向称弧 
    
边/弧的权值:   从一个顶点到另一个顶点的代价

子图:  图中的一部分节点以及这些节点的边 够成子图 

 路径:从一个顶点到另一顶点所经过的边
 简单路径:路径中所经过的顶点没有重复的
 
有向图: 边有方向
无向图: 边没有方向
有环图:有路径构成环
无环图:没有路径可以构成环    
带权图: 边有权值 
无权图: 边没有权值 
连通图: 任意两个顶点都有路径可以到达 
非连通图:  如图所示 

一个连通图  有n个节点   有n(n-1)/2 边    有n(n-1)条弧 

图的存储: 
    顺序存储:  节点: 数组   边: 邻接矩阵
        对稠密图 或 节点较少的图 
        消耗内存较多 
        
    离散存储:  邻接链表  
               十字链表 

稠密图:  边较多
稀疏图:  边较少 
完全图:  边 = n(n-1) 或 n(n-1)/2   有所有的边 

图解决实际问题: 
    寻路算法: 最短路径  Dijkstra算法
    
常见查找与排序算法: 
    索引查找:  目录   给数据设计一个目录  先查目录 
    哈希查找:  将目标 进行哈希, 得到哈希值  即存储位置  

    数据有序时:    
        二分法查找: 
        arr = {1,2,5,7,9,11,17,20,30,55,97};
                        (len-1)/2 
    查20的下标:     
        
写代码:
    
    int find(int data, int*arr, int start, int end);

//二分查找方式2
//data:要找的数
//arr :数组
//len :数组长度
//返回值: NULL没有找到 非空找到目标的地址
int * find2(int data, int *arr, int len)
{
    if(len <= 0) return NULL;
    int * p = arr + len / 2; 
    if(*p == data) return p;
    if(*p > data) return find2(data,arr, p - arr );
    else return find2(data, p+1, len - (p-arr)-1 );
}

排序: 
    冒泡排序:  O(N^2)
    选择排序:
    插入排序:
    归并排序: 
作业:     
归并: 将两个 有序数组  合成一个有序数组 
    
    快速排序:  快  O(N*log2N)  
    arr = {68,90,77,18,25,66,23,51,99}
    算法如图所示
    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值