图(Graph)型结构:
什么是图型结构:由有穷且非空的顶点和顶点之间的边组成
通常表示为G(V,E) G表示一个图,V是图中顶点的集合,E是图中的边(顶点之间的关系)的集合
图的基本概念:
简单图:不存在顶点到自己的边、不存在重复的边,数据结构中只研究简单图
无向图:边是用(A,B)方式表示,表示顶点A到B是互通的
完全无向图:在无向图中,任意两个顶点之间都有边,在含有n个顶点的完全无向图中,有 n(n-1)/2 条边
有向图:边是用<A,B>方式表示,仅表示从A到B方向有边,在有向图中边也叫做弧,A是弧尾,B是弧头
完全有向图:在有向图中,任意两个顶点之间都有方向相反的弧,在含有n个顶点的完全有向图中,有 n(n-1)条弧
稀疏图:图中顶点多、边少,具体多少没有规范
稠密图:图中边的数量多
带权图:给图中的边赋予某种含义的数值,称之为该边的权重,图中所有边都有权重的图称为带权图,也称为网
度:依附于某个顶点的边的数量称为该顶点的度,在有向图中,又分出度(从该顶点出发的弧数量)、入度(其他顶点指向该顶点的弧数量)
路径:从某个顶点到另一个顶点之间经过的边,称为这两个顶点之间的路径
路径长度:两个顶点之间路径上的边的条目数
环:图中有顶点能经过边后回到自身,称该图有环
回路:专指有向图中,从某点出发,最终通过弧回到该点,称图中有回路,在回路上的顶点一定有出度、入度
顶点序列中存在不重复出现的路径称为简单路径
连通:如果顶点V1到顶点V2之间有路径,则称V1V2是连通的
连通图:任意两个顶点之间是连通的,n个顶点的图中,至少需要n-1条边才可能形成连通图,也称为生成树,如果给边配上权值,其中权重的代价最小的生成树,称为最小生成树
图的遍历方式:
深度优先遍历(DFS):
从某个顶点出发,一直往下一个顶点遍历,直到没有下一个顶点为止,再返回上一个顶点的其他路径继续进行深度优先,直到该出发顶点的所有深度优先遍历结束,同样的操作对每个顶点都进行一次
该过程中会有顶点被重复遍历,需要记录是否被遍历过的标志位,防止结果重复
广度优先遍历(BFS):
DFS\BFS 遍历结果不唯一,与顶点顺序有关
图的存储方式:
邻接矩阵:
用一个一维数组存储n个顶点,根据顶点数量使用n*n的二维数组来存储边
char V[n] = {A,B,C,D,E,F,G};
char E[n][n] =
A B C D E F G
A 0 1 0 1 0 0 0
B 1 0 0 0 1 0 0
C 1 0 0 0 0 0 0
D 0 1 0 0 0 0 1
E 0 0 0 0 0 0 0
F 0 0 0 1 0 0 1
G 0 0 0 0 1 0 0
在二维数组E[i][j]的值为1,则表示V[i]到V[j]之间有边
注意:由于不存在自己到自己的边,所以左对角线上的值一定为0
注意:如果存储的是无向图,那么一定沿左对角线对称,可以进行压缩成一维数组(参考矩阵的压缩)
优点:计算顶点的度、出度、入度都很方便
缺点:当图越稀疏时,会越浪费存储空间
算法:
数据结构中的算法,指的是数据结构所具备的功能
解决特定问题的方法,学习的前辈们的一些优秀的经验总结
算法的五大特征:
(1)有穷性。一个算法必须总是在执行有穷步后结束,且每一步都必须在有穷时间内完成。
(2) 确定性。对千每种情况下所应执行的操作,在算法中都有确切的规定,不会产生二义性, 使算法的执行者或阅读者都能明确其含义及如何执行。
(3) 可行性。算法中的所有操作都可以通过已经实现的基本操作运算执行有限次来实现。
(4) 输入。一个算法有零个或多个输入。当用函数描述算法时,输入往往是通过形参表示的, 在它们被调用时,从主调函数获得输入值。
(5) 输出。一个算法有一个或多个输出,它们是算法进行信息加工后得到的结果,无输出的 算法没有任何意义。当用函数描述算法时,输出多用返回值或引用类型的形参表示。
如何评价一个算法:
时间复杂度:
由于计算机的性能不同,无法准确地确定一个算法的执行时间
因此使用执行算法的次数来代表算法的时间复杂度
一般用O(公式)来表示
注意:算法的时间复杂度不能完全反映一个算法的实际时间时间,有些算法看似时间复杂度高,但是速度也快,例如选择排序
常见的时间复杂度:
// O(1)
printf("%d\n",num);
// O(logN)
for(int i=N; i>0; i/=2)
{
printf("%d\n",i);
}
// O(N)
for(int i=0; i<N; i++)
{
printf("%d\n",i);
}
// O(NlogN)
for(int i=0; i<N; i++)
{
for(int j=1; j<=N; j*=2)
{
printf("%d\n",i);
}
}
// O(N^2)
for(int i=0; i<N; i++)
{
for(int j=0; j<N; j++)
{
printf("%d\n",i);
}
}
空间复杂度:
执行一个程序(算法)所需要的内存空间的大小,是对一个算法在运行过程中临时占用存储空间大小的衡量
通常来说,只要这个算法不涉及动态分配内存以及递归,通常空间复杂度为O(1)
例如:求第N项斐波那契数列,空间复杂度O(N)
注意:一个算法的时间复杂度、空间复杂度往往是相互影响的,如何选择根据实际情况
分治:
是一种算法思想,而不是某种特定的算法,分治就是把一个大而复杂的问题,分解成很多小而简单的问题,利用计算机强大的计算能力来解决所有小问题,从而解决最开始的问题
实现分治的具体方法:循环、递归
查找算法:
顺序查找:
对待查找的数据没有特殊要求,从头到尾逐一查找比较,在小规模数据的查找中较为常见,平均效率较低
时间复杂度:O(N)
二分查找(折半查找)
对待查找的数据必须有序,从中间位置开始查找,如果中间值比key大,则从左边继续二分查找,否则从右边二分查找
时间复杂度:O(logN)
块查找(权重查找):
是一种数据处理的思想,不是特定的算法实现,当待处理的数据量很巨大时,可以根据数据的特征进行分块处理,然后再对每块数据进行查找
例如:英文词典、通讯录
哈希查找Hash:
先把待查找的数据经过哈希函数,计算出该数据在哈希表中的位置,然后做标记,后续就可以很方便地查找数据,它的时间复杂度最高能到O(1)
缺点:该算法有很大的局限性,不适合查找浮点型数据,需要额外的内存空间、空间复杂度高、是一种典型的以空间换时间的算法
哈希函数设计方法:
1、直接定址法:直接把数据作为它在哈希表中的位置来做标记
2、数据分析法:先分析出数据的最大值、最小值,然后通过 最大值-最小值+1 来确定哈希表的长度,根据哈希函数 数据-最小值 进行标记和查找哈希表
3、平方取中法、折叠法、随机数法等
注意:无论哪种方法都可能会出现哈希冲突(有重复数据时无法确定具体数据),一般采用链表配合解决
Hash函数的应用:MD5加密算法、SHA-1都属于Hash算法
排序算法:
排序算法的稳定性:
在待排序的数据中,对于数值相同的数据,在整个排序过程中如果不会改变他们原来的先后顺序,则认为该排序算法是稳定的
冒泡排序:
数据左右比较,把较大的数据交换到右边,往后重复以上操作,直到把最大的数据交换到最后,特点是该算法对数据的有序性敏感,如果在一次的排序过程中没有发生一次交换,那么就意味着数据已经有序,可以立即停止排序
适合待排序的数据基本有序时,则冒泡的效率非常高
时间复杂度:平均:O(N^2) 最优O:(N)
稳定的
选择排序:
假定最开始的位置是最小值并记录下标min,然后与后面所有的数据比较,如果有比min位置的值更小的数,那么更新min,结束后min的下标与开始时发生过改变,才进行交换到开始位置
虽然时间复杂度较高,但是数据交换次数比较少,因此实际的运行速度并不慢(数据交换比数据比较耗时)
时间复杂度:O(N^2)
不稳定的 10 10 1
插入排序:
把数据看成两个部分,前部分是有序的,把剩余部分的数据逐个往前比较,如果比前面的数小,前面的数往后移动一位,继续往前比较,直到遇到更小的数,那么该数据的后一个位置即为可以插入的位置
适合对已经排序后的数据,新增数据后再排序
时间复杂度:O(N^2)
稳定的
希尔排序:
是插入排序的增强版,由于插入排序时数据移动的步长较短,都是1,所以增加了增量的概念,通过不停地缩减增量,最终步长还是变回1,可以提高排序效率
当待排序数据远离最终位置时,希尔排序的效率高于插入排序
时间复杂度:O(NlogN)~O(N^2)
不稳定
快速排序:
在待排序数据中先找一个标杆位置p,备份p位置的值val,记录左标杆l、右标杆r,l从p的左边找比val大的数,找到后赋值给p位置,更新p到l,然后r从p的右边找比val小的数,找到后赋值给p位置,更新p到r
循环往复,直到l和r重合,把val还原回p位置完成一次快排,然后用同样的方式对p左右两边的数据进行快排
它的综合性能最优,因此得名快速排序,笔试时考的最多
时间复杂度:O(NlogN)
不稳定
堆排序:
把待排序数据当做完全二叉树看待,先把二叉树调整成大顶堆结构,把根节点的值与末尾节点的值交换,然后数量范围-1,重新从上到下调整回大顶堆,继续以上操作,直到数量为1结束,此时全部数据就从小到大排序了
既可以顺序实现,也可以递归实现
时间复杂度:O(N*logN)
不稳定
归并排序:
首先需要把数据拆分成单独的个体,然后按照从小到大的顺序比较后排序到一段临时空间中,把排序后的临时空间中的数据拷贝回原内存,然后依次把有序的个体继续以上操作合并成更大的有序部分
归并排序不需要交换数据,减少了交换的耗时,但是需要额外的存储空间,是一种典型的以空间换时间的算法
时间复杂度:O(N*logN)
稳定的
计数排序:
找出待排序数据中的最大、最小值,创建长度为
最大值-最小值+1 的哈希表,根据哈希函数 数据-最小值 当做哈希表的下标并对所有数据做标记
然后从头遍历哈希表,当某个位置的值大于0,把该位置的下标+最小值的到结果依次放回原数据内存中
是一种典型的以空间换时间的算法
理论上该算法的速度是非常快的,它不是基于比较的排序算法,在一定范围内的整数排序中快于任意一种基于比较的排序算法,但是有很大的局限性,只适合整型数据排序,数据的差值不宜太大,否则会非常浪费内存
反之数据差值不大、重复数比较多的情况下性价比很高
时间复杂度:Ο(n+k)(其中k是整数的范围)
稳定的
桶排序:
一般是把数据根据数值分到不同的"桶",通过不同的、合适的其他排序算法对"桶"中的数据进行排序,然后再把各个"桶"的数据依次拷贝回原内存中
桶排序是一种降低排序规模的排序思想,也是以空间换时间的算法
缺点:如何分桶、桶如何定义,都需要针对具体待排序的数据进行分析后才能确定
桶排序的稳定性取决于桶内排序使用的算法
有些资料上认为桶排序可以做到稳定,所以认为桶排序稳定