排序的基本原理以及Java实现

import java.util.Arrays;

/**
 * Created by wst on 2017/7/13.
 */
public class Sort {
    public static void main(String[] args) {
        int[] arr={1,4,3,4,5,34,9,8,2,1,0,10};
        Sort sort=new Sort();
        //sort.ShellInsert(arr);
       // sort.quickSort(arr,0,arr.length-1);
       // sort.bubbleSort(arr);
        sort.selectSortPlus(arr);
        System.out.println(Arrays.toString(arr));
    }
    //直接排序,适合排序数目较少,而且基本有序的情况
    //其原理是,数组中的第i个数,与它前面的i-1个数,从后向前进行比较,比它大就往后移一位
    //比他小保持不变,依次类推
    public void InsertionSort(int[] arr){
        int j;
        for (int i = 1; i <arr.length ; i++) {//第一个元素不用比较,所以从1开始
            int temp=arr[i];
            for (j = i; (j >0)&&(temp<arr[j-1]) ; j--) {
                arr[j]=arr[j-1];
            }
            arr[j]=temp;
        }
    }
    //二分排序
    //实现原理,首先给定一个元素K,通过二分查找的原理,找到下标i,下标比i大的数组元素
    // ,大于K,因此只要把K元素前面到下标i的数依次向后移动一位即可。
    public void BinSort(int[] arr){
        for (int i = 1; i <arr.length ; i++) {
            int temp=arr[i];
            int left=0;
            int high=i-1;
            int j;
            while (left<=high){
                int mid=(left+high)/2;
                if (temp > arr[mid]) {
                    left=mid+1;
                }
                else {
                    high=mid-1;
                }
            }
            for (j = i; j >left ; j--) {
                arr[j]=arr[j-1];
            }
            arr[j]=temp;
        }
    }
    //希尔排序,排序不稳定性。先缩小增量,再进行直接排序。
    //其原理是,通过gap把数组划分为不同的子数组,对这些子数组进行直接排序;同时
    //gap/2不断缩小gap,直至gap1时,进行最后一次直接排序,完成希尔排序。
    // (因为直接排序,对基本有序的数组很容易,因此用gap来划分子数组,进行直接排序
    // 把原子数组变成基本有序子数组)
    public void ShellInsert(int[] arr){
        int length=arr.length;
        int j;
        for (int gap = length/2; gap>0 ; gap=gap/2) {
            for (int i = gap; i <length ; i++) {
                 int temp=arr[i];
                for (j = i; (j>=gap)&&(temp<arr[j-gap]) ; j=j-gap) {
                    arr[j]=arr[j-gap];
                }
                arr[j]=temp;
            }
        }
    }
    //冒泡排序,是稳定排序
    //冒泡排序的原理
    public void bubbleSort(int[] arr){
        boolean change=true;
        for (int i = 0; i <arr.length&&change ;++i) {
            change=false;//若此次排序过程中没有逆序的,则changefalse,那么排序完成
            for (int j = 1; j <arr.length-i ; ++j) {
                if (arr[j-1]>arr[j]){
                    int temp=arr[j-1];
                    arr[j-1]=arr[j];
                    arr[j]=temp;
                    change=true;//若此次排序有逆序,那么changetrue
                }
            }
        }
    }
    //快速排序,主要思想是,选取一个基准(pivot)为枢轴,比它大的放在右边,比它小的放在
    //左边;最后形成两个子数组,再对子数组进行递归排序。
    public void quickSort(int[] arr,int start,int end){
        if (start<end){
            int piont=partition(arr,start,end);
            quickSort(arr,start,piont-1);
            quickSort(arr,piont+1,end);
        }
    }
    public static int partition(int[] arr,int start,int end){
        int temp=median3(arr,start,end);
        while (start<end){
            while (temp<=arr[end]&&start<end){
               end--;
            }
            if (start<end){
                arr[start]=arr[end];
                start++;
            }
            while (temp>=arr[start]&&start<end){
                start++;
            }
            if (start<end){
                arr[end]=arr[start];
                end--;
            }

        }
        arr[start]=temp;
        return start;

    }
    public static int median3(int[] arr,int low,int high){
        int mid=(low+high)/2;
        if (arr[mid]>arr[high])
            swap(mid,high,arr);
        if (arr[low]>arr[high])
            swap(low,high,arr);
        if (arr[low]<arr[mid])
            swap(low,mid,arr);
        return arr[low];
    }
    public static void swap(int i,int j,int[] arr){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
    //简单选择排序
    public void selectSort(int[] arr){
        for (int i = 0; i <arr.length ; i++) {
            int k=i;
            for (int j = i+1; j <arr.length; j++) {
                if (arr[k] > arr[j]) k = j;
            }
                if (k!=i){
                    swap(i,k,arr);
                }
        }
    }
// 传统的选择排序每次只确定最小值,根据改进冒泡算法的经验,
// 我们可以对排序算法进行如下改进:每趟排序确定两个最值——最大值与最小值,
// 这样就可以将排序趟数缩减一半。
    public void selectSortPlus(int[] arr){
        for (int i = 0; i <arr.length/2; i++) {
            int kmin=i;
            int kmax=i;
            for (int j = i+1; j <=arr.length-i-1; j++) {
                if (arr[kmin]>arr[j]) kmin=j;
                if (arr[kmax]<arr[j]) kmax=j;
            }
            //当在本次比较中,最大值在最前面且最小值在最后面,那么只能交换依次,不然会重复
            if (kmax==i&&kmin==arr.length-i-1){
                swap(i,kmin,arr);
            }
            if (kmin!=i){
                swap(i,kmin,arr);
            //原来的第一个元素已经与下标为minPoint的元素交换了位置
            //如果之前maxPoint指向的是第一个元素,那么需要将maxPoint重新指向array[kmin]
            //因为现在array[kmin]存放的才是之前第一个元素中的数据
                if (kmax==i)
                    kmax=kmin;
            }
            if (kmax!=arr.length-i-1){
                swap(arr.length-i-1,kmax,arr);
            }
        }
    }
    //堆排序,首先把数组转换成堆,然后再删除最大值,并依倒序存放在数组的后面
    //最后返回的数组,就是按递增排的序
    public void heapSort(int[] arr){
       for(int i=arr.length/2-1;i>=0;i--)//build heap
           percDown(arr,i,arr.length);
       for (int i=arr.length-1;i>0;i--){//通过deleteMin来排序,通过下虑的算法实现。
            int temp=arr[0];
           arr[0]=arr[i];
           arr[i]=temp;
           percDown(arr,0,i);
       }
    }
    public static void percDown(int[] arr,int i,int arrLength){
        int child;
        int temp=arr[i];
        for (;i*2+1<arrLength;i=child){
            child=i*2+1;
            if (child!=arrLength-1&&arr[child]>arr[child+1]){
                child++;
            }
            if (arr[child]<temp){
                arr[i]=arr[child];
            }
            else
                break;
        }
        arr[i]=temp;
    }
    //归并排序,mergesort,主要思想是,通过分治思想,把复制问题简化
    //把一个数组,看成左右两个数组,然后这两个数组继续递归,知道每个数组中只剩一个数
    //而,在进行左右数组合并成一个数组的时候,这个两个数组已经各自有序,通过依次比较,
    //小的值,存在临时数组中,并且下标后移;大的值保持不变,然后再次比较知道有一个数组已经
    //全部存在临时数组中,此时把剩下的那个数组中的值全部添加在临时数组中。最后,
    // 再把临时数组中的数,按顺序返存在原数组中。
    public void mergeSort(int[] arr){
        int[] temp=new int[arr.length];
        mergesortRec(arr,temp,0,arr.length-1);
    }
    public static void mergesortRec(int[] arr,int [] tempArray, int start,int end){
        if (start<end) {//这个就是递归的基准,也就是结束条件
            int mid = (start + end + 1) / 2;
            mergesortRec(arr, tempArray, start, mid - 1);
            mergesortRec(arr, tempArray, mid, end);
            merge(arr, tempArray, start, mid, end);
        }
    }
    public static void merge(int[] arr1,int[] tempArray,int left,int right,int end){
        int leftend=right-1;
        int tmppos=left;
        int numElements=end-left+1;
        while (left<=leftend&&right<=end){
            if(arr1[left]<arr1[right]){
                tempArray[tmppos++]=arr1[left++];
            }
            else {
                tempArray[tmppos++]=arr1[right++];
            }
        }
        while (left<=leftend){
            tempArray[tmppos++]=arr1[left++];
        }
        while (right<=end){
            tempArray[tmppos++]=arr1[right++];
        }
        for (int i=0;i<numElements;i++, end--){
            arr1[end]=tempArray[end];
        }
    }
//基数排序,使用数组
public void redixSort1(int[] arr,int d) {
    int l = arr.length;
    int k = 0;//用于数组中的下标
    int n = 1;//用于各个位数取余
    int[] count = new int[l];
    int[][] buckets = new int[10][l];
    int idx=1;
    while (idx <= d) {
        //把数组中的数,存到桶中
        for (int num : arr) {
            int bit = (num / n) % 10;
            buckets[bit][count[bit]] = num;
            count[bit]++;
        }
        //进行桶排序
        for (int i = 0; i < l; i++) {//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
            if (count[i] != 0) {//判断该位是否有数,有则反存回数组
                for (int j = 0; j < count[i]; j++) {
                    arr[k] = buckets[i][j];
                    k++;
                }
            }
            count[i] = 0;//将桶里计数器置0,用于下一次位排序
        }
        n *= 10;
        idx++;
        k = 0;//k0,用于下一轮保存位排序结果
    }
}
public void redixSort2(String[] arr,int stringlen){
    final int BUCKETS=256;
    ArrayList<String>[] buckets=new ArrayList[BUCKETS];
    for (int i=0;i<BUCKETS;i++)
        buckets[i]=new ArrayList<>();
    //int len=0;
    for (int i=stringlen-1;i>=0;i--) {
        for (String s:arr) {
           // int len=s.length()-1;
            buckets[s.charAt(i)].add(s);
        }
        int idx=0;
        for (ArrayList<String> thisbucket:buckets){
            for (String s:thisbucket) {
                arr[idx++]=s;
            }
            thisbucket.clear();
        }
    }
}
}

总结

各种排序的稳定性,时间复杂度和空间复杂度总结:

 我们比较时间复杂度函数的情况:

                             时间复杂度函数O(n)的增长情况

所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。

时间复杂度来说:

(1)平方阶(O(n2))排序   各类简单排序:直接插入、直接选择和冒泡排序;  (2)线性对数阶(O(nlog2n))排序   快速排序堆排序归并排序  (3)O(n1+§))排序,§是介于0和1之间的常数。

       希尔排序 (4)线性阶(O(n))排序   基数排序,此外还有桶、箱排序。

说明:

当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至On);

而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为On2);

原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。

 

稳定性:

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。       稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序

 

选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。

设待排序元素的个数为n.

1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

   快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;        堆排序 :  如果内存空间允许且要求稳定性的,

       归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2)  当n较大,内存空间允许,且要求稳定性 =》归并排序

3)当n较小,可采用直接插入或直接选择排序。

    直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

    直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

5)一般不使用或不直接使用传统的冒泡排序。

6)基数排序 它是一种稳定的排序算法,但有一定的局限性:   1、关键字可分解。   2、记录的关键字位数较少,如果密集更好   3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值