一、基本概念
描述方式:自然语言、程序流程图、程序设计语言、伪代码
1.有穷性(有尽头):执行有穷步之后结束,且每一步都可在有穷时间内完成
2.有效性(可行性):算法的每个步骤都能有效执行,并能在执行有限次后得到确定的结果
3.确定性:算法中的每一条指令都是有确切含义的,不能模糊不清(可以无输入,不能无输出)
二、时间复杂度与空间复杂度
(1):概念
)1.时间复杂度
程序运行开始到结束所需要的时间(时间规模)
通常分析时间复杂度的方法是从算法中选取出一种对于 问题 基本运算 的操作,以该操作重复执行的次数作为算法的时间度量。
算法中原操作重复执行的次数,是规模n(序列长度)的某个函数T(n)。由于许多情况下要精确计算T(n)是困难的,因此引入了渐进时间复杂度在数量上估计一个算法的执行时间。例如一段程序的执行时间为T(n)=3n^3+2n^2+1,则T(n)=O(n^3),只看时间最大的量级3n^3(保留最大规模),较小量级就不再纳入量级里。
常用的对算法执行所需时间的度量:O(1) (常量级)< O(log2n) < O(n) (线性)< O(nlog2n) < O(n^2) < O(n^3) < O(2^n)
)2.空间复杂度
程序运行过程中,临时占用存储存储空间大小的度量(空间效率)。一个算法的空间复杂度只考虑在运行过程中局部变量分配存储空间的大小
(2):时间复杂度
)1.常数级时间复杂度 O(1)(最小)
1.单语句:如k=0
2.整个程序没有循环语句或复杂函数调用:如
void main() {
String s = "ABC";
int len = s.length();
System.out.plintln(len);
}
)2.时间复杂度 O(n)
单层循环(若有两个平行的for,取最大n)
void main() {
String s = "ABC";
int len = s.length();
for(int i = 0; i < n; i++) {
len = i;
}
}
)3.时间复杂度 O(n^2)
双层循环嵌套(n^3为三层循环嵌套,以此类推)
也可以计作n*n
void main() {
String s = "ABC";
int len = s.length();
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
len = i+j;
}
}
}
)4.时间复杂度 O(log2n/lgn)
2^x=n == x=log2n,对于n个节点的完全二叉树,高度为log2n
二分查找,n表示arr数组长度(若外层有循环n次,即是nlog2n)
int search(int arr\[\], int n, int v){
int l, r, middle;
l=0; r=n-1;
while(l
middle = (l + r) / 2;
if(arrmiddle > v) {
r = middle -1;
}
else if(arrmiddle < v) {
l = middle +1;
}
else {
return middle;
}
}
return -1;
}
)5.时间复杂度 O(n*log2n/nlgn)
典型代表:堆排序,每次重建堆的时间复杂度是log2n,n个元素为nlog2n
)6.时间复杂度 O(2^n)
典型代表:LCS最长公共子序列、钢管切割问题,动态规划法自顶向下,时间复杂度为O(2n)
(3):例题
1.根据渐进分析,排列表达式:n^4,lgn,2^n,1000n,n^2/3,n!
解:思路:1000n = n,n^2/3 = n^0.67
已知常用的对算法执行所需时间的度量:O(1) (常量级)< O(log2n) < O(n) (线性)< O(nlog2n) < O(n^2) < O(n^3) < O(2^n)
得出排序为:lgn < n^2/3 < 1000n < n^4 < 2^n < n!
三、查找算法
1、顺序查找
将待查找的关键字为key的元素从头到尾与表中的元素进行比较,如果中间存在关键字为key的元素,则返回成功,否则查找失败
1.平均查找长度(将所有的平均数累加起来):n+1/2
2.出现概率:1/n(n分之1,n为顺序表长度)
2、二分查找
在查找区域当中,寻找中间位置,然后进行比较(前提:必须为顺序存储,并且值为有序的)
1.中间位置:mid = (low+high)/2
判断查找值在中间位置的哪一侧,再在求出侧再次进行二分比较(若在左侧,此时中间位置mid = (low+high-1)/2,high-1是因为high作为上一层的中间位置,已经在上一层时被比较过了,所以不需要再次进行比较。若在右侧,此时mid = (low+1+high)/2,low+1是因为low作为上一层的中间位置,已被比较,所以不需要再次进行比较),直至求得值位置
2.最多比较次数:log2n+1
3.时间复杂度:O(log2n)
4.前提:元素为有序,并且为顺序存储
3、哈希查找表
哈希表存储方式为 h=key%p
设:p=5,记录关键码为{3, 8, 12, 17, 9}
|---|---|----|----|---|
| 3 | 8 | 12 | 17 | 9 |
(1):线性探测法
若两关键码在哈希表位置中发生冲突,则后来者关键码顺这链表往后排到哈希表未存放关键码的第一个位置进行存放
3%5=3,则关键码3放到哈希表3的位置
8%5=3,由于与关键码3发生了冲突,则存放位置往后移一位,存放4的位置
12%5=2,关键码12放到哈希表2的位置
17%5=2,由于与关键码12发生了冲突,后边两位置(3、4)也已存放了关键码,所以存放到5的位置
9%5=4,由于哈希表4已存放了关键码,后移一位5也存放了,所以还需要后移一位6
|---|----|---|---|----|---|
| 1 | 2 | 3 | 4 | 5 | 6 |
| | 12 | 3 | 8 | 17 | 9 |
查找时,首先取余值,然后找到哈希表对应编号,若当前非本次要查找的关键码,则后移一位继续查找,直至查找至最尾
(2):拉链法
若两关键码在哈希表位置中发生冲突,则在该位置形成一个列表,关键码存放随着链表逐个往后排
位置为:
3%5=3,则关键码3放到哈希表3的位置
8%5=3,由于与关键码3发生了冲突,则存放到32位置
12%5=2,关键码12放到哈希表2的位置
17%5=2,由于与关键码12发生了冲突,则存放到22位置
9%5=4,关键码12放到哈希表4的位置
|---|----|---|---|---|
| 1 | 2 | 3 | 4 | |
| | 12 | 3 | 9 | |
| | 17 | 8 | | |
查找时,首先取余值,然后找到哈希表对应编号,若当前编号非查找的关键码,则顺着链表继续往下查找,直至查找至链表结尾
四、排序算法
1、基本概念
(1):概念
1.稳定排序:值相同的关键字,排序时先后顺序不发生改变
2.不稳定排序:值相同的关键字,排序时先后顺序位置发生了改变
3.内排序:未借助内存以外的存储空间进行排序
4.外排序:借助了内存以外的存储空间进行排序
(2):分类
1.插入类:直接插入排序、希尔排序
2.交换类:冒泡排序、快速排序
3.选择类:简单选择排序、堆排序
4.归并排序
5.基数排序
2、插入类排序
(1):直接插入
稳定排序:插入第i个元素的时候,其他元素均已排好,因此从集合最后一个元素开始比较,然后选择合适的位置,插入到集合中。简单明了,但是速度很慢
时间复杂度:O(n^2),需要有两个集合嵌套循环
在排序过程中仅需要一个元素的辅助空间,空间复杂度为:O(1)
适用于基本有序的情况,此时间复杂度近乎线性O(n),即待排序的集合已经是有序的了
设:待排序列57、68、59、52
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 57 | 68 | 59 | 52 |
插入排序时:
1.插入0-57,首位元素,不需要比较,直接放到新集合0号下标
2.插入1-68,比较后不需要交换位置,放到新集合1号下标
3.插入2-59,比较后大于68,小于57,所以将68后移至下标2的位置,59插入至新集合下标1的位置
4.插入3-52,比较后小于68、59、57,所以这三个元素均往后移一位,52插入到新集合下标为0的位置
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
(2):希尔排序
不稳定排序
时间复杂度:O(n^1.3)
在排序过程中仅需要一个元素的辅助空间,空间复杂度为:O(1)
先进行分组,n/2,再插入排序
设:待排序列
|----|----|----|----|----|----|----|----|----|----|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 57 | 68 | 59 | 52 | 72 | 28 | 96 | 33 | 24 | 19 |
希尔排序时:n=序列长度10
》第一步:求d1=n/2=5,求得d1后,将序列中元素分为5组(即d1的值),组合坐标间隔为5个,分别为:0~5、1~6、 ······ 、4~9

