本文首发于 我的博客算法
程序 = 数据结构 + 算法数组
其实不少同窗知道数据结构与算法很重要,可是却不明觉厉。 这里咱们看一个简单的题:markdown
对天然数从1到100的求和数据结构
最简单的设计无非是:函数
void addNum () {
int total = 0;
for (int i = 1; i <= 100; i ++) {
total += i;
}
printf("total is %d \n",total);// 5050
}
复制代码
没毛病,可是哥根廷的数学家 高斯
在其9岁的时候就发明了一个快速计算等差数列求和的小技巧 (1+100,2+99,3+98.....),总共50 对101,结果5050。其公式能够概括为:oop
不论n多大,咱们只用算一次就能够得出结果,而上面的循环却要循环n次,n很小无所谓,若是几万几十万这个时间消耗不言而喻,因而可知数据结构与算法确实很重要。spa
数据结构中最基本的5个概念:数据、数据元素、数据项、数据对象,他们总体构成数据结构。设计
比较抽象,咱们用代码来展现以下:指针
struct Teacher {
char *name;
char *sex;
int age;
};
int main(int argc, const char * argv[]) {
struct Teacher t1; // 数据元素
struct Teacher tArray[10]; // 数据对象( 一组数据元素组成 )
t1.name = "Mary"; // 数据项
t1.sex = "female"; // 数据项
t1.age = 23; // 数据项
return 0;
}
复制代码
数据元素之间不是独立的,存在特定的关系,这些关系即结构,数据结构指的就是数据对象中数据元素之间的关系。code
从视角的不一样做以区分,咱们将数据结构分为两种类型:逻辑结构 和 物理结构
逻辑结构分为:集合结构 、线性结构、树形结构 、图形结构。
集合结构
集合结构中的数据元素除了同属一个集合外,彼此之间没有其余关系。
线性结构
线性结构中的数据元素之间都是一对一的关系,从图中也能够看出他们像一条线同样把各个元素连起来,经常使用的线性结构有:链表 ,栈,队列 ,数组 等等。
树形结构
树形结构中的元素是呈现一对多的关系,常见的树有:二叉树,B树,哈夫曼树 等等。
图形结构
图形结构之间的元素是多对多的关系,常见的图形结构有: 矩阵 等。
物理结构其实就是存储结构,就是存储到计算机中的形式。数据存储的结构才真正反映了数据存在的样式,也反映了数据元素之间的逻辑关系,如何设计数据的存储以及相互之间的关系才是数据结构的关键。
顺序存储
咱们知道计算机中的内存都是连续的,如上图所述,1-6个元素按照顺序存储的方式存到内存里,好比第一个元素的内存地址是 0x000001
假设咱们这个顺序表中每一个元素占1个位置,那么很容易获得第二个元素的地址是 0x000002
,以此类推很容易知道第六个元素的地址是 0x000006
链式存储
这里我画了一个简单的图,大概描述了一下链式存储在内存中是如何体现的。当咱们的元素在内存中是散乱的,也就是他们的地址之间没有必定的规律,这个时候就要靠咱们的指针去标记位置了,好比咱们把 元素2
的物理地址 0x000019
存储到 元素1
中去,那么每两个元素之间就会有必定的相互绑定的关系,这就是链式存储的基本逻辑。
经过上述咱们会发现,顺序存储的结构查找会很容易,由于直接按照顺序对应的索引就能对应找到相应的内存地址,而链式存储则不行,不过链式存储的结构对于数据的增删就特别快了,由于他们之间的关系靠的是指针,而不是内存地址的偏移。
算法是解决特定问题步骤的描述,在计算机中表现为解决特定问题的一系列代码
数据结构脱离算法,或者算法脱离数据结构都是没法进行的,由于算法是基于数据结构进行的,只有数据结构而没有算法那么数据结构存在就没有意义了。
程序 = 数据结构 + 算法
一般咱们用程序代码执行的次数做为算法时间复杂度的衡量,一般咱们用 大O
法来进行标记。
n+2n+log(n)+n^2
-----> n^2
O(5logn) -----> O(logn)
一般用O(1)
来表示
//1+1+1 = 3
void testSum1(int n){
int sum = 0; //执行1次
sum = (1+n)*n/2; //执行1次
printf("testSum2:%d\n",sum);//执行1次
}
复制代码
无论n是多少,这个地方都只用执行1次,因此n不会影响其时间复杂度,O(1)
void add2(int x,int n){
for (int i = 0; i < n; i++) {
x = x+1;
}
}
复制代码
能够发现这个for循环会执行n次,因此其时间复杂度为 O(n)
int count = 1;
while(count < n){
count = count * 2;
}
复制代码
上述算法换个写法就是2^x = n
就能够获得 x = log2n
,咱们说过常数若是不是1均可以去掉,最终结果就是 O(logn)
// x=x+1; 执行n*n次
void add3(int x,int n){
for (int i = 0; i< n; i++) {
for (int j = 0; j < n ; j++) {
x=x+1;
}
}
}
复制代码
外层循环n次,内层循环n次,加在一块儿就是 n*n = n^2
次,因此其时间复杂度为 O(n^2)
O(1)<O(logn)< O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
其实咱们只用关注前面的复杂度就好了,若是你的算法时间复杂度像后面的那几位靠齐,也就没啥好说的了,指数级的增加仍是蛮恐怖的。
算法设计有一个重要原则: 空间 时间 权衡
算法的空间负责度是经过计算算法所需的存储空间实现,算法空间复杂度的计算公式是:s(n) = n(f(n)) 其中n为问题的规模,f(n) 为语句关于n所占存储空间的函数。
咱们经过一个简单的例子大概了解一下空间复杂度:
int n = 5;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
//算法实现(1)
/*
算法(1),仅仅经过借助一个临时变量temp,与问题规模n大小无关,因此其空间复杂度为O(1);
*/
int temp;
for(int i = 0; i < n/2 ; i++){
temp = a[i];
a[i] = a[n-i-1];
a[n-i-1] = temp;
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
//算法实现(2)
/*
算法(2),借助一个大小为n的辅助数组b,因此其空间复杂度为O(n).
*/
int b[10] = {0};
for(int i = 0; i < n;i++){
b[i] = a[n-i-1];
}
for(int i = 0; i < n; i++){
a[i] = b[i];
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
复制代码
须要注意的是:算法的空间复杂度并非整个算法占用内存的空间,而是该算法在实现的时候所需的辅助空间。
研究算法的最终目的是要提升程序运行的效率,咱们还想下降程序运行所占用的内存等等,这两个一个是时间维度,一个是空间惟独。一个好的算法并非说必定要时间复杂度低,也不必定要空间占用小,这些东西要根据项目实际状况去均衡。但愿这篇文章可以将数据结构与算法的基础知识讲清楚。