几种排序方法的总结
参考文章链接:https://blog.csdn.net/jackesy/article/details/80135033
一.选择排序
基本思路:首先知道数组中最小的那个元素,其次将它与数组中的第一个元素交换位置(如果第一个元素是最小元素那么久和它自己交换)。再次在剩下的元素中找到最小元素,将它与数组的第二个元素交换位置,如此往复知道将整个数组排序。
时间复杂度为:
public static void Sort(int[] data){
for(int i=0;i<data.length;i++)
{ int temp=data[i];
int flag=i;
for(int j=i+1;j<data.length:j++)
{
if(data[j]<data[i])
{ temp=data[j];
flag=j;
}
}
if(flag!=i)
{ data[flag]=data[i];
data[i]=temp;
}
}
}
二 直接插入排序
将当前数据插入到已经排好序的序列中。
时间复杂度:
public static void Sort(int[] data){
int temp;
for(int i=1;i<length;i++){
temp=data[i];
for(int j=i-1;j<=0;j--){
if(temp<data[j]){
data[j+1]=data[j];
}
else
{
break;
}
}
data[j+1]=temp;
}
}
三 冒泡排序
设排序表长为n,从后向前或者从前向后两两比较相邻元素的值,如果两者的相对次序不对(A[i-1]> A[i]),则交换它们,其结果是将最小的元素交换到待排序序列的第一个位置,我们称它为一趟冒泡。下一趟冒泡时,前一趟确定的最小元素不再参与比较,待排序序列减少一个元素,每趟冒泡的结果把序列中最小的元素放到了序列的”最前面”。
以下这个代码的思路是对于一个序列,从前往后两两比较相邻的值,如果前一个的值大于就一个值,那么就交换两个数字的位置,直到将最大的元素交换到最后的位置。
时间复杂度:
空间复杂度:O(1)
public static void Sort(int[] data){ //排序后的结果是从小到大,这个程序和一般的不太一样
int flag=data.length;
while(flag>0){
int k=flag;
flag=0;
for(int j=1;j<k:j++){
int temp=data[i];
if(data[j-1]>data[j]]){
temp=data[j-1];
data[j-1]=data[i];
data[j]=temp;
flag=j; //这个flag在这里表示的是交换的个数。
}
}
}
}
以下是更一般的写法:(来自网上大神的写法)
public class Maopao {
public static void getSortD_X(int[] array){ //从大到小的写法
for (int i = 0; i < array.length; i++) {
for(int j=0;j<array.length-1;j++){
if(array[i]>array[j]){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
}
System.out.print("从大到小:");
for (int i = 0; i < array.length; i++) {
if(i==array.length-1){
System.out.println(array[i]);
}else{
System.out.print(array[i]+",");
}
}
}
public static void getSortX_D(int[] array){//从小到大
for (int i = 0; i <array.length; i++) {
for(int j=0;j<array.length-i-1;j++){
if(array[j]>array[j+1]){
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
System.out.print("从小到大:");
for (int i = 0; i < array.length; i++) {
if(i==array.length-1){
System.out.println(array[i]);
}else{
System.out.print(array[i]+",");
}
}
}
}</span>
四.快速排序
当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
优点:实现简单,是原地排序(只需要一个很小的辅助栈)(这部分不太确定),且将长度为N的数组排序所需的时间和NlgN成正比,另外快速排序的内循环比大多数排序算法都要短小,速度比较快;
从其他地方找到的数据:
最优的情况下空间复杂度为:O(logn) ;每一次都平分数组的情况
最差的情况下空间复杂度为:O( n ) ;退化为冒泡排序的情况
时间复杂度:
最好情况下是O(nlogn)。每次划分都比较均匀,正好可以将数组分为差不多两半
最差情况下是。待排序的序列为正序或者是逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置。
平均情况时间复杂度是O(nlogn)
缺点:非常脆弱
分治的排序算法,将一个数组分成两个子数组,将两部分单独排序。
以下代码是直接使用数组的第一个元素作为快速排序的切分点进行排序,应该是一种经典快排的思路:还有一个是随机快速排序,产生一个随机位置,然后与数组第一个元素进行交换。
public class Quick
{
public static void sort(int[] a){
sort(a,0,a.length-1);
}
private static void(int[]a, int lo,int hi)
{ if(hi<=lo) return;
int j=partition(a,lo,hi); //切分元素
sort(a,lo,j-1); //将左半部分进行排序
sort(a,j+1,hi); //将右半部分进行排序
}
private static int partition(int a[],int lo,int hi)
{ //将数组切分为a[lo...i-1],a[i],a[i+1...hi]
int i=lo,j=hi+1; //左右扫描指针
int v=a[lo]; //切分元素
while(true)
{ //扫描左右,检查扫描是否结束并交换元素
while(a[++i]<v) if(i==hi) break; //找到从左往右第一个比切分元素大的元素
while(v<a[--j]) if (j==lo) break; //找到从右往左第一个比切分元素小的元素
if (i>=j) break;
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
int temp1=a[lo];
a[lo]=a[j];
a[j]=a[lo];
return j;
}
}
对排序算法的一些改进优化:
(1)切换到插入排序
对于小数组快速排序比插入排序要慢;因为递归,快速排序方法在小数组也会调用自己
方法:在排序小数组中切换到插入排序。
(2)中位数切分
使用子数组中的一小部分元素的中位数来切分数组
(3)熵最优排序
对于大量重复元素的数组
将数组切分为三部分,分别对应小于等于大于切分元素的数组元素。
五,堆排序
堆排序可以分为两个阶段:在堆的构造阶段,将原始数据重新安排进一个堆中;然后在下沉阶段,从堆中按照递减顺序取出所以元素并得到排序结果。
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储顺序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
当一颗二叉树的每一个结点都大于等于它的两个子节点时,它被称为堆有序。相应的在堆有序的二叉树中,每一个结点都大于等于它的父结点。
对堆排序的一些分析:
以总体来说,堆排序的时间复杂度为O(nlogn),由于堆排序对原始记录的状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的时间复杂度了。
空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行的,因此堆排序也是一种不稳定的排序方法。空间复杂度为O(1).
这里补充一点,在建堆完成之后是将堆以数组的形式进行保存,对应关系如下所示:
数组0下标不使用,使用数组下标1作为根节点的下标
父节点i的左子节点在位置(2*i);
父节点i的右子节点在位置(2*i+1);
子节点i的父节点在位置(i/2);
堆排序的程序如下:(构造大顶堆)
public static void sort(int[] a){
int N=a.length:
for(int k=N/2-1;k>=0;k--)
{
sink(a,k,N-1);
}
for(int k=N-1;k>0;k--){ //将堆顶的最带元素放在最后的位置,对之前元素重新构造堆
int temp = a[0];
a[0] = a[k];
a[k] = temp;
sink(a, 0, k- 1);
}
}
public static void sink(int[] a,int k, int N) {
while(2*k<=N)
{
int j=2*k;
if(j<N&&a[j]<a[j+1]) j++;
if(a[k]>=a[j]) break;
int temp = a[k];
a[k] = a[j];
a[j] = temp;
k=j;
}
}
六 归并排序
归并是将两个有序的数组归并为一个更大的有序数组。
其基本原理如下:对于给定的一组记录,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列
以下是自顶向下的归并排序:
public static sort(int[] a, int lo, int mid, int hi) {
int[] aux = new int[high - low + 1];
int i= lo;// 左指针
int j = mid + 1;// 右指针
for(int k=lo;k<=hi;k++){
aux[k]=a[k];
}
for(int k=lo;k<=hi:k++){
if (i>mid) a[k]=aux[j++]; //左半边用尽去右边元素
elseif(j>hi) a[k]=aux[i++];
else if (aux[j]<aux[i]) a[k]=aux[j++];
else a[k]=aux[i++];
}
}
public static mergeSort(int[] a, int lo, int hi) {
if(hi<=lo) return;
int mid = lo+(hi-lo)/2;
if (low < high) {
mergeSort(a, low, mid);//将左边排序
mergeSort(a, mid + 1, hi);//将右边排序
merge(a, lo, mid, hi);//左右归并
}
}
算法分析:
一趟归并需要将数组 a[]中相邻的长度为h的有序序列进行两两归并.并将结果放到aux[]中,这需要将待排序列中的所有记录扫描一遍,因此耗费O(n),而由完全二叉树的深度可知,整个归并排序需要进行log2n(2表示以2为底)次,因此总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。
由于归并排序在归并过程中需要与原始序列同样数量的存储空间存放归并结果以及递归时深度为的栈空间,因此空间复杂度为O(n+logn).这个不确定,也有说O(n)d
另外,对代码进行仔细研究,发现merge函数中有if (a[i] < a[j]) 的语句,说明它需要两两比较,不存在跳跃,因此归并排序是一种稳定的排序算法。
也就是说,归并排序是一种比较占内存,但却效率高且稳定的算法