一、算法和算法分析
什么是算法:
对特定问题的求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作。
算法的五个重要特性:
- 有穷性
- 确定性
- 可行性
- 输入
- 输出
算法设计的要求:
- 正确性
- 可读性
- 健壮性
- 高效率与低存储
算法效率的度量:
算法的执行时间需要依据该算法所编制的程序在计算机上运行所消耗的时间来度量,一般有两种方法:
事后统计的方法:
很多计算机内部都有计时功能,不同的算法的程序可通过一组或若干相同的统计数据以分辨优劣,例如:time ./a.out,但这种方法有两个缺陷,一是必须先运行依据算法编程的程序,如果程序不满足要求编写程序所耗费的人力物力就浪费了,二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣,因此人们常常采用另一种事前分析法。
事前分析估算的方法:
一个用户高级程序语言编写的程序在计算机上运行所消耗的时间取决于下列因素:
1、依据算法先用何种策略,比如循环操作,可使用函数递归或for、while、do while等循环语句。
2、问题的规模,少量数据的问题求解、大规模数量的问题求解、海量数据的问题求解。
3、书写程序的语言,对于同一种算法,实现的语言级别越高,执行效率就越低。
4、编译程序所产生的机器代码的质量。
5、机器执行指令的速度,相同的编程语言,使用的编译器不同,产生的机器指令就会不同,从而执行的速度就会不同。
总结:同一个算法用不同的语言实现,或者用不同的编译器进行编译,或者不同的计算机上运行,效率均不相同,这表明使用绝对的时间单位衡量算法的效率是不合适的,抛开这些与计算机硬件、软件有关因素,可以认为特定算法“运行工作量”的大小,只依赖于问题的规模,或者说,它是问题规模的函数。
一个算法是由控制结构(顺序、分支、循环等3种基本结构)和原操作(指固有数据类型类型的操作)构成的,则算法时间取决于两者的统合效果,为了便于比较同一问题的不同算法,同学做法是,从算法中选取一种对于 所研究的问题来说是基本操作的原操作,以该基本操作重复执行的次数作为算法的时间量度。
所以:算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记叙T(n)=O(f(n)),它表示问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称做算法的渐近时间复杂度,简称时间复杂度。
常见的时间复杂度有:
- O(1)
- O(log2N)/O(logN)
- O(N)
- O(NlogN)
- O(N^2)
- O(2^N)
注意:一般时间复杂度取的是一种大约情况不会精确到特别详细,也就是不会百分百正确。
最差时间复杂度:
数据的极端排序顺序会让算法执行效率最差的一种情况。
最优时间复杂度:
数据的极端排序顺序会让算法执行效率最优的一种情况。
平均时间复杂度:
数据的排序顺序队伍事物发展规律,算法执行效率接近真实的一种情况。
空间复杂度:
它代表随着问题规模n的某个函数f(n),它表示问题规模n的增大,算法所需要的存储空间增长率,被称为算法的空间复杂度。
二、查找算法
什么是查找算法:
在一个数据序列中,查找某个数据是否存在或存在的位置,在实际开发过程中使用的频率非常高,例如对数据常见的操作有增、删、改、查,增加数据时需要查询新增加的数据是否重复,删除数据时需要先查询到数据所在位置再删除,修改数据时也需要先查询到被修改的数据在什么位置,查找算法在编程中重要性排列在第一位。
顺序查找:
顺序表的顺序查找:
// 在顺序表中按照从前到后的顺序查找数据,如果找到则返回合法的下标,找不到则返回 -1
int linear_search(int* arr,size_t len,int key)
{
for(int i=0; i<len; i++)
{
if(arr[i] == key)
return i;
}
return -1;
}
链表的顺序查找:
// 在顺序表中按照从前到后的顺序查找数据,如果找到则返回所在节点的地址,找不到则返回NULL
Node* linear_search(Node* head,int key)
{
for(Node* n=head; NULL!=n; n=n->next)
{
if(n->data == key)
return n;
}
return NULL;
}
顺序查找的优点:
对数据的有序性没有要求,无论数据是否有序都可以查找。
对数据结构没有要求,无论是顺序表还中链式表都可以查找。
顺序查找的缺点:
查找速度相比其它查找算法慢,最优时间复杂度:O(1),最差时间复试度:O(N),平均时间复杂度:O(N)
二分查找:
数据序列必须有序,然后关键字key与中间数据比较,如果相等则立即返回,如果key小于中间数据,则只需要在中间数据左边继续查找即可,如果key大于中间数据,则只需要在中间数据右边继续查找即可,重复该步骤,直到找到关键字,或中间数据左右两边为空,则查找失败。
// 循环语句实现
int binary_search(int* arr, size_t len, int key)
{
int left = 0 , right = len;
while(left < right)
{
int p = (left + right) / 2;
if(arr[p] == key)
return p;
if(arr[p] > key)
r = p;
if(arr[p] < key)
l = p+1;
}
return -1
}
// 函数递归实现
int _binary_search(int* arr, int left, int right, int key)
{
if(left >= right)
return -1;
int p = (left + right) / 2;
if(arr[p] > key)
return _binary_search(arr,left,p,key);
if(arr[p] < key)
return _binary_search(arr,p+1,right,key);
return p;
}
int binary_search(int* arr, size_t len, int key)
{
return _binary_search(arr,0,len,key);
}
二分查找的优点:
查询速度快,时间复杂度:O(log2N)。
二分查找的缺点:
1、对数据的有序性有要求,必须要先排序完成。
2、对数据结构有要求,不适合链式表使用,因为无法计算出中间位置的数据。
索引查找:
索引查找是在索引表和主表(即线性表的索引存储结构)上进行的查找,但需要先建立索引表,索引表就类似图书的目录,能大提高查找效率。
给顺序表创建索引表:
typedef struct Student
{
int id;
char name[20];
char sex;
short age;
float src;
}Student;
Student stu[1000];
typedef struct Index
{
int id;
void* addr;
}Index;
Index* index[1000];
void create_index(Student* stu,Index* index,size_t len)
{
for(int i=0; i<len; i++)
{
index[i].id = stu[i].id;
index[i].addr = stu+i;
}
}
索引表的顺序查找:
// 它比直接查询原感受的跳转速度快,如果数据在内存中可以减少跳转的内存页数量,如果数据在机械硬盘中可以减少硬盘磁头的移动距离,如果使用stu[i],i每增加一下,需要跳转sizeof(Student)个字节,如果使用index[i],i每增加一下,只需要跳转sizeof(Index)个字节,。
int linear_index(Index* index,size_t len,int id)
{
for(int i=0; i<len; i++)
{
if(index[i].id == id)
return i;
}
}
索引表二分查找:
// 如果对原表直接排序,则每次需要交换sizeof(Student),如果对索引表进行排序每次只需要交换sizeof(Index)个字节,数据的交换量对排序算法的程度影响非常大。
void sort_index(Index* index,size_t len)
{
for(int i=0; i<len-1; i++)
{
int min = i;
for(int j=i+1; j<len; j++)
{
if(index[min].id > index[j].id)
min = j;
}
if(i != min)
{
Index tmp = index[min];
index[min] = index[i];
index[i] = tmp;
}
}
}
Student* binary_search(Index* index,size_t len,int id)
{
int left = 0, right = len;
while(left < right)
{
int p = (left + right) / 2;
if(index[p].id == id)
return index[p].ptr;
if(index[p].id > id)
right = p;
if(index[p].id < id)
left = p+1;
}
return NULL;
}
给链表创建索引表:
// 给链表创建了索引表后,再对索引表进行排序,就可以对链表进行二分查找的效果
void create_index(Node* head,Index* index)
{
int i = 0;
for(Node* n=head; NULL!=n; n=n->next)
{
index[i].id = ((Student*)n->ptr)->id;
index[i].addr = n->ptr;
}
}
索引查找的优点:
1、对于顺序表的顺序查找,索引表可以缩小数据的查找范围。
2、对于顺序表的二分查找,索引表可以提高排序速度。
3、对于链表的可以在创建索引表后,进行二分查找。
索引查找的缺点:
使用了额外的存储空间创建索引表,是一种典型的用空间换取时间的策略。
索引查找的使用场景:
针对内存中的顺序表数据,索引查找提升的速度并不是特别大,而是对存储在机械硬盘中的数据的查找速度提升非常大,因此在数据库中索引查找使用的特别多。
分块查找:
先对数据进行分块,可以根据日期进行分块,可以对整形数据根据数据的最后一位分为10个分表,针对字符串数据可以根据第一个字母分为26个分表,然后再对这些分表进行二分查找、顺序查找,它是分治思想的具体实现。
分块查找的优点:
可以把海量数据进行分化,降低数据规模,从而提升查找速度。
分块查找的缺点:
操作比较麻烦,可能会有一定的内存浪费。
二叉排序树和平衡二叉树:
二叉排序树也叫二叉搜索树是根据数据的值进行构建的,然后进行前序遍历查找,它的查找原理还是二分查找,我在二叉搜索树中已经详细过,在此不再赘述。
平衡二叉树首先是一棵二叉排序树或二叉搜索树,二叉排序树在创建时,由于数据基本有序,会导致创建出的二叉排序树呈单枝状,或者随机数据的插入、删除导致二叉排序树左右失衡呈单枝状,就会导致二叉树排序的查询速度接近单向链表的O(N);
平衡二叉树要求左右子树的高度差不超过1,这样就会让二叉排序树左右均匀,让二叉排序树的查找速度接近二分查找(要了解AVL树和红黑树的区别)。
哈希表查找:
在查找数据时需要进行一系列的比较运算,无论是顺序查找、二分查找、索引查找、这类的查找算法都是建立在比较的基础上的,但最理想的查找算法是不经过任何比较,一次存取就能查找到数据,那么就需要在存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和数据中的唯一的存储位置相对应。在查找时,根据对应关系找到给定的关键字所对应的数据,这样可以不经过比较可以直接取得所查找的数据。
我们称存储位置在关键字之间的对应关系为哈希函数,按这个思想建立的表为哈希表。
问题:假定有100万个0255之间的随机数,然后输入一个0255之间的整数,查询出该数一共出现了多少次。
##### 索引查找的优点:
1、对于顺序表的顺序查找,索引表可以缩小数据的查找范围。
2、对于顺序表的二分查找,索引表可以提高排序速度。
3、对于链表的可以在创建索引表后,进行二分查找。
##### 索引查找的缺点:
使用了额外的存储空间创建索引表,是一种典型的用空间换取时间的策略。
##### 索引查找的使用场景:
针对内存中的顺序表数据,索引查找提升的速度并不是特别大,而是对存储在机械硬盘中的数据的查找速度提升非常大,因此在数据库中索引查找使用的特别多。
#### 分块查找:
先对数据进行分块,可以根据日期进行分块,可以对整形数据根据数据的最后一位分为10个分表,针对字符串数据可以根据第一个字母分为26个分表,然后再对这些分表进行二分查找、顺序查找,它是分治思想的具体实现。
##### 分块查找的优点:
可以把海量数据进行分化,降低数据规模,从而提升查找速度。
##### 分块查找的缺点:
操作比较麻烦,可能会有一定的内存浪费。
#### 二叉排序树和平衡二叉树:
二叉排序树也叫二叉搜索树是根据数据的值进行构建的,然后进行前序遍历查找,它的查找原理还是二分查找,我在二叉搜索树中已经详细过,在此不再赘述。
平衡二叉树首先是一棵二叉排序树或二叉搜索树,二叉排序树在创建时,由于数据基本有序,会导致创建出的二叉排序树呈单枝状,或者随机数据的插入、删除导致二叉排序树左右失衡呈单枝状,就会导致二叉树排序的查询速度接近单向链表的O(N);
平衡二叉树要求左右子树的高度差不超过1,这样就会让二叉排序树左右均匀,让二叉排序树的查找速度接近二分查找(要了解AVL树和红黑树的区别)。
#### 哈希表查找:
在查找数据时需要进行一系列的比较运算,无论是顺序查找、二分查找、索引查找、这类的查找算法都是建立在比较的基础上的,但最理想的查找算法是不经过任何比较,一次存取就能查找到数据,那么就需要在存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和数据中的唯一的存储位置相对应。在查找时,根据对应关系找到给定的关键字所对应的数据,这样可以不经过比较可以直接取得所查找的数据。
我们称存储位置在关键字之间的对应关系为哈希函数,按这个思想建立的表为哈希表。
问题:假定有100万个0~255之间的随机数,然后输入一个0~255之间的整数,查询出该数一共出现了多少次。
##### 索引查找的优点:
1、对于顺序表的顺序查找,索引表可以缩小数据的查找范围。
2、对于顺序表的二分查找,索引表可以提高排序速度。
3、对于链表的可以在创建索引表后,进行二分查找。
##### 索引查找的缺点:
使用了额外的存储空间创建索引表,是一种典型的用空间换取时间的策略。
##### 索引查找的使用场景:
针对内存中的顺序表数据,索引查找提升的速度并不是特别大,而是对存储在机械硬盘中的数据的查找速度提升非常大,因此在数据库中索引查找使用的特别多。
#### 分块查找:
先对数据进行分块,可以根据日期进行分块,可以对整形数据根据数据的最后一位分为10个分表,针对字符串数据可以根据第一个字母分为26个分表,然后再对这些分表进行二分查找、顺序查找,它是分治思想的具体实现。
##### 分块查找的优点:
可以把海量数据进行分化,降低数据规模,从而提升查找速度。
##### 分块查找的缺点:
操作比较麻烦,可能会有一定的内存浪费。
#### 二叉排序树和平衡二叉树:
二叉排序树也叫二叉搜索树是根据数据的值进行构建的,然后进行前序遍历查找,它的查找原理还是二分查找,我在二叉搜索树中已经详细过,在此不再赘述。
平衡二叉树首先是一棵二叉排序树或二叉搜索树,二叉排序树在创建时,由于数据基本有序,会导致创建出的二叉排序树呈单枝状,或者随机数据的插入、删除导致二叉排序树左右失衡呈单枝状,就会导致二叉树排序的查询速度接近单向链表的O(N);
平衡二叉树要求左右子树的高度差不超过1,这样就会让二叉排序树左右均匀,让二叉排序树的查找速度接近二分查找(要了解AVL树和红黑树的区别)。
#### 哈希表查找:
在查找数据时需要进行一系列的比较运算,无论是顺序查找、二分查找、索引查找、这类的查找算法都是建立在比较的基础上的,但最理想的查找算法是不经过任何比较,一次存取就能查找到数据,那么就需要在存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和数据中的唯一的存储位置相对应。在查找时,根据对应关系找到给定的关键字所对应的数据,这样可以不经过比较可以直接取得所查找的数据。
我们称存储位置在关键字之间的对应关系为哈希函数,按这个思想建立的表为哈希表。
问题:假定有100万个0~255之间的随机数,然后输入一个0~255之间的整数,查询出该数一共出现了多少次。
索引查找的优点:
1、对于顺序表的顺序查找,索引表可以缩小数据的查找范围。
2、对于顺序表的二分查找,索引表可以提高排序速度。
3、对于链表的可以在创建索引表后,进行二分查找。
索引查找的缺点:
使用了额外的存储空间创建索引表,是一种典型的用空间换取时间的策略。
索引查找的使用场景:
针对内存中的顺序表数据,索引查找提升的速度并不是特别大,而是对存储在机械硬盘中的数据的查找速度提升非常大,因此在数据库中索引查找使用的特别多。
分块查找:
先对数据进行分块,可以根据日期进行分块,可以对整形数据根据数据的最后一位分为10个分表,针对字符串数据可以根据第一个字母分为26个分表,然后再对这些分表进行二分查找、顺序查找,它是分治思想的具体实现。
分块查找的优点:
可以把海量数据进行分化,降低数据规模,从而提升查找速度。
分块查找的缺点:
操作比较麻烦,可能会有一定的内存浪费。
二叉排序树和平衡二叉树:
二叉排序树也叫二叉搜索树是根据数据的值进行构建的,然后进行前序遍历查找,它的查找原理还是二分查找,我在二叉搜索树中已经详细过,在此不再赘述。
平衡二叉树首先是一棵二叉排序树或二叉搜索树,二叉排序树在创建时,由于数据基本有序,会导致创建出的二叉排序树呈单枝状,或者随机数据的插入、删除导致二叉排序树左右失衡呈单枝状,就会导致二叉树排序的查询速度接近单向链表的O(N);
平衡二叉树要求左右子树的高度差不超过1,这样就会让二叉排序树左右均匀,让二叉排序树的查找速度接近二分查找(要了解AVL树和红黑树的区别)。
哈希表查找:
在查找数据时需要进行一系列的比较运算,无论是顺序查找、二分查找、索引查找、这类的查找算法都是建立在比较的基础上的,但最理想的查找算法是不经过任何比较,一次存取就能查找到数据,那么就需要在存储位置和它的关键字之间建立一个确定的对应关系,使每个关键字和数据中的唯一的存储位置相对应。在查找时,根据对应关系找到给定的关键字所对应的数据,这样可以不经过比较可以直接取得所查找的数据。
我们称存储位置在关键字之间的对应关系为哈希函数,按这个思想建立的表为哈希表。
问题:假定有100万个0255之间的随机数,然后输入一个0255之间的整数,查询出该数一共出现了多少次。