0(57)与5(28)比较、1(68)与6(96)比较、······、4(72)与9(19)比较
分组后进行比较后求得序列:
|----|----|----|----|----|----|----|----|----|----|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 28 | 68 | 33 | 24 | 19 | 57 | 96 | 59 | 52 | 72 |
》第二步:求得d2=d1/2(取奇数整数)=5/2=3,求得d2后,将序列中元素分为3组(即d2的值),组合坐标间隔为3个,分别为:0~3~6~9、1~4~7、2~5~8

分组后进行比较后求得序列:
|----|----|----|----|----|----|----|----|----|----|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 24 | 19 | 33 | 28 | 59 | 52 | 72 | 68 | 57 | 96 |
》第三步:求得d3=d2/2(取奇数整数)=3/2=1,求得d3=1后,就表示已经到了最后一轮比较,因此不再进行分组,将序列中每个元素挨个进行比较,即0与1比,1与2比,······,8与9比
进行比较后求得序列即是排序序列:
|----|----|----|----|----|----|----|----|----|----|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 19 | 24 | 28 | 33 | 52 | 57 | 59 | 68 | 72 | 96 |
3、选择类排序
(1):简单选择排序(直接排序)
不稳定排序
时间复杂度:O(n^2)
在排序过程中仅需要一个元素的辅助空间,空间复杂度为:O(1)
首选在元素序列中,选择最小的一个元素把它与第一个交换,再在其余元素中选择最小的元素,把它与第二个交换,以此类推,直至排序完成
设:待排序列57、68、59、52
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 57 | 68 | 59 | 52 |
1.最小值为52,与第一个元素交换位置
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 68 | 59 | 57 |
2.最小值为57,与第二个元素交换位置
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
3.最小值为59,无需替换
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
4.完成排序
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
(2):堆排序
时间复杂度:O(nlog2n)
在排序过程中仅需要一个元素的辅助空间,空间复杂度为:O(1)
适用于数据量大的元素序列
将元素序列转换为树结构后,非大顶堆和小顶堆的元素序列,都不能称之为堆,也不能用堆排序
小顶堆:每一个父节点都比子节点小
大顶堆:每一个父节点都比子节点大
将最值放置堆顶后输出
排序过程:初建堆 =====》【取出堆顶元素 =====》重建堆 =====》······ =====》取出堆顶元素 =====》重建堆】 =====》完成排序
>设:元素序列
|----|----|----|----|----|----|----|----|----|----|
| 30 | 60 | 10 | 50 | 45 | 16 | 15 | 80 | 40 | 20 |
1.初建堆、转换树结构:根据树结构转换,可以判断出此序列即非大顶堆,也非小顶堆(父节点30比左子节点60小,比右子节点10大),得出并非堆结论

