算法简介
-
算法:描述解决问题的方法称之为算法
-
定义:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
注意:
1.同一个问题可能会有很多种不同的算法
2.没有通用的算法能解决所有问题 -
特性
有穷性
确定性
可行性 -
数据结构与算法的关系
数据结构是“内功心法”
算法是“武学招式” -
算法设计的要求
算法效率的度量
-
事后统计方法
通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序运行时间进行比较,从而确定算法效率的高低
缺陷:
1.必须依据算法编制好程序,这需要花费大量的实践和精力
2.时间必须依赖计算机硬件和软件等环境因素,有时会掩盖算法本身的优劣
3.算法的测试数据设计困难,并且程序的运行时间往往还与测试数据的规模有很大关系 -
事前分析估算方法
-
在计算机程序编制前,依据统计方法对算法进行估算
不关心编写程序所用的程序设计语言是什么。也不关心这些程序将跑在什么样的计算机中,我们只关心它所实现的算法
-
决定计算机程序运行时间的因素
算法采用的策略、方法
编译产生的代码质量(软件支持)
问题的输入规模:指输入量的多少
机器执行指令的速度 -
在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤
-
函数的渐进增长
-
函数渐进增长:
给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)
-
判断两个算法A和B的效率
观察发现:
1.最高次项的相乘的常数并不重要
2.函数随着n的增长,最高次项的指数越大,结果也会变得增长特别快- 结论:
判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注最高阶项的阶数
- 结论:
算法的时间复杂度
-
定义:
算法的时间复杂度就是算法的时间度量,记作:T(n) = O(f(n))
T(n)表示语句总的执行次数,它是关于问题规模n的函数
用O()来体现算法时间复杂度的记法,我们称之为大O记法 -
推导大O阶次的方法
用常数1取代运行时间中的所有加法常数
在修改后的运行次数函数中,只保留最高阶项
如果最高阶存在且不是1,则去除与这个项相乘的常数
-
常数阶的时间复杂度记为O(1)
-
线性阶的时间复杂度均为O(n)
-
平方阶时间复杂度为O(n2)
-
对数阶时间复杂度O(log n)
-
常见的时间复杂度
算法的空间复杂度
-
算法的空间复杂度通过计算算法所需的存储空间实现
-
算法的空间复杂度的计算公式记作:S(n) = O(f(n)),其中n为问题的规模,f(n)为语句关于n所占存储空间的函数
- 如果算法执行时所需空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1)
-
算法优劣判定的基本原则
优先判断时间复杂度的优劣
时间复杂度相同,再判断空间复杂度的优劣
数据结构知识图谱
查找
-
查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值得数据元素
-
查找的基本概念
-
查找算法的评价指标
顺序查找算法
-
应用范围
顺序表或线性链表表示的静态查找表
表内元素之间无序
-
顺序表的表示
typedef struct{ ElemType * R; //表基址(顺序表的起始地址) int length; //表长 }SSTable;
-
顺序查找算法描述
int locateElem(Sqlist L, ElemType e) { for(int i = 0; i < L.length; i++) { if(L.elem[i] == e) return i; } return 0; }
-
顺序查找算法改进算法
原算法中,for循环里有一次判断,for循环体内还会有一次判断
改进算法中,只需要判断key值是否相等就可以,省了for循环次数的判断/** 改进后的代码 */ int locateElem(SqList L, ElemType e) { //把第一个位置设置为哨兵 L.elem[0].data = e; int i; for(i = L.length; L.elem[i].data != e; i--); return i; }
顺序查找的性能分析
-
时间复杂度
- 查找成功时的平均查找长度,设表中各记录查找概率相等:ASLS(n) = (1+2+…+n) / n = (n+1) / 2
- 查找不成功时的平均查找长度:ASLf= n + 1
-
空间复杂度
- 一个辅助空间
有序表(二分)查找
-
有序表查找的放法被称作折半查找,又叫二分法
-
应用范围
- 线性表中的记录是有序的,一般按照从小到大的顺序排列
-
基本思想
- 在有序表中,取中间值和要查找的值比较
- 若等于中间值,则中间值即为要查找的
- 若小于中间值,则中间值得左半区域继续查找
- 若大于中间值,则中间值得右半区域继续查找
- 不断重复上述过程
设表长为n、low、high和mid分别指向待查元素所在区间的上界、下界和中点,k为给定值。
初始时,令 low = 1, high = n, mid=【(low + high) / 2】(【】取整)
二分查找原理
-
查找成功
-
查找失败
-
二分查找非递归算法描述
/** 使用二分查找法返回关键字所在的位置(从1开始,没找到就返回0) */ int SearchSeqListBinary(SeqList seqList, KeyType key) { int low = 1; int high = seqList.length; //二分查找法的前提:必须有序,从小到大升序。 while(low <= high) { int mid = (low + high) / 2; if(key == seqList.datas[mid].key) { return mid; } else if(key < seqList.datas[mid].key) { high = mid - 1; } else { low = mid + 1; } } return 0; }
-
二分查找递归算法描述
/** 使用递归实现的二分查找法返回关键字所在的位置(从1开始,没找到就返回0) */ int SearchSeqListBinaryRecursion(SeqList seqList, KeyType key, int low, int high) { while(low > high) return 0; int mid = (low + high) / 2; if(key == seqList.datas[mid].key) return mid; else if(key < seqList.datas[mid].key) { return SearchSeqListBinaryRecursion(seqList, key, low, mid - 1); } else { return SearchSeqListBinaryRecursion(seqList, key, mid + 1, high); } return 0; }
二分查找的性能分析
-
查找过程
- 比较次数少,查找效率高(每次将待查记录所在区间缩小一半,比顺序查找效率高);
- 时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)
-
使用条件
- 采用顺序存储结构的有序表
- 不适用于元素经常变动的线性表
- 不宜用于链式结构
二叉排序树(BinarySortTree)
-
树表的查找:表结构在查找过程中动态生成
-
查找:
- 对于给定值key
- 若表中存在,则成功返回
- 否则插入关键字等于key的记录
-
二叉排序树(BinarySortTree):又称为二叉查找树(BinarySearchTree),也称为二叉搜索树
二叉树排序树是一棵空树或是满足如下性质的二叉树:
- 若其左子树非空,则左子树上所有结点的值均小于根节点的值;
- 若其右子树非空,则右子树上所有结点的值均大于等于根节点的值;
- 其左右子树本身又各是一棵二叉排序树
-
中序遍历二叉排序树
二叉排序树的查找
- 二叉查找原理
- 二叉排序树查找算法描述
- 二叉排序树的插入算法描述
二叉排序树查找的性能分析
第i层结点需要比较i次
在等概率的前提下,两图的平均查找长度分别为:
左图
∑
i
=
1
n
p
i
c
i
=
(
1
+
2
∗
2
+
3
∗
2
)
/
5
=
2.2
\sum_{i=1}^np_ic_i=(1+2*2+3*2) / 5 = 2.2
∑i=1npici=(1+2∗2+3∗2)/5=2.2
右图
∑
i
=
1
n
p
i
c
i
=
(
1
+
2
+
3
+
4
+
5
)
/
5
=
3
\sum_{i=1}^np_ic_i = (1+2+3+4+5) / 5 = 3
∑i=1npici=(1+2+3+4+5)/5=3