非线性排序算法原理及其实现

为了更方便的实现排序算法,我们将元素交换算法单独写入到方法swap中,如下面代码:

//将c数组中a位置和b位置的元素交换一下
    public void swap(int [] c,int a,int b){
        int temp = c[a];
        c[a] = c[b];
        c[b] = temp;
    }

冒泡排序

原理

重复的走访要排序的数列,一次比较两个元素,如果他们的顺序错误,就把他们交换过来。走访数列的工作是重复地进行知道没有再需要交换,也就是说该数列已经排序完成。

代码实现

 public void BubbleSort(int a[]){
        for (int b = 0;b < a.length;b++){
            for (int i = 0;i < a.length-1-b;i++){
                if (a[i] > a[i+1]){
                    swap(a,i,i+1);//将大的交换到后面去
                }
            }
        }
    }

选择排序

特点

1、运行时间和输入无关,为了找出最小的元素而扫描一遍数组并不能为下一遍扫描提供什么信息
2、数据移动是最少的。

原理

首先在未排序的序列中找到最小(最大)元素,放到排序序列中的起始位置,然后,再从剩余未排序的元素中寻找最小(最大)元素,然后放到已排序的序列的末尾。直到所有元素均排序完毕。

代码实现

 public void SelectSort(int [] a){//选择排序,找到数组中最小的那个元素,
        // 其次,将它和数组的第一个元素交换位置,
        // 再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置,如此反复
        int N = a.length;

       for (int b = 0; b<N;b++){
           int min = b;
           for (int i = b+1;i<N;i++){
               if (a[min] > a[i]){
                   min = i;//找到未排序元素中的最小元素
               }

           }
           swap(a,b,min);
       }
    }

插入排序

特点

与选择排序不同的是,插入排序所需的时间取决于输入中元素的初始顺序,如果对一个很大的且其中的元素已经有序的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快的多。

原理

将一个元素插入到其它已经有序的数列中的适当位置。为了给插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位。

代码实现

实现一:

public void InsertSort(int []a,int lo,int hi){
        int B = a.length;
        for (int i = lo+1;i < hi+1;i++){
            for (int b = i;b > lo;b--){
                if (a[b] < a[b-1]){//将一个数字插入到其它已经有序的数组中的适当位置,前一部分是有序的,后一部分时无序的,从有序的那部分的最后一个元素开始往前比较,依次交换位置
                    swap(a,b,b-1);
                }
            }
        }
    }

算法改进

    public void InsertSortImproved(int []a){
        int B = a.length;
        for (int i = 1;i < a.length;i++){
            int temp = a[i];//提前保存要插入的值
            int b = 0;
            for (int c = i-1;c >=0;c--){
                if (temp < a[c]){//将较大的元素都往后移动(不是交换,这样访问数组的次数就可以减半)
                    a[c+1] = a[c];
                }else {
                    b = c+1;
                    break;
                }
                }
            a[b] = temp;
        }
    }

希尔排序

原理

这是一种基于插入排序的快速的排序算法。对于大规模乱序的数组插入排序很慢,因为元素只能一点一点的从数组的一端移动到另一端。如果主键最小的元素在数组的尽头,要将它挪动到正确的位置就需要N-1次移动。希尔排序为了加快速度简单的改进了插入排序,交换不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组进行排序。
希尔排序的思想是使数组中任意间隔为h的元素都是有序的,实现希尔排序的一种方法是对于每个h,用插入排序将h个子数组独立的排序,只需要在插入排序的代码中将移动元素的距离由1改为h即可。

原始数组为
这里写图片描述
当h = 5时,意味着整个数组被分为5组,[8,3],[9,5],[1,4],[7,6],[2,0]

这里写图片描述
对这5组进行插入排序,结果如下:
这里写图片描述
随后,将h设为2,对以上两组再进行插入排序,结果如下:
这里写图片描述
将h设为1,整个数组为一组,进行插入排序,结果如下:
这里写图片描述

特点

希尔排序更高效的原因是它权衡了子数组的规模和有序性。排序之初,各个子数组都很短,排序之后子数组都是部分有序的。这两种情况都很适合插入排序。

代码实现

