数据结构学习
感谢郝斌老师的《数据结构》视频课!
学习的步骤:
学习的四大步骤:WWWA
1.什么是A (what)
2.为什么需要A (why)
3.怎么用A (how)
4.需要注意的问题 (attention)
我们知道程序 = 数据结构 + 算法,可见数据结构的重要性。在平时我们创建的一个数组以及对数组的添加删除操作都属于数据结构的范畴。那么,什么是数据结构呢?
什么是数据结构?
定义:我们如何把现实中大量而复杂的问题以特定的数据类型(个体)和特定的存储结构(个体与个体之间的关系)保存到主存储器(内存)中,以及在此基础上为实现某个功能而执行的相应操作,这个相应操作也叫算法。
其实,数据结构并没有严格的定义,我们只需要了解到数据结构研究的是个体以及个体之间的关系
数据结构 = 个体 + 个体之间的关系
算法 = 对存储数据的操作
衡量算法的标准:
1.时间复杂度:程序大概要执行的次数,而非执行的时间
2.空间复杂度:算法执行过程中大概所占用的最大内存
3.难易程度:
4.健壮性:
5.正确性
逻辑结构
物理结构
数据的存储结构分为:线性结构、非线性结构(树、图)
线性结构分为:连续存储(数组)、离散存储(链表)
线性结构的两种常见应用:栈(先进后出)、队列(先进先出)
内存中的堆和栈:是指内存的分配方式
链表的定义:
链表是一个离散存储的,每一个结点只有一个前驱结点和一个后继结点,且第一个结点没有前驱结点,最后一个结点没有后继结点。
首结点、尾结点、头结点、头指针、尾指针:
首结点指的是第一个有效结点;
尾结点指的是最后一个有效结点;
头结点指的是首结点前面的结点,头结点并不存放有效数据,该结点的存在是为了对链表更好的操作;
头指针指的是指向头结点的指针;
尾指针指的是指向尾结点的指针;
头指针Head必须存在
头结点不一定存在
a1是首结点
确定一个链表只需要一个参数:头指针
链表的分类:
单链表
双链表:每一个结点有两个指针域;
循环链表:通过任何一个结点可以找到其他所有的结点;
非循环链表:
int i = 3;
int* p = (int *)malloc(sizeof(int) * 2);
p为指针变量,p存储的是类型为int的地址,p存储在栈区。p指向的内容存储在堆区。
free( p):释放的是p指针指向的内存空间,而不是p本身;
线性结构的两种常用应用:栈和队列。
栈:先进后出。类似于枪的弹夹
利用栈的存储结构存储数据的地方称为栈区。栈是由系统自动管理的。
栈分为静态栈和动态栈:
以数组的形式用一块连续的存储空间进行数据存储的栈称为静态栈;
以链表的形式用不连续的存储空间进行数据存储的栈称为动态栈;
队列:先进先出。类似排队。
队列分为:静态队列和链式队列
静态队列是以数组的形式存储的队列。
静态队列必须是循环队列。
循环队列需了解的几个问题:
-
静态队列为什么必须是循环队列
不管是对front指向的头结点进行出队删除操作 ,还是对rear指向的尾结点的下一个结点进行入队添加操作,指针都是增加的,这样势必会造成内存浪费 -
循环队列需要几个参数能够确定
一个循环队列的确定需要两个参数:front和rear。 -
循环队列各个参数的意义
front指向第一个元素,rear指向最后一个有效元素的下一个元素。
或者
front指向第一个有效元素的下一个元素,rear指向最后一个有效元素。
了解为什么real需要指向最后一个有效元素的下一个元素。这是为了方便对队列的操作,原因如下:
队列的初始化操作:front和rear指向的都是空指针NULL
判断一个队列为空:front=real
判断一个队列为非空:front!=real -
循环队列的入队伪算法
1.将需要入队的值付给rear指向的位置
2.rear = (rear + 1)%数组的长度 -
循环队列的出队算法
1.将front指向的值赋值给需要存储的值
2.front = (front + 1)%数组的长度 -
如何判断一个队列为空
front = rear -
如何判断一个队列已满
两种方式:
第一种:在每个结点中添加一个参数length,该参数记录队列长度,在front=rear时,判断length的值,如果为0则队列为空,如果为非0则为满。
第二种:少用一个元素,
front = (rear + 1)%数组长度
堆区:是指的用堆排序的方法进行操作存储的区域。
递归:
一个函数直接或间接调用自己,在满足一定条件可以结束调用的函数
函数的调用的过程:
(一)当一个函数调用另一个函数时,在运行被调用函数之前,系统会做三件事:
- 将所有的实际参数,返回地址(调用函数的第一个语句地址)等信息传递给被调用函数保存;
- 为被调用函数的局部变量分配空间;
- 将控制转移到被调用函数入口。
(二)从被调用函数返回到主调用函数之前,系统也要做三件事:
- 保存被调用函数的返回值;
- 释放被调用函数所占的空间;(包括形参以及局部参数)
- 依照被调函数存储的返回地址信息,将控制转移到调用函数。
(三)函数互相调用,利用栈来完成
树
定义:有且只有一个根结点;有若干个子树,子树也是树
深度:从根节点到最底层节点的层数
叶子节点:没有子节点的节点
度:子节点的个数
树的度,按最大的度说
一般树:任意一个节点的子节点的个数都不受限制
二叉树:任意一个节点的子节点的个数最多两个,且子节点的位置不可更改(有序的)
二叉树的分类:
一般二叉树:
满二叉树:全满
完全二叉树:如果只是删除了满二叉树最底层右边的连续若干个节点,这样形成的二叉树就是完全二叉树
二叉树的性质:
1)在二叉树的第i层上,最多有2^(i-1)个结点
2)深度为K的二叉树,最多有2^k-1个结点
3)对任何一棵二叉树T,如果其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1;
原因:假设度为1的结点数为n1,则n=n0+n1+n2;
接连数总是等于n-1,并且等于n1+2n2;
n-1=n1+2n2可以推出n0+n1+n2-1=n1+n2+n2,推出:n0=n2+1;
4)具有n个结点的完全二叉树的深度为└log2n」 + 1
二叉树的存储:
1.1 连续存储:(必须是 完全二叉树)
1.2 链式存储:
一般树的存储有一下几种方法:
双亲表示法:
孩子表示法
双亲孩子表示法
二叉树表示法:
一般树转化二叉树:任意一个节点的左指针域指向它的第一个孩子,右指针域指向它的兄弟
森林的存储:先把森林转为二叉树,再存储。转化方法跟一般树转二叉树差不多
二叉树操作:
1)遍历:(先中后,指的就是根)
先序遍历:根、左、右
中序遍历:左、根、右
后续遍历:左、右、根
2)已知两种遍历序列求原始二叉树:必须有中序
已知
先序遍历:ABCDEFGH
中序遍历:BDCEAFHG
求后续遍历
在先序遍历中,A一定是根节点,而在中序遍历中可知BDCE为A的左子树,FHG为A的右子树。而BCDE可看出,B为根节点,DCE为右子树。C为根节点,D为左子树,E为右子树。以此递归可知:
后续遍历:DECBHGFA