常用数组排序算法

1. 冒泡排序 Time Complexity:O(N2) Space Complexity:O(1)
关键点:
a. 外层循环i从0循环到N-2共N-1次(N-1个数排好了,N个数也就排好了);
b. 内层循环j每次从1到N-i-1,范围逐渐变小(后面排好的数逐渐变多);
c. 每次比较和交换都是相邻的数
void BubbleSort(int A[], int N)
{
    int tmp;
    for (i = 0; i < N - 1; ++i)
    {
        for (j = 1; j < N - i; ++j)
        {
            if (A[j] > A[j - 1])   //从大到小排序,把较小的交换到后面来
            {
                tmp = A[j - 1];
                A[j - 1] = A[j];
                A[j] = tmp;
            }
        }
    }
}
缺点:交换次数太多
是否稳定:是
其他:比较次数始终为仍为 N - 1 + N –2 + … + 1 = N(N-1)/2;
在数组元素已有序的情况下,不用移动元素;
数组逆序情况下,移动次数 3N(N - 1)/2;
小改进:
void BubbleSort(int A[], int N)
{
    int tmp;
    bool exchanged;
    for (i = 0; i < N - 1; ++i)
    {
        exchanged = false;
        for (j = 1; j < N - i; ++j)
        {
            if (A[j] > A[j - 1])   //从大到小排序,把较小的交换到后面来
            {
                exchanged = true;
                tmp = A[j - 1];
                A[j - 1] = A[j];
                A[j] = tmp;
            }
        }
        if(!exchanged)
            return;
    }
}
可以把数组有序情况下的比较次数减少为N-1,其他改进参考[1]
 
2. 插入排序 Time Complexity:O(N2) Space Complexity:O(1)
关键点:
a. i从1到N-1循环,假设前i个元素都是排序好(这里以从大到小为例)的
b. 将但前元素保存到tmp值,并从后向前(从小到大)和前面的元素比较,如果该元素值需要往前面移动,则将前面的元素向后挪动。
c. 这是一个比较和移动相互结合的过程,和先找到当前元素应该插入的位置,然后再统一移动元素有点不同。
//从大到小排序
void InsertSort(int A[], int N)
{
    int i, j, tmp;
    for (i = 1; i < N; ++i)
    {
        tmp = A[i];
        for (j = i - 1; j >= 0; --j)
        {
            if (tmp > A[j])
            {
                A[j + 1] = A[j];
            }
            else
                break;
        }
        A[j + 1] = tmp;
    }
}
是否稳定:是
当数组有序时,比较N-1次,移动2(N-1)次;
当数组逆序时,比较N(N-1)/2次,移动(N-1)(N+4)/2次
 3. 选择排序 Time Complexity:O(N2) Space Complexity:O(1)
