首页 > 代码库 > 排序 -- 思路简析(一)

排序 -- 思路简析(一)

简介

本篇文章总结一下最近学习的排序算法,提炼出其思想及不同之处。有归并排序,快速排序,堆排序以及冒泡排序

归并排序(Merging Sort)

  • 归并是指将两个或两个以上的有序表组合成一个新的有序表。
  • 归并排序是指把无序的的待排序序列分解成若干个有序子序列,并把有序子序列合并为整体有序序列的过程。
  • 长度为1的序列是有序的。

采用两两分解和归并的策略简单易行,这样的归并排序称为2-路归并排序。

归并排序的实现


    public static void sort(int[] arr) {
        if (arr == null || arr.length == 0)
            return;

        int len = arr.length;
        int[] temp = new int[len];
        mergeSort(arr, 0, len - 1, temp);
    }

    public static void mergeSort(int[] arr, int first, int last, int[] temp) {

        // 条件
        if (first < last) {
            int mid = (first + last) / 2;
            mergeSort(arr, first, mid, temp);
            mergeSort(arr, mid + 1, last, temp);
            mergeArray(arr, first, mid, last, temp);
        }
    }

    public static void mergeArray(int[] arr, int first, int mid, int last, int[] temp) {

        int i = first, j = mid + 1;
        int index = first;

        while (i <= mid && j <= last) {
            // 排序条件
            if (arr[i] > arr[j])
                temp[index++] = arr[i++];
            else
                temp[index++] = arr[j++];
        }

        while (i <= mid)
            temp[index++] = arr[i++];

        while (j <= last)
            temp[index++] = arr[j++];

        for (int k = first; k <= last; k++) {
            arr[k] = temp[k];
        }
    }

另一种写法:

// 将相邻有序的a[i..m]和a[m+1..n] 归并到b[i..n]  包含 n 
void Merge(int a[], int i,int m, int n, int b[]){
    int k,j;
    for(k = i, j = m+1; k <= m && j <= n;k++){
        if(a[i] < a[j]){
            b[k] = a[i++];
        }else{
            b[k] = a[j++];
        }
    }

    while(i<=m) b[k++] = a[i++];
    while(j<=n) b[k++] = a[j++];
}

// i:递归的层次(第一层为0)-- 用于指示在MSort当前调用层次中a,b数组的具体作用:
// 当i为奇数时,则从a[s..t]归并至b[s..t]
// 否则从b[s..t]归并至a[s..t]
void MSort(int a[], int b[], int i,int s,int t){
    int m;
    if( s == t ){
        if( 1 == i%2 ) b[s] = a[s];
    }else{
        m = (s+t)/2;
        MSort(a,b,i+1,s,m);
        MSort(a,b,i+1,m+1,t);

        if( 1 == i%2 ) Merge(a,s,m,t,b);
        else Merge(b,s,m,t,a);
    }
}

void MergeSort(int a[],int len){
    int b[len];
    MSort(a,b,0,0,len-1);
    free(b); // 释放资源
}

简单分析:
整个归并排序过程需要进行?logn?<script type="math/tex" id="MathJax-Element-1">\lceil log n \rceil</script>层的递归分解和归并,由此得归并排序的算法时间复杂度为O(n*logn)

归并排序是稳定排序

快速排序(Quick Sort)

最简单的交换排序是冒泡排序,而快速排序是对冒泡排序的改进。

首先从待排序列中选定一个关键字,称为枢轴,通过关键字与枢轴的比较将待排序的序列划分为位于枢轴前后的两个子序列,其中枢轴之前的子序列的所有关键字都不大于枢轴,枢轴之后的子序列都不小于枢轴,此时枢轴已经到位,再按同样的方法对这两个子序列分别递归进行快速排序,最终使得整个序列有序

快速排序的一次划分具体过程:

  • 将位标low指向待排记录的第一个记录(同时是枢纽记录),high指向最后一个记录,并将枢轴记录复制至指向的0号闲置单元。
  • 首先将high所指位置向前移动,直至找到第一个比枢轴记录关键字小的记录并复制至pivotkey所指位置。
  • 然后将low向后移动,找到第一个比枢轴记录关键字大的记录并将其复制至high所指位置。
  • 直至low与high相等时,将枢轴记录复制至low所指的位置

    private static void qSort(int[] arr) {
        if (arr == null || arr.length == 0)
            return;

        qSort(arr, 0, arr.length - 1);
    }

    public static void qSort(int[] arr, int first, int last) {
        // 长度大于一
        if (first < last) {
            int pKey = partition(arr, first, last);
            // 记得pKey位置已经不用再参与了,所以pKey - 1 和 pKey + 1
            qSort(arr, first, pKey - 1);
            qSort(arr, pKey + 1, last);
            System.out.println("key : " + pKey);
        } else {
            System.out.println("first > last");
        }
    }

    public static int partition(int[] arr, int first, int last) {

        int key = arr[first];
        while (first < last) {
            // 注意这里的>= 和 <=
            while (first < last && arr[last] >= key)
                last--;

            arr[first] = arr[last];

            while (first < last && arr[first] <= key)
                first++;

            arr[last] = arr[first];
        }
        arr[first] = key;

        return first;
    }

