为什么要学习算法
俗话说条条大路通罗马,我们在使用代码解决问题时,不同的思路会有不同的代码,同时也会有不一样的执行效率。那么如何让我们的代码更加简洁和提高执行效率便是一个有意思的话题。作为入门的菜鸟一枚,在这里分享一下我的学习思考。
衡量算法效率的标准
1.时间复杂度
时间复杂度是一个函数,它定性描述该程序的运行时间。
2.空间复杂度
一个程序的空间复杂度是指运行完一个程序所需内存的大小。
- 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
- 可变空间。这部分空间主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
//我自己感觉时间复杂度度与空间复杂度中间有着联系
3.稳定性
什么叫算法的稳定性:算法稳定性指的是在一组待排序记录中,如果存在任意两个相等的记录R和S,且在待排序记录中R在S前,如果在排序后R依然在S前,即它们的前后位置在排序前后不发生改变,则称为排序算法为稳定的。
//以后细讲如何提高效率
入门排序(冒泡排序,插入排序,选择排序)
1.冒泡排序(bubble sort)
原理:从第一个数据开始,相邻的数据比较大小(默认升序)大的向后移,每一趟将遍历的数据移到后面,完成排序。
具体说明:每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
代码
//冒泡排序两两比较的元素是没有被排序过的元素--->
void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){//控制比较轮次,一共 n-1 趟
for(int j=0;j<array.length-1-i;j++){//控制相邻元素进行比较
if(array[j] > array[j+1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;//大的数据向后移动
}
}
}
特点
1.时间复杂度 冒泡排序算法的每一轮要遍历所有元素,轮转的次数和元素数量相当,所以时间复杂度是O(N^2),经过优化后,最优的情况,序列已经是顺序的,那么只要进行一次循环,所以最优时间复杂度是O(N)
2.空间复杂度 冒泡排序算法排序过程中需要一个临时变量进行两两交换,所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性 冒泡排序算法在排序过程中,元素两两交换时,相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法
2.插入排序
原理:插入排序的主要思想是每次取一个列表元素与列表中已经排序好的列表段进行比较,然后插入从而得到新的排序好的列表段,最终获得排序好的列表。
具体说明:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后\n重复步骤2~5
代码
void insert_sort(int a[],int n){
for(int i=0;i<n;i++)
for(int j=i;j>0;j--)
if(a[j]<a[j-1]){
int b=a[j];
a[j]=a[j-1];
a[j-1]=b;
}
}
特点
1.时间复杂度 插入排序算法要进行n-1轮,每一轮对比的元素最坏的情况依次是1, 2, 3 … n-1,所以时间复杂度是O(N^2)
2.空间复杂度 插入排序算法排序过程中需要一个临时变量存储插入元素,所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性 插入排序算法在排序过程中,无序数列插入到有序区的过程中,不会改变相同元素的前后顺序,是一种稳定排序算法
3.选择排序
原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,继续放在起始位置知道未排序元素个数为0。
具体说明:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到未排序序列的起始位置。
- 重复第二步,直到所有元素均排序完毕。
代码
void select(int a[],int n){
for(int i=0;i<n;i++)
{
int j=i;
for(int k=i+1;k<n;k++)
{
if(a[j]>a[k])
j=k;
}
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
注意:在选择排序中,每趟都会选出最大元素与最小元素,然后与两端元素交换,此时,待排序序列中如果存在与原来两端元素相等的元素,稳定性就可能被破坏。如[5,3,5,2,9],在array[0]与array[3]元素交换时,序列的稳定性就被破坏了,所以选择排序是一种不稳定的排序算法。
特点
1.时间复杂度 选择排序算法的每一轮要遍历所有元素,共遍历n-1轮,所以时间复杂度是O(N^2)
2.空间复杂度 选择排序算法排序过程中需要一个临时变量存储最小元素(最大元素),所需要的额外空间为1,因此空间复杂度为O(1)
3.稳定性 选择排序算法是一种不稳定排序算法,当出现相同元素的时候有可能会改变相同元素的顺序
进阶排序
4 .快速排序
原理:二分法;分边而站
具体说明
- 先从数列中取出一个数作为基准数。
- 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 再对左右区间重复第二步,直到各区间只有一个数。
代码:
void quickSort(int a[], int start, int end){
if(start>end) return;
int i,j,base;
//i,j是循环变量
i=start;
j=end;
base=a[start];
while (i<j){
//从右边寻找小于基准数,左边寻找大于基准,找到后交换两数,如果没有找到,i=j跳出循环,所有数都完成了交换,等同于没找到跳出循环
while (i<j && nums[j]>=base) j--;
while (i<j && nums[i]<=base) i++;
if(i<j)
swap(nums,i,j);
}
}
特点
1.时间复杂度 快速排序算法在分治法的思想下,原数列在每一轮被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可再分为止,平均情况下需要logn轮,因此快速排序算法的平均时间复杂度是O(nlogn)
在极端情况下,快速排序算法每一轮只确定基准元素的位置,时间复杂度为O(N^2)
2.空间复杂度 快速排序算法排序过程中只是使用数组原本的空间进行排序,因此空间复杂度为O(1)
3.稳定性 快速排序算法在排序过程中,可能使相同元素的前后顺序发生改变,所以快速排序是一种不稳定排序算法
5.归并排序
原理:归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,归并排序对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的。
具体说明:
-
首先是分的步骤,将整个数组进行分割,分成了若干个单独的较小的单元。
-
然后是合并的过程,分别将分开的两个较小单元进行比较合并到原来的数组里面;将两个已经有序的子序列合并成一个有序序列, 换句话说
-
首先将待排序的元素分成大小大致相同的两个序列。
-
再把子序列分成大小大致相同的两个子序列。
-
如此下去,直到分解成一个元素停止,这时含有一个元素的子序列都是有序的。
-
进行合并操作,将两个有序的子序列合并为一个有序序列,如此下去,直到所有的元素都合并为一个有序序列。
代码:
-
分离
void mergesort(int x,int y) //分离,x 和 y 分别代表要分离数列的开头和结尾 { if (x>=y) return; //如果开头 ≥ 结尾,那么就说明数列分完了,就要返回 int mid=(x+y)/2; //将中间数求出来,用中间数把数列分成两段 mergesort(x,mid); mergesort(mid+1,y); //递归,继续分离 merge(x,mid,y); //分离玩之后就合并 } -
合并
void merge(int low,int mid,int high) //归并 //low 和 mid 分别是要合并的第一个数列的开头和结尾,mid+1 和 high 分别是第二个数列的开头和结尾 { int i=low,j=mid+1,k=low; //i、j 分别标记第一和第二个数列的当前位置,k 是标记当前要放到整体的哪一个位置 while (i<=mid && j<=high) //如果两个数列的数都没放完,循环 { if (a[i]<a[j] b[k++]=a[i++]; else b[k++]=a[j++]; //将a[i] 和 a[j] 中小的那个放入 b[k],然后将相应的标记变量增加 } //b[k++]=a[i++] 和 b[k++]=a[j++] 是先赋值,再增加 while (i<=mid) b[k++]=a[i++]; while (j<=high) b[k++]=a[j++]; //当有一个数列放完了,就将另一个数列剩下的数按顺序放好 for (int i=low;i<=high;i++) a[i]=b[i]; //将 b 数组里的东西放入 a 数组,因为 b 数组还可能要继续使用 }
特点
1.时间复杂度 归并排序算法每次将序列折半分组,共需要logn轮,因此归并排序算法的时间复杂度是O(nlogn)
2.空间复杂度 归并排序算法排序过程中需要额外的一个序列去存储排序后的结果,所占空间是n,因此空间复杂度为O(n)
3.稳定性 归并排序算法在排序过程中,相同元素的前后顺序并没有改变,所以归并排序是一种稳定排序算法