数据结构基础知识

1.数据结构概述

算法

  • 解题的方法和步骤
  • 衡量算法的标准
  • 时间复杂度:大概程序要执行的次数,而非执行的时间
  • 空间复杂度:算法执行过程中大概所占用的最大内存
  • 难易程度
  • 健壮性

数据结构的地位

数据结构是软件中最核心的课程
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

2.预备知识

指针

指针是c语言的灵魂
无论指针变量指向的变量占多少个字节,指针统一只占8个字节(64位电脑)

定义

  • 地址:内存单元的编号,从0开始的非负整数
  • 范围:0-FFFFFFFF[4G-1](32位电脑)
  • 指针:指针和指针变量
    指针就是地址,地址就是指针
    指针变量:是存放内存单元地址的变量
    指针的本质是一个操作受限的非负整数

分类

基本类型的指针
  • 基本概念
int i = 10;
int * p = &i; 

详解这两步操作
1) p存放了i的地址,所以我们说p指向了i
2) p和i是完全不同的两个变量,修改其中的任意一个变量的值不影响另一个变量
3) p指向i,p就是i变量本身,更形象的说所有出现p的地方可以换成i,所有出现i的地方可以换成*p
总结:

  • 如果一个指针变量(假定为p)存放了某个普通变量的地址,那我们就可以说p指向i,但p与i是两个不同的变量

  • p等价与i,或者说p可以与i在任何地方互换

  • 如果一个指针变量指向了某个普通变量,则*指针变量就完全等价于普通变量。

  • 注意
    指针变量也是变量,只不过它存放的不能是内存单元的内容,只能存放内存单元的地址,普通变量前不能加*
    常量和表达式前不能加&

  • 如何通过修改被调函数修改主调函数中普通变量的值

  • 实参为相关变量的地址

  • 形参为该变量的类型为类型的指针变量

  • 在被调函数中通过*形参变量名的方式可以修改主函数中普通变量的值

指针和一维数组
  • 数组名
    一维数组名是个指针常量,它存放的是一维数组第一个元素的地址,它的值不能被改变,一维数组名指向的是数组的第一个元素
  • 下标和指针的关系
    a[i] 等价于 (a+i)
    假设指针变量的名字为p
    则p+i的值是p+i
    (p所指向的变量所占的字节数)
  • 指针变量的运算
    指针变量不能相加,不能相乘,不能相除
    如果两个指针变量属于同一数组,则可以相减
    指针变量可以减一整数,前提是最终结果不能超过指针
    P+i的值是p+i*(p所指向的变量所占的字节数)
    p-i的值是p-i*(p所指向的变量所占的字节数)
    p++等价于p+1
    p—等价于p-1
  • 举例-如何通过被调函数修改主调函数中一维数组的内容
    两个参数
    存放数组首元素的指针变量
    存放数组元素长度的整型变量

动态内存的分配和释放

动态构造一维数组

假设动态构造一个int型数组
Int * p = (int *)malloc(int len)

  • malloc只有一个int型的形参,表示要求系统分配的字节数
  • malloc函数的功能是请求系统分配len个字节的空间,如果请求成功,则返回第一个字节的地址,如果分配失败,则返回NULL
  • malloc函数能且只能返回第一个字节的地址,所以我们需要把无实际意义的第一个字节的地址(俗称干地址)转化为一个有实际意义的地址,malloc前面必须加(数据类型*),表示把这个无实际意义的地址强制转化为相应类型的地址
    如:
    Int * p = (int *)malloc(50)
  • 判断下面这个程序中的指针p是否指向合法的整型变量
int main()
{ int * p;
fun(&p)}
Int fun(int **q)
{int s;
*q=&s;
}

不能,在fun函数运行时p指向了s,但当fun运行结束,s的内存单元释放,此时p无法指向合法的整型单元

结构体

为了表示一些复杂的数据结构,而普通的基本类型变量无法满足要求,结构体是用户根据实际需求,自己定义的复合数据类型
两种方式:
struct Student st = {1000,“张三”, 34};
struct Student * pst = &st;

  • st.sid
  • pst->sid
    pst->sid表示pst所指向的结构体变量中的sid这个成员
    注意事项:
    结构体变量不能加减乘除,但可以相互赋值
    普通结构体变量和结构体指针变量作为函数传参的问题