为避免枢轴关键字为最大或者最小的情况,可采用“三者取中”的方法,即以a[s]、a[t]、a[(s+t)/2]三者中关键字的大小居中的记录为枢轴,并与a[s]互换

堆排序之堆的基本操作

堆是一类完全二叉树,若堆中所有非叶子结点均不大于其左右子节点,则称为小顶堆;若堆中所有非叶子结点均不小于其左右子节点,则称为大顶堆

堆的结构类型


typedef struct{
    int *rcd; // 堆基址,0号但愿不可用 
    int n; // 堆长度
    int size; // 堆容器 
    int tag; // 小顶堆和大顶堆的标志
    int (*prior)(int a,int b); // 函数变量,用于关键字优先级比较  
} Heap;

int greatPrior(int x,int y) { return x>=y; }
int lessPrior(int x,int y) { return x<=y; }
  • 筛选
    • 堆的筛选操作,对堆中指定的pos结点为根的子树进行堆的特性的维护,其前提是pos结点的左右子树均满足堆特性
    • 过程:将pos结点与左右孩子优先者比较,若pos结点优先则结束;否则pos结点与左右孩子优先者交换位置,pos位标下移,重复以上步骤。

int SwapHeapElem(Heap &H,int i, int j){
    if( i < 0 || i > H.n || j < 0 || j > H.n ) return 0;

    int t;
    t = H.rcd[i];
    H.rcd[i] = H.rcd[j];
    H.rcd[j] = t;
    return 1;
}

// 堆的筛选操作,对堆中指定的pos结点为根的子树进行堆的特性的维护,其前提是pos结点的左右子树均满足堆特性 
void ShiftDown(Heap &H,int pos){
    int c, rc;
    while(pos<=H.n/2){
        c = pos*2;
        rc = pos*2+1;
        if(rc<=H.n && H.prior(H.rcd[rc],H.rcd[c])) c = rc; // 这里注意函数的比较元素位置

        if(H.prior(H.rcd[pos],H.rcd[c]))
            return ;
        SwapHeapElem(H,pos,c);
        pos = c;
    }
}
  • 删除堆顶结点
    • 过程:1、取出堆顶结点;2、将堆顶结点与堆位结点交换位置,并将堆长度减1;3、对堆顶结点进行筛选

int RemoveFirsHeap(Heap &H, int &e){
    if(H.n<=0) return 0;

    e = H.rcd[1];
    SwapHeapElem(H,1,H.n);
    if(H.n>1)  ShiftDown(H,1);
    return 1;
} 
  • 建堆操作

void MakeHeap(Heap &H,int a[],int n,int size,int tag,int (*prior)(int a,int b)){
    H.rcd = a;
    H.n = n;
    H.size = size;
    H.tag = tag;
    H.prior = prior;

    int i;
    // 叶子结点满足堆特性,故不需要筛选 
    for( i = H.n/2; i > 0; i -- ){
        ShiftDown(H,i);
    }
}

堆排序(Heap Sort)

  • 利用堆的特性进行排序,大顶堆可以进行升序排序,小顶堆可以进行降序排序。首先将待排序序列建成一个大顶堆,使得堆顶结点最大,将堆顶结点与堆尾交换位置,堆长度减1,调整剩余结点为堆,得到次大值结点。重复这一过程,即可得到一个升序序列
void HeapSort(int a[],int n){
    Heap H;
    MakeHeap(H,a,n,n+2,1,lessPrior);
    int e;
    for( int i = H.n; i > 0; i -- ){
        if(RemoveFirsHeap(H,e) )
            printf("%d ",e);
    }
}

冒泡排序(Bubble Sort)

  • 在第一趟冒泡过程中,首先将比较第一和第二个记录,如果为逆序,则交换位置。然后在比较第二和第三记录,依次操作,直至比较第n-1和第n个记录为止,此时第n个记录最大。第二趟冒泡将次大的记录交换至n-1位置,直至所有记录有序。

void BubbleSort(int a[], int len){
    int t;
    int i, j;
    // i表示已经排序的个数
    for( i = 0; i < len; i ++ ){
        // 开始冒泡到len-i,len-i之后表示已经达到最值
        for( j = 1; j < len-i; j ++ ){
            if( a[j-1] < a[j] ){
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
} 

改进:


void BubbleSort(int a[], int len){
    int t;
    int i, j, flag;
    flag = -1;
    // i表示已经排到序的个数,(及排到最尾端的个数) 
    // flag表示a[flag-1,flag..len-1]已经为有序序列,所以flag之后的为不用再排序序列 
    for( i = 0; i < len; i ++ ){
        if( flag != -1 ){
            i = len - flag; // 表示 已经排到序的个数
            flag = -1;
        }

        for( j = 1; j < len-i; j ++ ){
            if( a[j-1] > a[j] ){
                flag = j;
                t = a[j-1];
                a[j-1] = a[j];
                a[j] = t;
            }
        }
    }
} 

其他学习链接冒泡排序的三种实现

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    排序 -- 思路简析(一)