public void shellSort(int []a){
        int B = a.length;
        int h = 1;
        while (h < B/3){
            h = 3*h+1;//设置步长h
        }
        while (h >=1){
            for (int i = h;i < a.length;i++){
                for (int b = i;b >= h;b-=h){//将插入排序移动的距离由1改为h
                    if (a[b] < a[b-h]){
                        swap(a,b,b-h);
                    }
                }
            }
            h = h/3;//排序完后,再次设置步长,重新进行插入排序
        }

    }

归并排序

原理

将两个有序数组归并成一个更大的有序数组。要将一个数组排序,可以先(递归的)将它分成两半分别排序,然后将结果归并起来,归并排序最吸引人的性质是能够保证将任意长度为N的数组排序所需时间和NlogN成正比,它主要的缺点是它所需要的额外空间和N成正比。

将两个有序数组归并为一个有序数组实现如下代码所示:

     //两步:
    //第一步,先复制到一个新的数组
    //第二步,合并两个有序数组的步骤
    public void merge(int []a,int lo,int mid,int hi){//将lo到mid,mid+1到hi进行合并
        int aux[] = new int[a.length];
        for (int k = lo;k <= hi;k++){//将a[lo..hi]复制到aux[lo...hi]
            aux[k] = a[k];
        }

        int i = lo;
        int j = mid +1;
        for (int k = lo;k<=hi;k++){//归并回到a[lo..hi],该部分实现可以参考合并两个有序数组程序
            if (i > mid){
                a[k]=aux[j++];
            }else if (j > hi){
                a[k]=aux[i++];
            }else if (aux[j] < aux[i]){
                a[k]=aux[j++];
            }else {
                a[k]=aux[i++];
            }
        }
    }

自顶向下归并

原理

这里写图片描述

代码实现

  //自顶向下归并排序,
    public void MergeUpToDownSort(int []a,int lo,int hi){
        if (hi <= lo){
            return;
        }
        int mid = lo + (hi - lo) /2;
        MergeUpToDownSort(a,lo,mid);//将左半边排序
        MergeUpToDownSort(a,mid+1,hi);//将右半边排序
        merge(a,lo,mid,hi);//归并结果
    }

自底向上归并排序

原理

先归并那些微型数组,然后再成对归并得到的子数组。首先是两两归并(将每个元素想象成一个大小为1的数组),然后是四四归并(将两个大小为2的数组归并成一个有4个元素的数组),然后八八归并,一直下去。
这里写图片描述

代码实现

    //自底向上归并
    public void MergeDownToUpSort(int []a){
        for (int i = 1;i<a.length;i*=2){
            for (int lo = 0;lo < a.length - i;lo+=i*2 ){
                int hi =Math.min(lo+i*2-1,a.length-1);
                int mid = lo +i-1;
                merge(a,lo,mid,hi);
            }

        }
    }

快速排序

原理

快速排序时一种分治的排序算法。它将一个数组分成两个子数组。将两部分独立的排序。快速排序和归并排序是互补的,归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序,而快速排序将数组排序的方式则是当两个子数组有序的时候,整个数组自然也就有序了。
该方法关键在于切分,整个过程使数组满足下面三个条件:
1、对于某个j,a[j]已经排定
2、a[lo]到a[j-1]中的所有元素都不大于a[j]
3、a[j+1]到a[hi]中的所有元素都不小于a[j]

要完成这个排序,首先实现切分方法。代码如下:

  //将数组切分为a[lo,i-1]  a[i] a[i+1,hi]
    public int QuickPartition(int a[],int lo,int hi){
        int i = lo;
        int j = hi+1;
        int partitionEle = a[lo];//第一个元素为切分元素
        while (true){
            while (a[++i] < partitionEle){
                if (i == hi){
                    break;
                }
            }
            while (a[--j] > partitionEle){
                if (j == lo){
                    break;
                }
            }
            if (i >= j){
                break;
            }
            swap(a,i,j);

        }
        swap(a,lo,j);//将游标放在正确的位置
          //a[lo...j-1]<= a[j]<=a[j+1..hi]达成
        return j;
    }

代码实现

public void QuickSort(int a[],int lo,int hi){
        if (hi <= lo){
            return;
        }
        int partition = QuickPartition(a,lo,hi);
        QuickSort(a,lo,partition-1);//对游标左半部分排序
        QuickSort(a,partition+1,hi);//对游标右半部分排序
    }

算法改进

切换到插入排序

