这学期学了数据结构,现在已经放假了,借此机会,写一篇博客当作复习,主要内容是数据结构入门,使用语言是C语言。
包含以下几个章节
时间复杂度和空间复杂度
衡量一个算法的好坏,一般是从空间和时间两个方面来衡量的。
时间复杂度主要衡量一个算法的运行快慢,空间复杂度主要衡量一个算法运行所需要的额外空间
时间复杂度
2.1 时间复杂度的概念
算法中的基本操作的执行次数,为算法的时间复杂度。
即:找到某条基本语句与问题规模N之间的数学表达式,就是算出了该算法的时间复杂度。
2.2 大O的渐进表示法
大O符号:是用于描述函数渐进行为的数学符号
推导大O阶的方法如下:
1.用常数1取代运行时间中的所有加法常数
2.在修改后的运行次数函数中,只保留最高阶项
3.如果最高阶项存在且不是1,则去除与这个项目相乘的常数,得到的结果就是大O阶。
在实际情况中会有三种情况:最好,最坏和平均情况。其实大O渐进表示法去掉了那些对结果影响不大的项,实际取的是算法的最坏运行情况。
列如,在长度为N的数组中搜素一个数x的次数
1.最好情况:任意输入规模的最小运行次数(下界) 1次找到
2.最坏情况:任意输入规模的最小运行次数(上界界) N次找到
3.平均情况:N/2次找到
所以时间复杂度为O(n)
实例:
void Func1(int N)
{
int count=0;
for(int k=0;k<2*N;++k)
{
++count;
}
int M=10;
while(M--)
{
++count;
}
printf("%d\n",count);
}
//Func1的时间复杂度是O(N);
//2.
void Func2(int N)
{
int count=0;
for(int k=0;k<100;++k)
{
++count;
}
printf("%d\n",count);
}
//Func2时间复杂度是O(1);
空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度,也使用大O渐进表示法
注意:函数运行时所需要的栈空间(存储参数,局部变量,寄存器信息等)再编译期间已经确定好了,因此空间复杂度主要通过函数在运行时显示申请的额外空间来确定
空间复杂度不是O(1)就是O(N),使用了常数个空间就是O(1),开辟了N个空间就是O(N).
实例:
void BubbleSort(int *a,int n)
{
assert(a);
for(size_t end-n;end>0;--end)
{
int exchange=0;
for(size_t i=1;i<end;++i)
{
if(a[i-1]>a[i])
{
Swap(&a[i-1],&a[i]);
exchange=1;
}
}
if((exchange==0)
break;
}
}
//申请了常数个空间,因此BubbleSort的空间复杂度为O(1)
//2.
long long* Fibonacci(size_t n)
{
if(n==0)
return NULL;
long long * fibArrat=(long long*)malloc(n+1)*sizeof(long long);
fibArray[0]=0;
fibArray[1]=1;
for(int i=2;i<=n;++i)
{
fibArray[i]=fibArray[i-1]+fibArray[i-2];
}
returb fibArray;
}
//开辟了n个空间,所以斐波那契数列的空间复杂度是O(N);
//3.
long long Fac(size_t N)
{
if(N==0)
returun 1;
return Fac(N-1)*N;
}
//递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间,空间复杂度是O(N)
顺序表和链表
线性表是n个具有相同特性的数据元素的有限序列,常见线性表有:顺序表,链表,栈,队列,字符串
线性表在逻辑上是线性结构,但在物理结构上不一定是连续的,在物理上存储时,通常是以数组和链式结构的形式存储。
顺序表
顺序表是用一段物理地址连续的存储单元一次存储数据元素的线性结构,一般采用数组存储,在数组上完成数据的增删查改
顺序表一般可分为:
1.静态顺序表:使用定长数组存储元素。(不常用)
2.动态顺序表:使用动态开辟的数组存储。
链表
链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针链接次序实现的
简单来说,链表逻辑结构上是连续的,但在物理上不一定连续,由指针相连。
链表的分类:
1.单向或双向
2.带头结点或不带头结点
3.循环或者非循环
但主要是用无头单向非循环链表和带头双向循环链表
栈和队列
栈
栈:一种特殊的线性表,只允许在一端进行插入和删除元素操作。进行数据的插入和删除操作的一端称作栈顶,另一端称作栈底,后进先出原则
压栈:栈的插入操作叫进栈/压栈,入数据在栈顶
出栈:栈的删除操作叫出栈,出数据也在栈顶
栈的实现一般可以使用数组或者链表实现,但使用数组更好一些,数组在尾上插入方便
队列
队列:只允许在一端进行插入数据操作,另一端进行删除数据操作的特殊线性表。进行 插入操作的一端称为对尾,进行删除操作的一端称为队头、先进先出的原则
队列的实现也可以用数组或者链表实现,但使用链表好些,因为队列删除的时候使用数组效率低
二叉树
树
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
有一个特殊的结点,称为根结点,根结点,没有前驱结点
除根结点外,其余结点被分成互不相交的集合,每个集合类似子树,每个子树的根节点只有一个前驱,可以有0个或者多个后继
树是递归定义的
树形结构中,子树之间不能有交集。
树的一些相关概念
二叉树
一棵二叉树是结点的一个有限集合,该集合为空,或者由一个根结点加上两棵别称左子树和右子树的二叉树组成
二叉树不存在度大于2的结点
二叉树的子树有左右之分,次序不能颠倒,因此是有序树
二叉树都是由以下几种情乱复合组成的
特殊的二叉树
满二叉树:满二叉树的每一层结点都达到最大值,假设有k层,则结点总数是2^k-1
完全二叉树:对于深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树,完全二叉树的效率很高。
二叉树的一些性质
1.若根节点的层数是1,则一棵非空二叉树的第i层上最多有2(i-1)个结点
2.若根节点的层数是1,则深度为h的二叉树的最大节点数是2^k-1。
3.对任意一棵二叉树,如果度为0其叶结点个数为n0,度为2的分支结点个数为n2,则有n0=n2+1;
4.若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log2(n+1).
5.对于具有n个结点的完全二叉树,从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点有:
若i>0,i位置的双亲序号:(i-1)/2。 i=0,i为根结点,无双亲结点
若2i+1<n,左孩子序号:2i+1. 若 2i+1>=n则无左孩子
若2i+2<n.右孩子序号: 2i+2. 若2i+2>=n则无右孩子
二叉树的存储结构
1.顺序存储
顺序存储就是使用数组来存储,只适合表示完全二叉树。二叉树的顺序存储是一个数组,在逻辑上是一棵二叉树。
2.链式存储
用链表来表示一棵二叉树。链表中的每个结点由三个域组成,数据域和左右指针域,左右指针域分别用来指出该结点左右孩子的存储地址。链式结构又分为二叉链和三叉链,目前使用二叉链,后面红黑树会用到三叉链
堆的概念及结构
1.将一个集合k={k1,k2,k3,k4,k5,k6……kn-1}中所有的元素按照完全二叉树的顺序存储方式存储在一个一维数组中并满足(ki<=k2i+1且ki<=k2i+2)。
将根节点最大的堆叫做最大堆或大根堆
将根结点最小的堆叫做最小堆或小根堆
2.堆的性质:
堆中的某个结点的值总是不大于或不小于其父节点的值
堆总是一棵完全二叉树