2.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,80=>50

2.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,45=>20无须调整,10=>16

3.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,60=>80

4.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,30=>80

5.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,30=>60

5.取出堆顶元素 =====》重建堆:调整树结构,从下向上调整。调整为大顶堆,30=>50

6.完成初建堆,生成大顶堆,并输出堆顶元素:80,将顶堆最后一个元素推为堆顶,再重建大顶堆


7.完成初建堆,生成大顶堆,并输出堆顶元素:60,将顶堆最后一个元素推为堆顶,再重建大顶堆

······以此类推
8.直至所有顶堆数输出,排序完成
|----|----|----|----|----|----|----|----|----|----|
| 80 | 60 | 50 | 45 | 40 | 30 | 20 | 16 | 15 | 10 |
4、交换类排序
(1):冒泡排序
稳定排序
时间复杂度:O(n^2)
在排序过程中仅需要一个元素的辅助空间,空间复杂度为:O(1)
通过相邻元素之间的比较和交换,将排序码较小的元素逐渐从底部移向顶部(几个元素,经历几轮冒泡)
设:待排序列57、68、59、52
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 57 | 68 | 59 | 52 |
从后往前冒泡排序:
第一轮(确定第一个元素):首先3与2进行比较后交换,2与1比较后交换,1与0比较后交换
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 57 | 68 | 52 | 59 |
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 57 | 52 | 68 | 59 |
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 68 | 59 |
第二轮(确定第二个元素):待排序列有57、68、59,3与2进行比较后交换,2与1比较后无须交换
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 68 | 59 |
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
第三轮(确定第三个元素):待排序列有59、68,3与2进行比较后无须交换
|----|----|----|----|
| 0 | 1 | 2 | 3 |
| 52 | 57 | 59 | 68 |
(2):快速排序
采用分治法,基本思想是将原问题分解成若干个规模更小但子结构与原问题相似的子问题。并通过递归解决这些子问题,再将这些子问题的解组合成原问题的解。平均性能最好
不稳定排序
以中间元素为基准时间复杂度:O(nlog2n)
以第一个元素为基准时间复杂度:O(n^2)(最差情况,有序,所以不推荐基本有序序列做快速排序)
>步骤
第一步:在待排序的元素中取任意一个元素,并以该元素的排序吗为准。将所有元素分为两组,第一组小于此元素,第二组大于此元素
第二步:采用相同的方法再将左右两组分别进行排序。直到所有元素都排到相应位置为止
>设:待排序列
|----|----|----|----|----|----|----|----|----|----|
| 57 | 68 | 59 | 52 | 72 | 28 | 96 | 33 | 24 | 19 |
1.求第一个左右组:以57为基准,进行划分左右组