3.模块一:线性结构

线性结构:【把所有的结点用一根线穿起来】

连续存储【数组】

什么叫数组

元素类型相同,大小相等

数组的优缺点

  • 优点:存取速度很快
  • 缺点:插入删除元素很慢,空间通常有限制;
    事先必须知道数组的长度,需要大块连续的内存

创建与数组相关的函数

初始化
增加元素
插入元素
删除元素
判断数组是否为空
判断数组是否为满
排序
打印数组元素
倒置数组

离散存储【链表】

定义

N个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点,每一个节点只有一个后续节点
首节点没有前驱节点,尾节点没有后续节点
链表

专业术语

  • 首节点
    第一个有效节点

  • 尾节点
    最有一个有效节点

  • 头节点
    头节点的数据类型和首节点类型一样
    第一个有效节点之前的那个节点
    头节点并不存放有效数据

  • 头指针
    指向头节点的指针变量

  • 尾指针
    指向尾节点的指针变量

  • 如果希望通过一个函数对链表进行处理,我们至少需要接收链表哪些参数
    只需要一个参数:头指针
    因为我们通过头指针可以推算出链表的其他所有信息

分类

  • 单链表
  • 双链表
    每一个节点有两个指针域
  • 循环链表
    能通过任何一个节点找到其他所有的节点
  • 非循环链表

算法

遍历
查找
清空
销毁
求长度
排序
删除节点
插入节点

  • 狭义的算法是与数据的存储方式密切相关
  • 广义的算法与数据的存储方式无关
  • 泛型:利用某种技术达到的效果就是:不同的存储方式执行的操作是一样的

链表的优缺点

  • 连续存储【数组】
    优点:存取速度很快
    缺点:插入删除元素很慢,空间通常有限制
    事先必须知道数组的长度,需要大块连续的内存
  • 离散存储【链表】
    优点:空间没有限制
    缺点:存取速度很慢

线性结构的两种常见应用之一:栈

定义

静态内存在栈里分配
动态内存在堆里分配
栈:一种可以实现“先进后出”的存储结构
栈类似于箱子,弹夹
栈的本质就是操作受限的链表

分类

静态栈
动态栈

算法

定义栈

pTop:指向栈顶元素
pBottom:指向栈底(非有效节点)

定义栈

初始化栈

初始化栈

压栈

压栈

遍历栈

遍历栈

出栈

出栈
清空栈
清空栈

应用

函数调用
中断
表达式求值
内存分配
缓冲处理
迷宫

线性结构的两种常见应用之二:队列

定义

一种可以实现“先进先出”的存储结构

分类

  • 链式队列:用链表实现
    链式队列

  • 静态队列:用数组实现
    静态队列通常必须是循环队列实现
    静态队列
    循环队列的讲解:

  • 静态队列为什么必须是循环队列
    传统数组实现会造成空间浪费
    循环队列类似于卷积

  • 循环队列需要几个参数确定及其含义
    需要2个参数front和rear,2个参数在不同场合有不同的意义(建议初学者先记住,之后慢慢体会)
    1) 队列初始化
    front和rear的值都是零
    2) 队列非空
    front代表的是队列的第一个元素
    rear代表的是队列的最后一个有效元素的下一个元素
    3) 队列空
    front和rear的值相等,但不一定是零

  • 循环队列入队伪算法讲解
    两步完成:
    将值存入r所代表的位置
    正确的写法:r =( r+1)%数组的长度(r = r+1 ;这样写肯定是错误的)

  • 循环队列出队伪算法讲解
    f=(f+1)%数组的长度

  • 如何判断循环队列是否为空
    如果front和rear的值相等,该队列就一定为空

  • 如何判断循环队列是否已满
    预备知识
    Front的值可能比rear大,也完全有可能比rear小,当然也可能相等
    1)多增加一个标识参数
    2)少用一个元素[通常使用第二种方式]
    如果r和f的值紧挨着,则队列已满
    If ((r+1)%s数组长度 == f)
    已满
    Else
    不满

队列算法

入队
出队

队列的具体应用

所有与时间有关的操作都有队列的影子

4.专题:递归

定义

一个函数自己直接或间接调用自己

