本文已参与「新人创作礼」活动,一起开启掘金创作之路。
排序算法是软件开发中非常重要的一环。本期就来讨论关于算法在面试中会遇到的一些问题,其中包含个人一些理解,由于本人学识有限,难免会有纰漏之处,还望读者能多多包涵。
上一期我们讲了冒泡排序、选择排序和堆排序。本期继续讲解其他的排序算法。
插入排序
插入排序思想比较简单,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表。插入排序的平均时间复杂度也是 ,空间复杂度为常数阶 ,具体时间复杂度和数组的有序性也是有关联的。
插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1 次,时间复杂度为 。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 。
插入排序过程用文字描述就是(从小到大排序):首先位置1上的数和位置0上的数进行比较,如果位置1上的数小于位置0上的数,将位置0上的数向后移一位,将1插入到0位置,否则不处理。位置k上的数和前面的k-1个数依次进行比较,如果位置K上的数更小,将之前的数向后移位,最后将位置k上的数插入不满足条件点,反之不处理。
Java实现如下:
public static void insertSort(int[] array) {
int temp,j;
for(int i=1;i<array.length;i++)
{
if(array[i-1]>array[i]) //如果前面比自己大
{
temp=array[i];
for(j=i-1;j>=0 && array[j]>temp;j--) //那么自己与前面所有的进行比较
{
array[j+1]=array[j];
}
array[j+1]=temp;
}
}
//输出
for(int k=0;k<array.length;k++) {
System.out.print(array[k]+" ");
}
}
public static void main(String[] args) throws IOException {
int[] array= {5,0,1,2,8,4};
insertSort(array);
}
在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
归并排序
归并排序(Merge sort)是建立在归并操作上的一种有效、稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
文字描述过程:首先让数组中的每一个数单独成为长度为1的区间,然后两两一组有序合并,得到长度为2的有序区间,依次进行,直到合成整个区间。
归并排序适用于数据量大,并且对稳定性有要求的场景。
Java代码实现版本:
public static void MergeSort(int[] k) {
int i,next,left_min,left_max,right_min,right_max;
int n=k.length;
//动态申请一个与原来数组一样大小的空间用来存储
int[] temp=new int[n];
//逐级上升,第一次比较2个,第二次比较4个,第三次比较8个。。。
for(i=1; i<n; i*=2)
{
//每次都从0开始,数组的头元素开始
for(left_min=0; left_min<n-i; left_min = right_max)
{
right_min = left_max = left_min + i;
right_max = left_max + i;
//右边的下标最大值只能为n
if(right_max>n)
{
right_max = n;
}
//next是用来标志temp数组下标的,由于每次数据都有返回到K,
//故每次开始得重新置零
next = 0;
//如果左边的数据还没达到分割线且右边的数组没到达分割线,开始循环
while(left_min<left_max&&right_min<right_max)
{
if(k[left_min] < k[right_min])
{
temp[next++] = k[left_min++];
}
else
{
temp[next++] = k[right_min++];
}
}
//上面循环结束的条件有两个,如果是左边的游标尚未到达,那么需要把
//数组接回去,可能会有疑问,那如果右边的没到达呢,其实模拟一下就可以
//知道,如果右边没到达,那么说明右边的数据比较大,这时也就不用移动位置了
while(left_min < left_max)
{
//如果left_min小于left_max,说明现在左边的数据比较大
//直接把它们接到数组的min之前就行
k[--right_min] = k[--left_max];
}
while(next>0)
{
//把排好序的那部分数组返回该k
k[--right_min] = temp[--next];
}
}
}
//输出
for (int j = 0; j < n; j++) {
System.out.print(k[j]+" ");
}
}
public static void main(String[] args) throws IOException {
int[] array= {5,0,1,2,81,-4,21,9};
MergeSort(array);
}
本代码没有采用递归方式,而是使用迭代方式。这样可以节省深度为的栈空间。
当有 n 个记录时,需进行 轮归并排序,每一轮归并,其比较次数不超过 n,元素移动次数都是 n,因此,归并排序的时间复杂度为 。归并排序时需要和待排序记录个数相等的存储空间,所以空间复杂度为 。
归并排序算法,是目前为止最重要的算法之一,是分治法的一个典型应用,需要牢记。