常见的排序
概述
常见的10大排序,这里先详细的介绍前7种排序;
稳定性:
定义:如果两个元素具有相同的关键字,那么在排序后他们的相对顺序不变;
排序前有标记的5在前面,排序后依旧在前面即稳定,如果位置互换了就不稳定;
直接插入排序
- 原理:将未排序的数据插入到已排序部分的合适位置。通常从第二个元素开始,将其与前面已排好序的元素进行比较,找到合适位置插入。
- 使用场景:对于接近有序的数组效率较高。
- 与其他算法的联系:希尔排序是对插入排序的优化。插入排序在处理部分有序数据时具有一定优势,其思想在一些高级算法处理小规模数据时也会被借鉴,比如快速排序在处理小规模数据时可能会切换为插入排序。
代码:
public void Insertint(int[] array){
for (int i = 1; i < array.length; i++) {
int j=i-1;
int tem=array[i];
for ( ; j >=0; j--) {
if(array[j]>tem){
array[j+1]=array[j];
}else {
array[j+1]=tem;
break;
}
array[j]=tem;
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定
希尔排序
- 原理:对插入排序的一种优化,通过设置不同的步长序列,逐步对数据进行分组和局部排序,最终实现整体数据的有序。
- 使用场景:中等规模数据的排序。
- 与其他算法的联系:源于插入排序,通过改进插入排序的方式提高效率。在处理某些数据时,性能可能介于简单排序算法和高级排序算法之间。
代码:
public void shell(int[] array){
int gap=array.length/2;
while (gap>0){
shellsert(array,gap);
gap=gap/2;
}
}
private void shellsert(int[] array, int gap) {
for (int i = gap; i < array.length; i++) {
int j=i-gap;
int tem=array[i];
for ( ; j >=0; j-=gap) {
if(array[j]>tem){
array[j+gap]=array[j];
}else {
array[j+gap]=tem;
break;
}
array[j]=tem;
}
}
}
时间复杂度:O(n^1.3) 空间复杂度:O(1) 稳定性:不稳定
选择排序
- 原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 使用场景:适用于数据量较小、对效率要求不高的情况。
- 与其他算法的联系:和冒泡排序一样是较为简单的算法。但选择排序在每一轮只进行一次交换,效率相对冒泡排序可能会高一些。与堆排序有一定联系,堆排序在选择最大(小)元素的过程中利用了堆这种数据结构,而选择排序是直接遍历寻找。
public static void selectSort(int[] array){ //选择排序
for (int i = 0; i < array.length; i++) {
int minindex=i;
int j=0;
for ( j = i+1; j < array.length; j++) { //每次找出最小值的下标,再换到i位置
if(array[minindex]>array[j]){
minindex=j;
}
}
swap(array,minindex,i);
}
}
public static void swap(int[] array,int ret1,int ret2){
int tem=array[ret1];
array[ret1]=array[ret2];
array[ret2]=tem;
}
时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:不稳定
堆排序
- 原理:利用堆这种数据结构进行排序。首先将待排序序列构建成一个大顶堆(或小顶堆),然后将堆顶元素与最后一个元素交换,再对剩余元素重新调整为堆,如此反复,直到序列有序。
- 使用场景:适用于需要高效排序且对空间要求不高的情况。
- 与其他算法的联系:在选择最大(小)元素方面与选择排序有相似之处,但利用堆的特殊性质可以更高效地进行选择。
代码:
public static void HeapSort(int[] array){//堆排序
CreatHeap(array); //建堆
int end=array.length-1;
while (end>0){
swap(array,0,end); //把最大值和最后一个互换,
siftdown(array,0,end); //除最后一个找出最大值
end--;
}
}
public static void CreatHeap(int[] array){
for (int parent = (array.length-1-1)/2; parent >=0; parent--) {
siftdown(array,parent,array.length);
}
}
public static void siftdown(int[] array,int parent,int len){
int child=2*parent+1;
while (child<len){
if(child+1<len&&array[child+1]>array[child]){//左右孩子中找出大的孩子
child++;
}
if(array[parent]<array[child]){ //<是大根堆,>是小根堆
swap(array,parent,child);
parent=child;
child=2*parent+1;
}else {
break ;
}
}
}
public static void swap(int[] array,int i,int j){
int tem=array[i];
array[i]=array[j];
array[j]=tem;
}
时间复杂度:O(nlogn) 空间复杂度:O(1) 稳定性:不稳定
冒泡排序
- 原理:重复地走访要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
- 使用场景:数据量较小且对效率要求不高的情况。
- 与其他算法的联系:是一种较为简单直观的排序算法,效率较低。其他一些算法可以看作是对冒泡排序思想的改进和扩展。例如希尔排序可以看作是对插入排序的优化,而插入排序在某些方面又与冒泡排序有相似之处,都是通过不断比较和交换元素来实现排序。
代码:
public static void bubbleSort(int[] array){
for(int i=0;i<array.length;i++){
for (int j = 0; j < array.length-1-i; j++) {
if(array[j]>array[j+1]){
int tem=array[j] ;
array[j]=array[j+1];
array[j+1]=tem;
}
}
}
}
public static void bubbleSortPerfect(int[] array){//优化后的冒泡排序,有序可达到O(n)
for(int i=0;i<array.length;i++){
boolean flg=false;//设置标志位
for (int j = 0; j < array.length-1-i; j++) {
if(array[j]>array[j+1]){
int tem=array[j] ;
array[j]=array[j+1];
array[j+1]=tem;
flg=true;
}
}
if(!flg){
break;
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1) 稳定性:稳定
快速排序
- 原理:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
- 使用场景:数据量大且随机性较强的情况。
- 与其他算法的联系:采用分治思想,与归并排序类似。但快速排序是在原地进行划分,效率相对较高。在某些情况下会使用插入排序处理小规模数据,以提高整体效率。
代码:(挖坑法)
public static void quickSort(int[] array){
quick2(array,0,array.length-1);
// write code here
}
private static void quick(int[] array, int start, int end) {
if(start>=end){
return ;
}
int pivot = partition(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
private static int partition(int[] array,int stact,int end){//挖坑法
int tem=array[stact];
int left=stact;
int right=end;
while (left<right){
while (left<right&&array[right]>=tem){
right--;
}
array[left]=array[right];
while (left<right&&array[left]<=tem){
left++;
}
array[right]=array[left];
}
array[right]=tem;
return right;
}
Hoare版
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
int pivot = array[left];
while (i < j) {
while (i < j && array[j] >= pivot) {
j--;
}
while (i < j && array[i] <= pivot) {
i++;
}
swap(array, i, j);
}
swap(array, i, left);
return i;
}
时间复杂度:O(nlogn) 空间复杂度:O(logn) 稳定性:不稳定
归并排序
- 原理:采用分治策略,将待排序序列分成若干个子序列,分别进行排序,然后将已排序的子序列合并成一个有序的序列。
- 使用场景:对稳定性要求较高,数据量大的情况。
- 与其他算法的联系:和快速排序一样基于分治思想。归并排序是稳定的排序算法,在某些需要保证数据原有顺序的场景下有优势。快速排序在某些方面可以看作是归并排序的一种更高效但不稳定的变体。
代码(递归):
public static void mergeSort(int[] array){
mergeSort1(array,0,array.length-1);
}
private static void mergeSort1(int[] array, int left, int right) {
if(left>=right){
return;
}
int mid=(left+right)/2;
mergeSort1(array,left,mid); //分离
mergeSort1(array,mid+1,right); //分离
merge(array,left,mid,right); //合并
}
private static void merge(int[] array, int left, int mid, int right) {//数组有序合并
int s1=left;
int e1=mid;
int s2=mid+1;
int e2=right;
int k=0;
int[] num=new int[right-left+1];
while (s1<=e1&&s2<=e2){
if(array[s1]<=array[s2]){
num[k++]=array[s1++];
}
if(array[s1]>array[s2]){
num[k++]=array[s2++];
}
}
while (s1<=e1){
num[k++]=array[s1++];
}
while (s2<=e2){
num[k++]=array[s2++];
}
for (int i = 0; i <k; i++) {
array[left+i]=num[i];
}
}
时间复杂度:O(nlogn) 空间复杂度:O(n) 稳定性:稳定
代码(非递归):
// 归并排序---非递归
public static void mergeSortNor(int[] array){
int gap=1;
while (gap<=array.length){
for (int i = 0; i < array.length; i=i+2*gap) {
int left=i;
int mid=left+gap-1;
if(mid>array.length){
mid=array.length;
}
int right=mid+gap;
if(right>array.length-1) {
right = array.length - 1;
}
merge(array,left,mid,right);
}
gap=gap*2;
}
}