······ 最终将所有元素都比较完 ······

求得左右组19, 24, 33, 52, 28 57 96, 72, 59, 68
2.以相同的方式再求第二个左右组
以此类推······直至组合出原问题解
5、归并排序
将两个或两个以上的有序子表合并成一个新的有序表(二分思想为主)。对n个数排序,最坏的情况下时间复杂度最低
稳定排序
时间复杂度:O(nlog2n)
空间复杂度:O(n)
设:待排序列
|----|----|----|----|----|----|----|----|
| 57 | 68 | 59 | 52 | 72 | 28 | 96 | 33 |
1.分解成若干个子表后进行排序
|----|----|---|----|----|---|----|----|---|----|----|
| 57 | 68 | | 52 | 59 | | 28 | 72 | | 33 | 96 |
2.将子表进行两两归并
|----|----|----|----|---|----|----|----|----|
| 52 | 57 | 59 | 68 | | 28 | 33 | 72 | 96 |
3.将子表进行两两归并
|----|----|----|----|----|----|----|----|
| 28 | 33 | 52 | 57 | 59 | 68 | 72 | 96 |
6、基数排序
稳定排序
时间复杂度:O(d(n+rd))
空间复杂度:O(rd)
设:待排序列
|-----|-----|-----|----|-----|----|----|----|
| 135 | 242 | 192 | 93 | 345 | 11 | 24 | 19 |
1.根据元素个位数收集
|----|-----|----|----|-----|---|---|---|----|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
| 11 | 242 | 93 | 24 | 135 | - | - | - | 19 | - |
| | 192 | | | 345 | | | | | |
调整后序列为:
|----|-----|-----|----|----|-----|-----|----|
| 11 | 242 | 192 | 93 | 24 | 135 | 345 | 19 |
2.根据元素十位数收集
|----|----|-----|-----|---|---|---|---|-----|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
| 11 | 24 | 135 | 242 | - | - | - | - | 192 | - |
| 19 | | | 345 | | | | | 93 | |
调整后序列为:
|----|----|----|-----|-----|-----|-----|----|
| 11 | 19 | 24 | 135 | 242 | 345 | 192 | 93 |
3.根据元素百位数收集
|-----|-----|-----|---|---|---|---|---|---|----|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
| 135 | 242 | 345 | - | - | - | - | - | - | 11 |
| 192 | | | | | | | | | 19 |
| | | | | | | | | | 24 |
| | | | | | | | | | 93 |
调整后序列为(即:最后求得排序序列):
|----|----|----|----|-----|-----|-----|-----|
| 11 | 19 | 24 | 93 | 135 | 192 | 242 | 345 |
7、算法对比
|-----|------------|------------|---------------|----------|-----|-------------------------|
| 类别 | 排序方法 | 时间复杂度 || 空间复杂度 | 稳定性 | 适用场景 |
| 类别 | 排序方法 | 平均情况 | 特殊情况 | 空间复杂度 | 稳定性 | 适用场景 |
| 插入类 | 直接插入 | O(n^2) | 基本有序最优O(n) | O(1) | 稳定 | 序列元素较少、基本有序 |
| 插入类 | 希尔排序 | O(n^1.3) | - | O(1) | 不稳定 | |
| 选择类 | 直接选择(简单选择) | O(n^2) | - | O(1) | 不稳定 | 序列元素较少 |
| 选择类 | 堆排序 | O(nlog2n) | - | O(1) | 不稳定 | n很大(只需要一个辅助空间) |
| 交换类 | 冒泡排序 | O(n^2) | - | O(1) | 稳定 | 基本有序 |
| 交换类 | 快速排序 | O(nlog2n) | 基本有序最差O(n^2) | O(log2n) | 不稳定 | n很大(内部认为最好的方案,平均运行时间最短) |
| 归并 | 归并排序 | O(nlog2n) | - | O(n) | 稳定 | n很大(稳定) |
| 基数 | 基数排序 | O(d(n+rd)) | - | O(rd) | 稳定 | n很大、元素位数较少 |