本文为学习基础排序算法之后的整理的笔记,不完全为原创。个人学习能力有限,图片是本人制作,文字、图片可能存在错误,若发现希望能够指出。
因已有大佬对各个算法进行了深度分析,故此篇博客只是记录、整理、分享我自学算法的笔记和过程,希望能与算法学习者们交流,请看官们轻喷。
冒泡排序
从序列的一端开始比较相邻两个数字的大小,根据结果交换两个数的位置。此过程中数据就像泡泡一样,从一端“漂浮”到另一端。
以下为图解:
在学习过程中,思想比较好理解,可以写出如下代码:
public class BubbleSort {
public static void BubbleSort (int []A) {
for (int i = 0; i < A.length; i++) {
for (int j = 0; j < A.length - 1; j++) {
if (A[j+1] < A[j]) {
Swap.swap(A,j,j+1);
}
}
}
}
}
其中Swap的定义是:
public class Swap {
public static void swap(int[] A,int i,int t){
int temp=0;
temp=A[i];
A[i]=A[t];
A[t]=temp;
}
}
设数据数为n,则可以看出,第1轮要比较n-1次,第2轮需要比较n-2次…第 n-1 轮需要比较1次。所以总的比较次数为 (n - 1) + (n - 2) + … + 1 ≈ n2/2。其比较次数固定,与输入数据的排列顺序无关。 而交换次数与输入数据的排列顺序有关,以极端思想来看,如果输入数据刚好以目标顺序排列,则不需要任何交换,反之则每次比较都需交换数据。所以可知其时间复杂度为O(n2)。
查找排序
从待排序的数据中线性查找最小值(从小到大排序时,从大到小则寻找最大值),将其位置与未排序的数据中最左边的数据位置进行交换,交换完成的数据视为已排序。
代码如下(从小到大):
public class SelectionSort {
public static void selection(int[] A){
int key=0, temp=0;
for(int I=0;i<A.length-1;i++){
key=i; //记录未排序数据的首位位置,从这里开始寻找最小值
for (int j=i+1;j<A.length;j++){
if(A[key]>A[j]){ //寻找最小值
key=j; //将最小值的下标记录在key
}
}
if(i!=key)
Swap.swap(A, i, key); //若key值发生变化则交换数据位置,swap参考冒泡中的定义
}
}
}
选择排序使用线性查找,需要比较(n-1)+(n-2)+…+1 ≈ n2/2 次。交换次数与输入数据有关,若输入数据刚好为目标顺序,则不需要交换,反之则最多需要n-1次交换。复杂度为O(n2)。
插入排序
插入排序(以从小到大排序为例)是从左对数据进行比较排序的算法。排序时,可以把数组看成左边(已归位部分)和右边(未归位)两部分。从未归位部分的左边第一个数据开始,将其与左边已归位部分的数据比较,小于则与其交换位置,继续向左比较,大于则结束比较。
简单来说,就是从未排序(未归位)的部分中依次取出数据,在已归位的部分里为它寻找合适位置插入。
public class InsertionSort {
public static void InsertionSort(int[] A) {
int key = 0, flag = 0;
for (int i = 1; i < A.length; i++) {
key = A[i]; //把进行交换的数存入key
flag = i - 1; //flag为在已归位的部分中为需要交换的数据寻找合适位置的一个"指针"(i-1即已归位部分最右边的数据)
while (flag >= 0 && A[flag] > key) {//指针指向的数据大于key时
A[flag + 1] = A[flag];//交换数据
A[flag] = key; //重新给key赋值
flag--; //指针向左移,准备下一次比较
}
}
}
}
插入排序需要将未排序部分的数据依次取出与左边(已归位部分)的数据进行比较,若比较时左边的数据更小,就不需要再比较,直接结束此轮操作。但若每一次取出的数据都比左边(已归位)部分所有数据都小时,就是坏的情况。则第n次取数据时需要操作n-1次。时间复杂度为O(n2)。
归并排序
归并排序将待排序的序列尽可能分成长度相等的两半,细分到每个子序列都只有一个数据时结束,从最小的子序列一步一步合并成有序的更大的子序列,再合并成有序的原序列。
划分过程如图:
合并过程:
代码如下(从小到大):
public class MergeSort {
public static void merge(int[] A,int left,int right,int[] temp){//A:排序数组,temp:暂时存放数据的额外数组;
if(right-left>1){
//划分
int m=left+(right-left)/2;//使其尽量分成元素数量相等的两半数组;m为前半数组的最大下标()
int p=left,q=m,i=left;//p为前半数组的"指针"(下标),q为后半数组的"指针"(下标),i为额外数组的"指针"(下标);
//
merge(A, left, m, temp);//调用递归求解(划分数组再合并)前半
merge(A, m, right, temp);//后半
//合并
while (p<m||q<right){//当左右两半数组任意数组还有未合并的数据时
if(q>=right||p<m&&A[p]<=A[q])//依次比较左右两半数组对应下标的数据的大小,将小的抄入额外数组中;
temp[i++]=A[p++];//从左半数组抄入额外数组;每抄入一个数据就使其下标p后移一位;
else
temp[i++]=A[q++];//从右半数组抄入额外数组;
}
for(i=left;i<right;i++)//从额外数组抄回原数组A。
A[i]=temp[i];
}
}
}
对于归并排序过程来说,每一步划分的子序列们中数据个数都是n,所以每行的运行时间都为O(n)。长度为n的序列分到最小,可以分成 log2n 次。因此,可以知道时间复杂度为 O(nlogn)。
致谢
最后,感谢学习和写作过程给我帮助的同学与朋友们。
新人第一次发博客,内容上可能有错误,希望读者看客们能够提出,让我加以改正,谢谢阅读。