【数据结构】排序算法全方位剖析
文章目录
前言
1.排序算法是数据结构当中的重要知识,是将数据按照规定的顺序进行重新排序。
2.不同的排序算法性能差异很大,根据代码的实现,可以具备不同的时间复杂度、空间复杂度、以及稳定性。
提示:下面我将为大家详细解析六种常见的排序算法。
一、插入排序
1.思路:
插入排序类似整理扑克牌,假设手里有一副乱序的扑克牌,假设扑克牌的第一个元素为有序,从第二张牌开始,将第二张牌插入到前面已经有序的牌当中,重复上述步骤,可以实现所有牌有序。
2.步骤
1.从第一个元素开始,默认该元素已被排序
2.取下一个元素为tmp,从已排序的元素序列从后往前扫描
3.如果该元素大于tmp,则将该元素移到下一位
4.重复步骤3,直到找到已排序元素中小于等于tem的元素
5.将tmp插入到有序元素中的合适位置
6.重复步骤2~5,直到遍历完数组
3.代码实现
public static void insertSort(int[] array){
int i;
int j;
int len= array.length; //数组长度
外层循环,从数组的第二个元素开始(假设第一个元素有序)
for ( j = 1; j <len ; j++) {
int tmp=array[j]; //将待插入元素放入tmp变量
// 内层循环,从当前元素的前一个位置开始向前扫描,找到应该插入的位置
for ( i = j-1; i >=0 ; i--) {
// 如果扫描到的元素大于tmp,则将其向后移动一位
if(array[i]>tmp){
array[i+1]=array[i];
}else{
break;
}
}
array[i+1]=tmp; //插入至合适位置
}
}
4.演示图
5.总结:
时间复杂度:O(N²)且数组越接近有序,效率越高
空间复杂度:O(1)
稳定性:稳定
二、希尔排序
1.思路
基于插入排序算法的优化,当数组的元素趋于有序时,插入排序的效率更高,若是提前将数组进行预排序(让数组趋于有序),再进行插入排序,此时效率单比插入排序会高很多,这便是希尔排序。
2.步骤
1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作
便可实现数组元素趋于有序。
2.当增量的大小减到1时,进行一次直接插入排序,排序完成。
3.代码实现
public static void ShellSort(int[] array){
int len= array.length;
int gap=len/2;
while(gap>=1){
int i;
int j;
for(i=gap;i<len;i++){
int tmp=array[i];
for (j = i-gap; j >=0 ; j-=gap) {
if(array[j]>tmp){
array[j+gap]=array[j];
}else{
break;
}
}
array[j+gap]=tmp;
}
gap/=2;
}
}
4.演示图
4.总结:
1.当gap>1时,都是预排序,让整体更趋于有序,gap==1时,进行最后一次插入排序
2.时间复杂度:不确定,因为gap取值不同,导致时间复杂度不固定,但整体近似于O(N^1.25)
3.空间复杂度O(1)
4.稳定性:不稳定
三、选择排序
1.思路:
遍历数组,每趟遍历时找出数组的最大值,以及最小值,将最大值和最小值元素放至数组最左边和最右边,接着除去已经排好的的区域,对未排序区域继续进行找最大和最小值操作。
2.代码实现
public static void choiceSort(int[] array){
int left=0;
int right=array.length-1;
while(left<right){
int maxIndex=left;
int minIndex=left;
for (int i = left; i <=right ; i++) {
if(array[i]>array[maxIndex]){
maxIndex=i;
}
if(array[i]<array[minIndex]){
minIndex=i;
}
}
swap(array,minIndex,left);
if(left==maxIndex){
maxIndex=minIndex;
}
swap(array, maxIndex, right);
left++;
right--;
}
}
3.总结:
时间复杂度:O(N²)
空间复杂度:O(1)
稳定性:不稳定
四、堆排序
1.思路:
堆排序是基于数据结构中的比较排序算法。利用完全二叉树,进行排序。
升序:建大根堆 降序:建小根堆
将堆顶元素与末尾元素进行交换,在减少堆中有效元素个数,再将剩余元素调整成新堆,反复进行此操作,即可完成排序。
2.代码实现
public static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
public static void heapSort(int[] array){
int len=array.length;
creatHeap(array); //创小根堆
for (int i = array.length-1;i >0; i--) {
swap(array,0,i);
len--;
siftDown(array,0,len);
}
}
private static void creatHeap(int[] array){
int par=(array.length-1)/2;
for (; par >=0 ; par--) {
siftDown(array,par,array.length);
}
}
private static void siftDown(int[] array,int par,int len){
int child=2*par+1;
while(child<len){
//调整child为子节点最小值的下标
if(child+1<len&&array[child]>array[child+1]){
child=child+1;
}
if(array[par]>array[child]){
swap(array,child,par);
}
par=child;
child=2*par+1;
}
}
3.总结:
时间复杂度:O(N*logN)
空间复杂度:O(1)
稳定性:不稳定
五、快速排序
1.思路:
选一个关键字key,一般为最左边或最右边,然后利用key将数组中元素分为左右两部分:左部分值都比key小,右部分值都比key大,再利用递归思想选取左部分key重复操作和右部分的key重复操作。
2.代码实现:
public static void quick(int[] array){
quickSort(array,0,array.length-1);
}
public static void quickSort(int[] array,int left,int right){
if(left>=right)
return;
int pivot=partition(array,left,right);
quickSort(array,left,pivot-1);
quickSort(array,pivot+1,right);
}
private static int partition(int[] array,int left,int right){
int tmp=array[left];
while(left<right){
while(left<right&&array[right]>=tmp){
right--;
}
array[left]=array[right];
while(left<right&&array[left]<=tmp){
left++;
}
array[right]=array[left];
}
array[left]=tmp;
return left;
}
3.总结:
时间复杂度:O(NlogN)
空间复杂度:O(nlogN)
稳定性:不稳定
六、归并排序
1.思路:
①先将数组中所有元素拆散,分成未排序的子数组,直到每个子数组只包含一个元素。
②依次将子数组中元素与相邻的子数组进行排序,再合并成有序的子数组。
③重复执行合并和排序的过程,直到最终合并成一个完整的、有序的数组。
2.代码实现
public static void mergeSort(int[] array){
int left=0;
int right=array.length-1;
apart(array,left,right);
}
/**
* 递归将数组分割成更小的部分,直到每个部分只有一个元素或为空。
* 随后调用 merge 方法来合并这些部分。
* */
private static void apart(int[] array,int left,int right){
if(left==right){
return;
}
int mid=(left+right)/2;
//将数组中所有元素分离
apart(array,left,mid); //递归地分割数组的左半部分
apart(array,mid+1,right); //递归地分割数组的右半部分
// 合并分割后的两个子数组
merge(array,left,right,mid);
}
/**
* 合并两个已排序的子数组,并将合并后的结果存储回原始数组中。
*
* @param array 原始数组,包含两个待合并的子数组
* @param left 合并后数组的起始索引(也是原始左子数组的起始索引)
* @param right 合并后数组的结束索引(也是原始右子数组的结束索引)
* @param mid 左右子数组的分界索引
*/
private static void merge (int[] array,int left,int right,int mid){
int s1=left;
int e1=mid;
int s2=mid+1;
int e2=right;
int[] tmpArray=new int[right-left+1]; //创建一个临时数组来存储合并后的结果
int count=0; //记录tmpArray中实际元素个数
// 合并两个子数组,直到其中一个子数组的所有元素都被复制
while(s1<=e1&&s2<=e2){
if(array[s1]<array[s2]){
tmpArray[count]=array[s1];
s1++;
count++;
}else{
tmpArray[count]=array[s2];
s2++;
count++;
}
}
// 如果左子数组还有剩余元素,将它们复制到临时数组中
while(s1<=e1){
tmpArray[count]=array[s1];
s1++;
count++;
}
// 如果右子数组还有剩余元素,将它们复制到临时数组中
while(s2<=e2){
tmpArray[count]=array[s2];
s2++;
count++;
}
for (int i = 0; i < tmpArray.length ; i++) {
array[left+i]=tmpArray[i];
}
}