数据结构笔记
1.数据结构概述
定义
我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也叫算法
数据结构=个体+个体关系
算法=对存储数据的操作
算法
解题的方法和步骤
衡量算法的标准
1.时间复杂度
大概程序要执行的次数,而非执行的时间
2.空间复杂度
算法执行过程中大概所占用的最大内存
3.难易程度
4.健壮性
数据结构的地位
数据结构是软件中最核心的课程
程序=数据的存储+数据的操作+可以被计算机执行的语言
2.预备知识
- 指针
指针的重要性:
指针是c语言的灵魂
定义:
地址
内存单元的编号
从0开始的非负整数
范围:0--FFFFFFFF【0~4G-1】
指针:
指针就是地址,地址就是指针
指针变量是存放内存单元地址的变量
指针的本质是一个操作受限的非负整数
分类
1.基本类型的指针
基本概念
int i=0;
int *p=&i;//等价与int *p;p=&i;
详解这两部操作
1)p保存i的地址,所以P指向了i
2)p和i 是完全不同的两个变量,修改p的值不影响i的值,修改i的值不影响p的值,
3)p指向i,*p=i 即变量i本身。
注意:
指针变量也是变量,只不过它存放的不能使内存单元的内容,只能存放内存单元的地址
普通变量前不能加*
常量和表达式前不能加&
如何通过被调函数修改主调函数中普通变量的值
Ⅰ 实参为相关变量的地址
Ⅱ 形参为以该变量的类型为类型的指针变量
Ⅲ 在被调函数中通过 *形参变量名 的方式就可以修改主函数
2.指针和数组(一维)的关系
指针和一位数组
数组名
一维数组名是个指针常量
它存放的是一维数组第一个元素的地址
它的值不能被改变
一维数组名指向的是数组的第一个元素
下标和指针的关系
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
举例
如何通过被调函数修改主调函数中一维数组的内容
答:
两个参数:存放数组首元素的指针变量
存放数组元素长度的整型变量
无论什么类型的变量,要改写它的值,只要放入地址就可以了
- 结构体
为什么会出现结构体:为了表示一些复杂的数据,而普通的基本类型变量无法 满足要求
什么叫结构体:结构体是用户根据实际需要自己定义的复合数据类型明年
如何使用结构体
两种方式
struct Student st={1000,"zhangsan",20};
struct Student *pst=&st;
1.st.sid
2.pst->sid
pst所指向的结构体变量中的sid这个成员
注意事项:
结构体变量不能加减乘除,但可以相互赋值
普通结构体变量和结构体指针变量作为函数传参的问题
- 动态内存的分配和释放
3.模块一:线性结构【把所有的结点用一根直线穿起来】
连续存储【数组】
1.什么叫数组
元素类型相同,大小相等
2.数组的优缺点:
离散存储【链表】
typedef为一个变量类型起名字
定义:
n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点和后续节点
首节点没有前驱节点,尾结点没有后续节点
专业术语:
首节点:第一个有效节点
尾节点:最后一个 有效节点
头结点:头结点的数据类型和首节点类型一样;
第一个有效节点之前的那个节点;
头结点并不存放有效数据;
加头结点的目的是为了方便 链表的操作
头指针:指向头结点的指针变量
尾指针:指向尾结点的指针变量
如果希望通过一个函数来对链表进行处理,我们至少需要接受链表的哪些参数:
只需要一个参数:头指针
因为我们通过头指针可以推算出链表的其他所有信息
分类:
单链表
双链表:每一个节点有两个指针域
循环链表:能通过任何一个节点找到其他所有的节点
非循环链表
算法:
狭义的算法是与数据的存储方式密切相关
广义的算法是与数据的存储方式无关
泛型:
利用某种技术达到的效果就是:不同的存储方式,执行的操作是一样的
遍历
查找
清空
销毁
求长度
排序
删除节点
p->pNext //p所指向结构体变量中的pNext成员本身;
链表的优缺点:
数据结构复习:
狭义:
数据结构是专门研究数据存储的问题
数据的存储包含两方面:个体的存储+个体关系的存储
广义:
数据结构既包含数据的存储也包含数据的操作
对存储数据的操作就是算法
算法:
狭义:算法是和数据的存储方式密切相关
广义:
算法和数据的存储方式无关
这就是泛型思想
数据的存储结构有几种
线性
连续存储【数组】
优点:存取速度很快
缺点:插入删除元素很慢;
空间通常是由有限制的;
实现必须知道数组的长度;
需要大块连续的内存块
非连续存储【链表】
优点:空间没有限制;插入删除元素很快
缺点:存取速度很慢
线性结构的应用--栈
线性结构的应用--队列
非线性
树
图
线性结构的两种常见应用之一 栈
定义:一种可以实现“先进后出'的存储结构
栈类似于箱子
分类:
静态栈
动态栈
算法
压栈
出栈
应用
函数调用
中断
表达式求值
内存分配
缓冲处理
迷宫
(动态分配在堆里分配,静态分配在栈内分配)
线性结构的两种常见应用之二 队列
定义:
一种可以实现“先进先出”的存储结构
分类:
链式队列---用链表实现
静态队列---用数组实现
静态队列通常都必须是循环队列
循环队列的讲解:
1.静态队列为什么必须是循环队列
2.循环队列需要几个参数来确定
需要2个参数来确定 及其含义
front
rear
3.循环队列各个参数的含义
2个参数不同场合有不同的含义
建议初学者先记住,然后慢慢体会
1)队列初始化
front和rear的值都是零
2)队列非空
front代表的是队列的第一个元素
rear代表的是队列的最后一个有效元素的下一个元素
3)队列空
front和rear的值相等,但不一定是零
4.循环队列入队伪算法讲解
两步完成:
1.将值存入r所代表的位置
2.错误的写法r=r+1;
正确的写法r=(r+1)%数组的长度
5.循环队列出队伪算法讲解
f=(f+1)%数组的长度
6.如何判断循环队列是否为空
如果front与rear的值相等,则该队列就一定为空
7.如何判断循环队列是否为满
预备知识:
front的值可能比rear大,
也完全有可能比rear小,
当然也可能两者相等
两种方式:
1.多增加一个标识参数
2.少用一个元素【通常使用第二种方式】
如果r和f的值紧挨着,则队列已满
if((r+1)%数组长度==f)
已满
else
不满
队列算法
入队
出队
队列的具体应用:
所有和时间有关的操作都有队列的影子
rear指向当前队列的下一个元素,front指向当前元素。
专题:递归
定义:
一个函数自己直接或间接调用自己
递归满足三个条件
1.递归必须得有一个明确的中止条件
2.该函数所处理的数据规模必须在递减
3.这个转化必须是可解的
循环和递归
递归:
易于理解
速度慢
存储空间大
循环:
不易理解
速度快
存储空间小
举例:
1.1+2+3+4。。。+100的和
2.求阶乘
3.汉诺塔
4.走迷宫
递归的应用:
树和森林就是以递归的方式定义的
树和图的很多算法都是以递归来实现的
很多数学公式就是以递归的方式定义的
斐波拉契序列:
模块二:非线性结构
树
树定义
专业定义:
1.有且只有
2.有若干个互不相交的子树,这些子树本身也是一棵树
通俗的定义:
1.树是由节点和边组成
2.每个节点只有一个父节点但可以有多个子节点
3.但有一个节点例外,该节点没有父节点,此节点称为根节点
专业术语
节点 父节点 子节点
子孙 堂兄弟
深度:
从根节点到最底层节点的层数称之为深度
根节点是第一层
叶子节点:
没有子节点的节点
非终端节点:
实际就是非叶子节点
度
子节点的个数称为度
树分类
一般树
任意一个节点的子节点的个数都不受限制
二叉树
任意一个节点的子节点个数最多两个,且子节点的位置不可更改
分类:
一般二叉树
满二叉树
在不增加树的层数的前提下,无法再多增加一个节点的二叉树就是满二叉树
完全二叉树
如果只是删除了满二叉树最底层最右边的连续若干个,这样形成的二叉树就是完全二叉树
森林
n个互不相交的树的集合
树的存储
二叉树的存储
连续存储[完全二叉树]
优点:
查找某个节点的父节点和子节点(也包括判断有没有孩子节点)
缺点:
耗用内存呢空间过大
链式存储
一般树的存储
双亲表示法
求父节点方便
孩子表示法
求子节点方便
双亲孩子表示法
求父节点和子节点都很方便
二叉树表示法
把一个普通树转化成二叉树来存储
具体转换方法:
设法保证任意一个节点的左指针域指向第一个孩子
右指针域指向它的兄弟
只要能满足此条件,就可以把一个普通树转换为二叉树
一个普通树转化成的二叉树一定没有右子树
森林的存储
先把森林转换为二叉树,再储存二叉树
二叉树操作
遍历
先序遍历【先访问根节点】
先访问根节点
再先序访问左子树
再先序访问右子树
中序遍历【中间访问根节点】
先中序访问左子树
再访问根节点
再中序访问右子树
后序遍历【最后访问根节点】
先后序访问左子树
再后序访问右子树
再访问根节点
已知两种遍历序列求原始二叉树
通过先序和中序 或者 中序和后序我们可以
还原出原始的二叉树
但是通过先序和后序是无法还原出原始的二叉树的
换种说法:
只有通过先序和中序,或者通过中序和后续
我们才可以唯一的确定一个二叉树
树应用
树是数据库中数据组织的一种重要形式
操作系统子父进程的关系本身就是一棵树
面向对象语言中类的继承关系本身就是一棵树
赫夫曼树
图
模块三:查找和排序
折半查找
排序: 冒泡
插入
选择
快速
归并
排序和查找的关系
排序是查找的前提
排序是重点
Java中容器和数据结构相关知识
Iterator接口
Map
哈希表
再次讨论什么是数据结构
数据结构研究的是数据的存储和数据 的操作的一门学问
数据的存储分为两部分:
个体的存储
个体关系的存储
从某个角度而言,数据的存储最核心的就是个体关系的存储
个体的存储可以忽略不计
再次讨论到底什么是泛型
同一种逻辑结构,无论该逻辑结构物理存储是什么样子的
我们都可以对它执行相同的操作
函数的调用
当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成三件事:
1.将所有的实际参数,返回地址等信息传递给被调函数保存
2.为被调函数的局部变量(也包括形参)分配存储空间
3.将控制转移到被调函数的入口