技术微刊
算法
要了解算法,就要先了解算法的时间复杂的。
该如何判断一个算法的好坏,很关键的一点就是拥有较少的程序运行时间,也就是较小的时间复杂度(time complexity)。那么什么是算法的复杂度呢:
简而言之,就是一个算法要花多少时间执行。
我们先以一个简单的算法导入概念:
#include<stdio.h>
int main(){
int a[10]={32,44,1,65,123,48,6,133,34,78};
int i,max;
max=a[0];
for(i=0;i<10;i++){
if(a[i]>max)max=a[i];
}
printf("%d\n",max);
return 0;
}
中间那部分是寻找最大值的算法。
指令 | 执行次数 |
---|---|
max=a[0] | 1 |
for(i=0;i<10;i++) | 9 (n-1) |
if(a[i]>max)max=a[i] | 9 (n-1) |
printf("%d\n",max) | 1 |
算法复杂度 | 20 (2n) |
这个算法的复杂度是2n, n是输入数值的个数。
我们常常使用O(n) (读作big O n)来表示一个算法的时间复杂度。 一般来说O里面是多次的式子,我们取最高次。
如一个算法复杂度是 n4+n2+n
那我们取写作O(n4)
排序介绍
下面我介绍二种排序:桶排序,插入排序
桶排序:
个人认为桶排序是最适合初学者,而且桶排序在一般情况下是最快的排序。
比如我们要排序的数字是8 5 5 3 2
那么我们可以申请一个一维数组a[10]来存放
首先我们将数组中的元素全部置0处理
下面开始处理要排序的数
首先是8 那么将a[8]++变成a[8] =1
然后是5 那么将a[5]++变成a[5]=1
后面一个元素还是5那么还是将a[5]++变成a[5]=2
以此类推
最后我们从1开始到数组结束
如果数组的元素大于0就输出这个数
实现代码
#include<stdio.h>
int main(){
int i;
int a[10]={0};
int b[5]={8,5,5,3,2};
for(i=0;i<5;i++)
{
a[b[i]]++;
}
for(i=0;i<11;i++)
{
while(a[i]>0){
printf("%d ",i);
a[i]--;
}
}
return 0;
}
你们可以把标记数组想象为桶,要排序的数字就是在对应的桶里放小旗子。然后从第一个桶开始输出有旗帜的桶。
所以可以得知桶排序算法复杂度就是桶的个数N和要排序的数据个数M之和就是O(N+M)
对比冒泡排序的 O(N2) 是不是快多了呢。但是桶排序有诸多的限制比如不能排序实数组,面对未知数组的大小的排序不是很好用。面对巨量的排序可能无法进行。
插入排序:
插入排序的核心思想就是插入,而且是插入有序的数组。你可以把要排序的数据的第一个元素想象成已经排过序的数,从第二个数开始插入到有序数列中。下面首先给出代码实现。我们从代码中先理解一下。插入的数首先和前面一个数比较,如果小于前面一个数那么前面一个数向后一位然后和再一个前面的数进行比较。如果比较失败那么插入这个数。
实现代码
#include<stdio.h>
int main(){
int a[10]={99 ,223,435,12,546,13,456 ,4523,124,438};
int i,j,key;
for(j=1;j<10;j++){
key=a[j];i=j-1;
while(i>=0&&a [i]>key){ //如果比它小则后移
a[i+1]=a[i];i=i-1;
}
a[i+1]=key;
}
for(i=0;i<10;i++)printf("%d ",a[i]);
return 0;
}
插入排序的算法复杂度涉及到多重考虑这边直接给出答案 O(n2) 可以看出插入排序和冒泡拥有近乎一样的算法复杂度,但为什么还要使用插入排序呢?
插入排序在某些情况下可以说是优化算法的一个好方法:比如拓展训练合并果子一题:
这题看似简单实则暗藏小陷阱,看似只要把最小的依次加起来,但其实最小的加起来的数不一定还是最小,加一次过后的数组需要重新排序,但我们知道其实只有一个元素未排序,这时候插入排序便起到了作用。偷偷告诉你们小编也被坑过好几回用了选择排序最后TLE,所以我就选择了插入排序来优化我的算法。
实现代码
#include<stdio.h>
void pai(int a[],int n)
{
int i,j,key;
key=a[n]; //直接进行插入
i=n-1;
while(i>=1&&a[i]<key){
a[i+1]=a[i];
i=i-1;
}
a[i+1]=key;
}
int main(){
int sum,n,i,j,key,t,a[10001];
scanf("%d",&n);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
sum=0;
for(j=1;j<=n;j++){
key=a[j]; //第一次完整插入排序
i=j-1;
while(i>=1&&a[i]<key){
a[i+1]=a[i];
i=i-1;
}
a[i+1]=key;
}
while(n>1){
t=a[n]+a[n-1];
sum+=t;
a[n-1]=t;
n--;
pai(a,n);
}
printf("%d\n",sum);
return 0;
}
首先我把第一次输进去的数组进行从大到小进行排序,这是为了什么呢,因为它要求每次把最小的两个合并,如果从小到大,那么数组的前面会有空余,进行下一次操作时不会特别的方便,所以我就从大到小排序每回操作数组最后的两个数。然后我将最后两个数的和给道倒数第二个数同时另数组长度减一,这时候它就变成了这个数组最后一个元素,而前面的元素是有序的,只要把最后一个数插入进去即可。因为最小的两个数加起来也不会很大,实际只要比较较少次数函数变回返回结束。如9 4 3 2 最后两个相加给到倒数第二个元素,这时候数组变成9 4 5,我只需要比较一次即可插入算法复杂度及其小,如果我在这边用了选择或者冒泡,那么他就要进行 n2 次操作,n为数组的长度。极大优化了算法的效率。