算法相关概念
- 次数
通过算法得到结果运行的次数。 - 运行时间
大O(n)表示法,n表示次数。
常见算法的运行时间:- O(n):如普通查找的线性时间
- O(n^2):如选择排序低效算法
- O(logn):如下二分查找log时间
- O(nlogn):如快速排序nlog时间
查找算法
- 二分查找
说明:比较查找,即每次都取列表中间的值和要查找的值相比较,直到找到要找的那个值。
前提条件:对于有序的数组才能使用。
查找次数是logn,运行时间是O(logn)。 - 普通查找
说明:可以是在无序中的数组中查找,查找长度为n的数组,最多查找n次数,运行时间是O(n)。
相关数据结构
- 数组
使用连续的内存空间。
随机读取效率高。 - 链表
使用非连续的内存空间。
读取全部或者读取第一个的元素和数组效率相当。
添加、插入、删除效率高。
循环和递归
性能相当,有时候循环性能更好。
如果不是为了代码逻辑更清晰,那就用循环。
- 递归
- 基线条件->为避免死循环,需要递归退出条件。
- 递归条件
- 栈:只有删除和读取的操作。
// go 代码
func main(){
f := func(name string){
f1(name)
fmt.Println(name+ "will say bye bye!")
f2(name)
}
f1 := func(name string){
fmt.Pritnln("hello"+name+"!")
}
f2 := func(name string){
fmt.Println("how are you"+ name)
}
f("wangwang")
}
/*
上述代码调用栈的解释:
运行main函数时,分配一个main函数运行的栈,接着调用函数f,在栈上分配f函数空间和变量name的内存空间,然后调用f1函数为f1函数分配栈内存空间和name分配内存空间,f1执行完后,f1的栈删除,(此时name变量的内存空间仍然在),再执行f函数中will say bye bye!,最后调用f2函数,为f2函数和变量分配内存空间,f2执行后从栈弹出,f从栈中弹出。
结论:
每次调用函数结束后都会弹出被执行完后的函数栈,所以不会占用太多的空间。
*/
// !3
func factorial(n int) int{
if n <= 1{ // 基线条件
return 1
}else{ // 递归条件
return n * factorial(n-1)
}
}
func main(){
fmt.Println(factorial(3))
}
/*
栈上运行过程分析如下:
第一次递归
factorial(3) // 分配factorial函数和n=3的栈内存
第二次递归
factorial(2) // 分配factorial函数和n=2的栈内存
第三次递归
factorial(1) // 分配factorial函数和n=1的栈内存,返回1
factorial(n) * factorial(n-1) * 1 // 返回factorial(n-1),返回factorial(n),返回6
结论:
每次调用完函数并不会从栈中弹出,直到到了基线条件所有函数和变量才依次从栈中弹出。
栈中保存了被调用函数列表。
*/
介于上面的代码,可以总结两点:
栈很方便,但也会分配额外的函数调用信息。
改善栈的方法有两种,一种是使用循环代替递归,另外一种是使用尾递归。
排序
- 排序
实现:使用循环,依次从列表中找到最大的元素,删除已有元素,追加到新的元素列表中,重复上述步骤,直到元素为空。
运行时间:O(n0.5n) ~ O(n^2) - 快速排序
分而治之。