和大多数递归排序算法一样,改进快速排序性能的一个简单办法基于以下两点:
1、对于小数组,快速排序比插入排序慢。
2、因为递归,快速排序的sort()方法在小数组中也会调用自己。
因此,在排序小数组的时候应该切换到插入排序。

代码实现


    public void InsertSort(int []a,int lo,int hi){
        int B = a.length;
        for (int i = lo+1;i < hi+1;i++){
            for (int b = i;b > lo;b--){
                if (a[b] < a[b-1]){//将一个数字插入到其它已经有序的数组中的适当位置,前一部分是有序的,后一部分时无序的,从有序的那部分的最后一个元素开始往前比较,依次交换位置
                    swap(a,b,b-1);
                }
            }
        }
    }
   public void QuickSortImprovedI(int a[],int lo,int hi){
        if (hi <= lo + 5){//当数组比较短的时候,切换为插入排序
            InsertSort(a,lo,hi);
            return;
        }
        int partition = QuickPartition(a,lo,hi);
        QuickSort(a,lo,partition-1);//对游标左半部分排序
        QuickSort(a,partition+1,hi);//对游标右半部分排序

    }

三向切分的快速排序

算法描述

它从左到右遍历数组一次,维护一个指针lt使a[lo..lt-1]中的元素都小于partition,一个指针gt使得a[gt…hi]中的元素都大于partition,一个指针i使得a[lt…i-1]中的元素都小于partition,a[i…gt]中的元素都还未确定。
对a[i]使用三向比较来直接处理以下情况:
1、a[i] < v,将a[lt]和a[i]交换,将lt和i加一
2、a[i] > v,将a[gt]和a[i]交换,将gt减一
3、a[i]等于v,将i加一。

这里写图片描述

代码实现
 //三项切分快速排序
    //维护一个指针lt使得a[lo...lt-1]中的元素都小于v
    //维护一个指针gt使得a[gt+1...hi]中的元素都大于v
    //维护一个指针i使得a[lt...i-1]的指针都小于v,a[i..gt]中的元素都还未确定
    public void QuickSortImprovedII(int a[],int lo,int hi){
        if (hi <= lo){
            return;
        }
        int v = a[lo];
        int i = lo+1;
        int lt = lo;
        int gt = hi;

        while (i <= gt){
            if (a[i] < v){
                swap(a,lt,i);//将小于v的交换到前面去
                lt++;
                i++;
            }
            else if (a[i] > v){
                swap(a,i,gt);//将大于v的交换到后面去
                gt--;
            }else {
                i++;
            }
        }
        QuickSortImprovedII(a,lo,lt-1);
        QuickSortImprovedII(a,gt+1,hi);
    }

这个排序的代码的切分能够将切分元素相等的元素归位,这样它们就不会被包含在递归调用处理的子数组中了。对于存在大量重复元素的数组,这种方法比标准的快速排序的效率高的多。

堆排序

在有序化的过程中我们会遇到两种情况。当某个节点的优先级上升(或者是在堆底加入一个新的元素时)我们需要由下至上恢复堆的顺序。当某个节点的优先级下降(例如将根节点替换为一个较小的元素时),我们需要由上至下恢复堆的顺序。

由下至上的堆有序化(上浮)

  public void swim(int []a,int k,int N){
        while (k > 1 && a[k] > a[k/2]){

            swap(a,k,k/2);
            k = k/2;
        }

    }

由上至下的堆有序化(下沉)

    public void sink(int a[],int k,int N){
        while (2*k <= N){
            int j = 2 * k;
            if (a[2 * k] < a[2*k+1] && j<N){
                j++;
            }
            if (a[k] < a[j]){
                swap(a,k,j);
                k = j;
            }else {
                break;
            }
        }
    }

堆排序算法实现

  //分为两步
    //1、先构造一个堆
    //2、排序,每次都把最大的那个元素交换到后面去
    public void HeapSort(int a[]){
        int N = a.length-1;
        for (int k = N/2;k >=1;k--){
            sink(a,k,N);
        }
        while (N >1){
            swap(a,1,N--);
            sink(a,1,N);
        }
    }

堆排需要注意的一个地方是,数组索引是从1开始排序的。

各种排序算法的性能特点

猜你喜欢

转载自blog.csdn.net/u011337574/article/details/80167397