举例

  • 1+2+3+4…+100
  • 求阶乘
  • 汉诺塔
    汉诺塔
    伪算法:
    If (n>1)
    {
  • A借助C将前n-1个盘子移动到B
  • A将第n个盘子移动到C
  • B借助A将前n-1个盘子移动到C
    }
  • 走迷宫
    将地图变成非常小的格子

递归满足的三个条件

  • 递归必须得有一个明确的终止条件
  • 该函数所处理的数据规模必须递减
  • 这个转化是可解的

递归三部曲

  • 确定递归函数的参数和返回值
  • 确定终止条件
  • 确定单层递归的逻辑

循环和递归

  • 递归:

易于理解
速度慢
存储空间大

  • 循环:

不易理解
速度快
存储空间小

递归的应用

树和森林就是以递归的方式定义的
数和图的很多算法都是以递归来实现的
很多数学公式就是以递归的方式定义的

5.模块二:非线性结构

树定义

专业定义:

  • 有且只有一个成为根的节点
  • 有若干个互不相交的子树,这些子树本身也是一棵树
    通俗定义:
  • 树是由节点和边组成
  • 每一个节点只有一个父节点但可以有多个子节点
  • 但有一个节点例外,该节点没有父节点,此节点称为根节点

树分类

  • 一般树:任意一个节点的子节点的个数不受限制
  • 二叉树:任意一个节点的子节点的个数最多两个,且子节点的位置不可更改
    分类:
    一般二叉树:
    满二叉树:在不增加树的层数的情况下,不能再增加节点的树
    完全二叉树:如果只是删除满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树。满二叉树是完全二叉树的一个特例。
  • 森林:n个互不相交的树的集合

树的存储

二叉树的存储

连续存储【完全二叉树】
优点:查找某个节点的父节点和子节点速度很快(也包括判断有没有子节点)
缺点:耗用的内存空间过大
链式存储

一般树的存储
  • 双亲表示法:求父节点方便
  • 孩子表示法:求子节点方便
  • 双亲孩子表示法:求父节点个子节点都方便
  • 二叉树表示法:把一个普通数转化成二叉树来存储
    具体转换方法:
    设法保证任意一个节点的
    左指针域指向它的第一个孩子
    右指针域指向它的下一个兄弟
    只要能满足此条件,就可以把一个普通树转换成二叉树
    一个普通树转化成的二叉树一定没有右子树(左孩子,右兄弟)
森林的存储

先把森林转化成二叉树,再存储二叉树

树操作

树的遍历
  • 先序遍历【先访问根节点】

先访问根节点
再先序访问左子树
再先序访问右子树

  • 中序遍历【中间访问根节点】

中序遍历左子树
再访问根节点
在中序遍历右子树

  • 后序遍历【最后访问根节点】

中序遍历左子树
中序遍历右子树
再访问根节点
树的遍历

已知两种遍历序列求原始二叉树

通过先序和中序或者中序和后序我们可以还原出原始的二叉树
但是通过先序和后序是无法还原出原始的二叉树
换种说法:
只有通过先序和中序或通过中序或后序
我们才可以唯一的确定一个二叉树

  • 已知先序和中序还原原始的二叉树
    已知先序和中序还原原始二叉树
    总结:根据先序确定根节点,根据中序确定左子树和右子树

  • 已知中序和后序还原原始的二叉树
    已知中序和后序还原原始的二叉树
    总结:根据后序确定根节点,根据中序确定左子树和右子树

树的应用

树是数据库中数据组织一种重要形式
操作系统子父进程的关系本身就是一棵树
面向对象语言中类的继承关系
赫夫曼树

图论

6.模块三:查找和排序

  • 折半查找
  • 排序
    冒泡
    插入
    选择
    快速排序
    归并排序

排序和查找的关系

排序是查找的前提
排序是重点

7.数据结构和泛型

  • 数据结构

数据结构是研究数据的存储和数据的操作的一门学问
数据的存储分为两部分:
个体的存储
个体关系的存储
从某个角度而言,数据的存储最核心的就是个体关系的存储
个体的存储可以忽略不计

  • 泛型
    同一种逻辑结构,无论该逻辑结构的物理存储是什么样子的
    我们都可以对它执行相同的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值