(1)不稳定方法
关键点:
a. 进行N-1次选择,每次选择都从剩下的未排序数组中选出最大的数放到当前轮最前面;
b. 选择过程只是记录最值下标;
c. 当最值下标和当前轮元素下标不同时实施移动;
d. 移动仅仅是交换,那么该方法是不稳定的;
e. 选择算法的比较次数无论如何都不会改变,最好情况下移动次数为0;
//从大到小排序
void SelectSort(int A[], int N)
{
    int i, j, k;
    int tmp;
    for (i = 0; i < N - 1; ++i)
    {
        k = i;
        for (j = i + 1; j < N; ++j)
        {
            if (A[j] > A[k])
            {
                k = j;
            }
        }
        if (k != i)
        {
            tmp = A[k];
            A[k] = A[i];
            A[i] = tmp;
        }
    }
}
以上算法最坏情况下移动次数为 3(N-1)
如果移动是将最值插入到该轮元素前面,那么选择算法就是稳定的:
void SelectSortStable(int A[], int N)
{
    int i, j, k;
    int tmp; 
   for (i = 0; i < N - 1; ++i)
    {
        k = i;
        for (j = i + 1; j < N; ++j)
        {
            if (A[j] > A[k])
            {
                k = j;
            }
        }
        if (k != i)
        {
            tmp = A[k];
            for (j = k; j > i; --j)
            {
                A[j] = A[j - 1];
            }
            A[i] = tmp;
        }
    }
}
上述算法最坏情况下移动次数为 N+1 + N + N-1 + … + 3 = (N+4)(N-1)/2
4. 归并排序 Time Complexity:O(Nlog2N) Space Complexity:O(N)
关键点:
a. 使用递归过程,直到子数组只有一个元素时停止;
b. 使用N个空间的辅助数组;
c. 递归时有栈开销;
d. 归并排序是O(Nlog2N) 复杂度的稳定排序
//从大到小排序
void Merge(int *arr, int start, int mid, int end, int *tmparr)
{
    int i, j, k = 0;
    for (i = start, j = mid + 1; i <= mid && j <= end;)
    {
        if (arr[i] >= arr[j])
            tmparr[k++] = arr[i++];
        else
            tmparr[k++] = arr[j++];
    }
    for (;i <= mid;)
    {
        tmparr[k++] = arr[i++];
    }
    for (;j <= end;)
    {
        tmparr[k++] = arr[j++];
    }
    //将排序后的辅助数组拷贝到原始数组
    for (i = 0; i < (end - start + 1); ++i)
    {
        arr[start+i] = tmparr[i];
    }
}
void MergeSort(int * arr, int start, int end, int *tmparr)
{
    if (start < end)
    {
        int mid = (start + end)/2;
        MergeSort(arr, start, mid, tmparr);
        MergeSort(arr, mid + 1, end, tmparr);
        Merge(arr, start, mid, end, tmparr);
    }
}
调用方法:MergeSort(arr, 0, N - 1, tmparr); 
非递归的算法如下,SubListSize为合并单元的长度,依次从1,2,4,…2n 增长,直到超过数组的总长度位置。对于从小到大的每个SubListSize,对原数组依次分组处理,每组的长度都为2*SubListSize(末尾不足的除外),这样从短到长一步步合并,最后得到排序后的数组。其优点是没有函数调用栈的开销。 
void Mergesort( ElementType A[ ], int N )
{
ElementType *TmpArray;
int SubListSize, Part1Start, Part2Start, Part2End;
TmpArray = malloc( sizeof( ElementType ) * N );
for( SubListSize = 1; SubListSize < N; SubListSize *= 2 )
{
Part1Start = 0;
while( Part1Start + SubListSize < N - 1 )
{
Part2Start = Part1Start + SubListSize;
Part2End = min( N, Part2Start + SubListSize - 1 );
Merge( A, TmpArray, Part1Start, Part2Start, Part2End );
Part1Start = Part2End + 1;
}
}
free(TmpArray);
}
5. 堆排序 Time Complexity:O(Nlog2N) Space Complexity:O(1)
堆排序就是将数组中的元素逻辑上组织成完全二叉树的形式,排序算法核心在于堆的调整。创建堆时,是从后向前构造,即从下往上的根节点构造。调整时,是将对顶元素向下调整。
堆排序时,依次交换堆顶元素和数组末尾元素,然后缩小堆长度并调整堆。如果需要从大到小排序,则建立小顶堆,如果从小到大排序,则建立大顶堆。堆排序是不稳定排序。
下面是从大到小的堆排序算法,设数组有效元素从1开始
void HeapAdjust(int *arr, int r, int m)
{
    int rv = arr[r];
    int j;
    for (j = 2 * r; j <= m; j = 2 * j)
    {
        if (j < m && arr[j+1] < arr[j])
        {
            j++;
        }
        if (arr[j] < rv)
        {
            arr[r] = arr[j];
            r = j;
        }
        else
            break;
    }
    arr[r] = rv;
}
void HeapSort(int *arr, int N)
{
    int i, tmp;
    for (i = N/2; i > 0; --i)
    {
        HeapAdjust(arr, i, N);
    }
    for (i = N; i > 1; --i)
    {
        tmp = arr[1];
        arr[1] = arr[i];
        arr[i] = tmp;
        HeapAdjust(arr, 1, i - 1);
    }

下面是数组元素从0开始的写法:
void HeapAdjust(int *arr, int r, int len)
{
    int rv = arr[r];
    int j;
    for (j = 2 * r + 1; j < len; j = 2 * r + 1)
    {
        if (j < len - 1 && arr[j+1] < arr[j])
        {
            j++;
        }
        if (arr[j] < rv)
        {
            arr[r] = arr[j];
            r = j;
        }
        else
            break;
    }
    arr[r] = rv;

void HeapSort(int *arr, int N)
{
    int i, tmp;
    for (i = N/2 - 1; i >= 0; --i)
    {
        HeapAdjust(arr, i, N);
    }
    for (i = N - 1; i > 0; --i)
    {
        tmp = arr[0];
        arr[0] = arr[i];
        arr[i] = tmp;
        HeapAdjust(arr, 0, i);
    }
}
调用方式都是:HeapSort(arr, N); 
6. 快速排序
快速排序是一般情况下使用最多的排序方法,它的平均时间复杂度为O(Nlog2N),虽然在最坏情况下可能达到O(N2),快速排序空间复杂度为O(1),一般使用递归实现,快速排序是一种不稳定的排序算法。
下面是一种经典的实现,从大到小排序:
int Partition(int *arr, int low, int high)
{
    int pivot = arr[low];
    while(low < high)
    {
        while(low < high && arr[high] <= pivot) high--;
        arr[low] = arr[high];
        while(low < high && arr[low] >= pivot) low++;
        arr[high] = arr[low];
    }
    arr[low] = pivot;
    return low;
}
void Qsort(int *arr, int low, int high)
{
    if (low < high)
    {
        int mid = Partition(arr, low, high);
        Qsort(arr, low, mid - 1);
        Qsort(arr, mid + 1, high);
    }
}
一种考虑更全面的实现如下,三数中值分割算法,从小到大排序:
int Median3(int A[], int Left, int Right)
{
    int Center = (Left + Right)/2;
    if (A[Left] > A[Center])
        Swap(&A[Left], &A[Center]);
    if (A[Left] > A[Right])
        Swap(&A[Left], &A[Right]);
    if (A[Center] > A[Right])
        Swap(&A[Center], &A[Right]);  
    //A[Left] <= A[Center] <= A[Right]
    Swap(&A[Center], &A[Right - 1]); //Hide pivot
    return A[Right - 1]; //Return pivot
}
void Qsort3(int A[], int Left, int Right)
{
    int i, j;
    int Pivot;
    //这里用了一个阈值,当数组长度小于阈值时用简单插入排序,比较高效
    if (Left + Cutoff <= Right)
    {
        Pivot = Median3(A, Left, Right);
        i = Left; j = Right - 1;
        for ( ; ;)
        {
            while( A[++i] < Pivot ) {}
            while( A[--j] > Pivot ) {}
            if (i < j)
                Swap(&A[i], &[j]);
            else
                break;
        } 
        Swap(&A[i], &A[Right - 1]); //Restore pivot
        Qsort3(A, Left, i - 1);
        Qsort3(A, i + 1, Right);
    }
    else
        InsertSort(A, Left, Right - Left + 1);
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值