现在的位置: 首页 > 综合 > 正文

内排序算法小结

2019年09月30日 ⁄ 综合 ⁄ 共 7801字 ⁄ 字号 评论关闭

从以下几个方面来比较排序算法

1. 算法的时间和空间复杂度

2. 排序的稳定性

3. 算法结构的复杂度

4. 参加排序的数据规模


排序的稳定性:

稳定排序方法: 插入排序、冒泡排序、二路归并排序、基数排序是稳定排序算法;

不稳定排序方法: 选择排序、谢尔排序、快速排序、堆积排序是不稳定排序算法。


算法复杂度比较:

各种内排序算法的时间、空间复杂度
排序方法 平均时间 最坏情况 辅助空间
插入排序 O(n*n) O(n*n) O(1)
谢尔排序 O(n*log2n) O(n*log2n) O(1)
泡排序 O(n*n) O(n*n) O(1)
快速排序 O(n*log2n) O(n*n) O(log2n)
选择排序 O(n*n) O(n*n) O(1)
堆积排序 O(n*log2n) O(n*log2n) O(1)
归并排序 O(n*log2n) O(n*log2n) O(n)
基数排序 O(d*(n+r)) O(d*(n+r)) O(n+r)

有结论如下:

1. 平均情况下,谢尔排序、快速排序、堆积排序、归并排序都能达到较快的排序速度,快速排序最快,堆积排序空间消费最省。

2. 平均情况下,插入排序和泡排序的排序速度较慢,但当参加排序的序列开始就局部有序时,这两种排序算法就能够达到较快的排序速度。最后情况下速度为O(n), 比情况 1 中叙述的4中方法都好,辅助空间消费也少。所以当 n 较小或者序列开始就局部有序时,可选择这两种方法。

3. 基数排序方法消费的辅助空间较多,但其时间复杂度可简化为O(dn); 当元素的位数较少时,可继续简化为O(n), 这种情况下也能达到较快的排序速度,另外,归并排序需要的辅助空间也较多。

4. 从算法结构的简单性来看,插入排序、泡排序、选择排序比较简单和直接;而谢尔排序、快速排序、堆积排序以及归并排序都可以看做对某一种排序算法的进一步改进,相对而言,改进后的算法都比较复杂。

5. 从参加排序的数据序列的规模大小来看,n越小,采用简单排序方法就越合适;n越大,采用改进的排序方法越合适。这是因为n越小,n*n 与 log2n 的差距越小。


以下是在考虑内排序算法的优缺点后,在某些情况下比较适合的算法选择:

1. 当参加排序的数据规模较大,关键字元素分布比较随机,并且不要求排序稳定性时,宜采用快速排序法。

2. 当参加排序的数据规模较大,内存空间又允许,并且有排序稳定性要求,宜采用归并排序法。

3. 当参加排序的数据规模较大,元素分布可能出现升序或者逆序的情况,并且对排序的稳定性无要求时,宜采用插入排序方法。

4. 当参加排序的数据规模较小(如小于100),元素基本有序(升序),或者分布也比较随机,并且有排序稳定性要求时,宜采用插入排序方法。

5. 当参加排序的数据规模n较小,对排序稳定性又不要求时,宜采用选择排序。


插入排序、选择排序、归并排序都比较容易在链表上实现,当利用链表作为存储结构时,可以避免将大量时间花费在元素的位置移动上。


算法描述:

1. 插入排序:

/* 简单插入排序
 * |data|中存储数组元素
 * |len|是数组大小
 * 1. 
 *  需要 |len - 1| 趟排序,每一趟排序将未排序的序列中第一个元素插入到已排序的序列中
 * 2.
 *  每一趟排序寻找插入位置的比较次数不定,但是至少需要一次
 * 3.
 *  最好的情况,原序列已是升序排列,则需要|n-1|趟排序,每一趟排序进行一次元素间的比较,时间复杂度O(n)
 * 4.
 *  最坏的情况,原序列是降序排列,同样需要|n-1|趟排序,第 i(i>=1 && i<len)趟排序需进行 i 次元素间比较,时间复杂度O(n*n)
 * 5.
 *  平均时间复杂度O(n*n)
 * 6.
 *  插入排序是稳定性排序
 * */
void simple_insert(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    int temp, i, j;
    for(i = 1; i < len; ++i) {
        temp = data[i];
        j = i - 1;
        while(j >= 0 && temp < data[j]) {
            data[j+1] = data[j--];
        }
        data[j+1] = temp;
    }
}

/* 折半插入排序
 * 与简单插入排序相比
 *  1. 折半插入排序减少了寻找插入位置时的元素之间的比较次数
 *  2. 并未减少元素的移动次数和排序所需的趟数
 *  3. 时间复杂度降为O(n*log2n)
 */
void binary_insert(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    int temp, i, j, low, mid, high;
    for(i = 1; i < len; ++i) {
        temp = data[i];
        low = 0;
        high = i - 1;
        while(low <= high) {
            mid = (low + high)/2;
            if(temp < data[mid])
                high = mid - 1;
            else
                low = mid + 1;
        }

        for(j = i; j > low; --j)
            data[j] = data[j-1];

        data[low] = temp;
    }
}

2. 谢尔排序

/* 谢尔排序,又称缩小增量排序法,是对直接插入排序法的一种改进
 * 1.
 *  时间复杂度在O(n*log2n) 和 O(n*n)之间
 * 2.
 *  谢尔排序是一种不稳定排序,不适合在链表结构上实现的排序算法
 * */
void shellSort(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    unsigned int gap = len;
    int i,j,temp;
    while(gap > 0) {
        gap >>= 1;
        for(i = gap; i < len; ++i) {
            j = i - gap;
            temp = data[i];
            while(j >= 0 && temp < data[j]) {
                data[j+gap] = data[j];
                j -= gap;
            }

            data[j+gap] = temp;
        }
    }
}

3. 泡排序

/* 冒泡排序
 * 1.
 *  至少需要进行一趟排序,此时是原始序列已经升序排列的情况,需进行|len - 1|次元素间的比较,时间复杂度O(n)
 * 2.
 *  最多进行|len - 1|趟排序(最小值处于原始序列最后),第 i(i>=1 && i<=len-1) 趟排序需要进行|len - i|次元素间的比较, 时间复杂度O(n*n)
 * 3.
 *  平均时间复杂度是O(n*n)
 * 4.
 *  冒泡排序是稳定性排序,适用于参加排序的数据量较小并且序列的初始状态为基本有序的情况
 * */
void bubbleSort(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    int flag = 1;
    int i, j, temp;
    i = len - 1;
    while(i > 0 && flag == 1) {
        flag = 0;
        for(j = 0; j < i; ++j) {
            if(data[j] > data[j+1]) {
                temp = data[j];
                data[j] = data[j+1];
                data[j+1] = temp;

                flag = 1;
            }
        }

        --i;
    }
}

4. 快速排序

/* randomInRange函数:
 * 产生[start, end] 之间的随机数,包括start, end两个数
 * */
int randomInRange(int start, int end)
{
    srand(time(NULL));

    return start + rand()%(end-start+1);
}

/* 
 * 交换elem1, elem2的值
 */
void swapElem(int *elem1, int *elem2)
{
    int temp = *elem1;
    *elem1 = *elem2;
    *elem2 = temp;
}

/* partition函数:
 * 在data序列的[start, end]之间随机选取一个元素值作为分界元素vp,
 * 该分界元素vp左边的值都小于等于vp, 右边的值都大于等于vp,
 * 函数返回该分界元素在序列中的索引位置
 * */
int partition(int *data, int len, int start, int end)
{
    if(data == NULL || len < 1 || start < 0 || end >= len) {
        printf("invalid parameters.");
        exit(1);
    }

    int index = randomInRange(start, end);
    swapElem(&data[index], &data[end]);
    int small = start - 1;

    for(index = start; index < end; ++index) {
        if(data[index] < data[end]) {
            ++small;
            if(small != index)
                swapElem(&data[index], &data[small]);
        }
    }
    swapElem(&data[++small], &data[end]);

    return small;
}

/* 快速排序递归算法:
 * 1. 
 *  时间复杂度是O(n*log2n)
 * 2. 
 *  快速排序是不稳定排序,不适合在链表结构上实现的排序算法
 * 3.
 *  每次排序时如果各个部分的分界元素恰好就是最大值元素时,快速排序就会倒退为"慢速排序", 此时并未把序列分界为两个子序列
 * */
void quickSort(int *data, int len, int start, int end)
{
    if(start == end)
        return;

    int index = partition(data, len, start, end);
    if(index > start)
        quickSort(data, len, start, index - 1);
    if(index < end)
        quickSort(data, len, index + 1, end);
}

5. 选择排序

/* 选择排序
 * 1. 
 *  总共需要|len - 1|趟排序,且第 i(i>=1 && i<=len-1) 趟排序需要进行 |len - i|次元素间的比较
 * 2.
 *  时间复杂度与初始序列的状态无关,都是O(n*n)
 * 3.
 *  选择排序是不稳定排序,例如(49, 38, 65, 97, 49, 13, 27)
 * */
void selectSort(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    int i, j, minIndex;
    for(i = 0; i < len - 1; ++i) {
        minIndex = i;
        j = i + 1;
        while(j < len) {
            if(data[j] < data[minIndex])
                minIndex = j;
            ++j;
        }

        if(minIndex != i) {
            int temp = data[i];
            data[i] = data[minIndex];
            data[minIndex] = temp;
        }
    }
}

6. 堆积排序

/* adjust函数:
 * |len| 数组序列的大小
 * |root| 数组中某一元素的索引
 * 以结点root为根结点的子树不是堆积,但结点root的左右子树都是堆积,
 * adjust函数将把第root个结点作为根结点的子树调整为一个新的堆积
 * */
void adjust(int *data, int len, int root)
{
    int temp = data[root];
    int i = root * 2;

    while(i <= len) {
        if(i < len && data[i] < data[i + 1])
            ++i;

        if(temp >= data[i])
            break;

        data[i/2] = data[i];
        i *= 2;
    }

    data[i/2] = temp;
}

/* 堆排序:
 * |len| data序列中最后一个元素的索引号, 也是参加排序的元素个数,
 * 注意不是data序列的元素个数
 * 1. 
 *  data序列中第1个元素(data[0])不参加排序
 * 2. 
 *  总共执行|len - 1|趟排序,每一趟排序确定未排序序列中的最大值的位置,并执行一次adjust函数
 * 3.
 *  无论是最坏情况还是平均情况,堆积排序的时间复杂度都是O(n*log2n)
 * 4.
 *  堆积排序的空间复杂度是O(1), 适合于len较大的序列排序
 * 5.
 *  堆积排序不适合在链表上实现
 * 6.
 *  堆积排序是不稳定排序,例如(5, 4, 3, 2, 2, 2, 1)
 */
void heapSort(int *data, int len)
{
    if(data == NULL || len < 1)
        return;

    int i;
    for(i = len/2; i > 0; --i) {
        adjust(data, len, i);
    }

    int temp;
    for(i = len; i > 1; --i) {
        temp = data[1];
        data[1] = data[i];
        data[i] = temp;

        adjust(data, i-1, 1);
    }
}

7. 归并排序

/* merge函数:
 * 将source[start..mid]和source[mid+1..end]中的元素归并有序序列des[start..end]
 *
 * */
void merge(int *source, int *des, int start, int mid, int end)
{
    int i = start;
    int j = mid + 1;
    int k = start;

    while(i <= mid && j <= end) {
       // if(source[i] <= source[j])
       //     des[k++] = source[i++];
       // else
       //     des[k++] = source[j++];
        des[k++] = source[i] <= source[j] ? source[i++] : source[j++];
    }

    while(i <= mid)
        des[k++] = source[i++];
    while(j <= end)
        des[k++] = source[j++];
}

/* mpass函数:
 * 将参加排序的序列分成若干个长度为t的,且各自按值有序的子序列,
 * 然后多次调用归并子算法merge将所有两两相邻成对的子序列合并成
 * 若干个长度为2t的,且各自按值有序的子序列。
 * 
 * 若最后一趟排序时,剩下的元素不足2t个元素:
 * 1.
 *  若剩余元素个数大于t,则再调用一次merge算法将剩余的两个不等长子序列合并为一个子序列
 * 2.
 *  若剩余元素个数小于等于t,则直接将所有剩余元素依次复制到前一个子序列的后面
 * */
void mpass(int *source, int *des, int len, int t)
{
    int i = 0;
    if(len - i >= 2*t) {
        merge(source, des, i, i + t - 1, i + 2 * t - 1);
        i += 2 * t;
    }

    if(len - i > t)
        merge(source, des, i, i + t - 1, len - 1);
    else {
        for(; i < len; ++i)
            des[i] = source[i]; 
    }
}

/* mergeSort排序:
 * |flag|,若flag = -1,则表示输入参数错误;
 * 若flag = 1, 则表示排好序的元素存放于data序列中
 * 若flag = 0,则表示排好序的元素存放于des序列中
 * 1. 
 *  总共会进行log2n趟排序,每趟排序调用mpass函数,mpass的时间复杂度为O(n),二路归并排序的时间复杂度为O(nlog2n)
 * 2.
 *  排序过程中需要同原始序列同等大小的辅助空间,空间复杂度为O(n)
 * 3.
 *  二路归并排序是稳定性排序
 * 4.
 *  二路归并排序的时间复杂度与序列的初始状态无关
 */
int mergeSort(int *data, int *des, int len)
{
    int flag = -1;
    if(data == NULL || len < 1)
        return flag;

    int t = 1;
    flag = 1;
    while(t < len) {
        mpass(data, des, len, t);
        flag = 0;

        t *= 2;
        if(t < len) {
            mpass(des, data, len, t);
            t *= 2;
            flag = 1;
        }
    }

    return flag;
}

8. 基数排序

/*
 * 定义的存储数据元素的结点
 */
typedef struct Node {
    int data;
    struct Node *next;
}QLink;

/* maxDig函数:
 * 找出data序列所有元素中最长的位数maxd并返回
 * */
int maxDig(int *data, int len)
{
    int maxd = 0;
    int maxCurD = 1;

    int base = 10;
    for(int i = 0; i < len; ++i) {
        maxCurD = 1;
        base = 10;
        while(data[i] >= base) {
            ++maxCurD;
            if(maxd < maxCurD)
                maxd = maxCurD;
            base *= 10;
        }
    }

    return maxd;
}

/*
 * 将data序列中所有元素均存储到链表中,并返回链表头结点head
 */
QLink* transferArrToQLink(int *data, int len)
{
    if(data == NULL || len < 1)
        return NULL;

    QLink *head = NULL;
    QLink *rear = NULL;

    for(int i = 0; i < len; ++i) {
        QLink *cur = (QLink *)malloc(sizeof(QLink));
        cur->data = data[i];
        cur->next = NULL;
        if(head == NULL)
            head = cur;
        else
            rear->next = cur;
        rear = cur;
    }

    return head;
}

/*
 * 释放链表中所有元素结点
 */
void freeQLink(QLink *phead)
{
    QLink *toFree = NULL;
    while(phead != NULL) {
        toFree = phead;
        phead = phead->next;
        free(toFree);
    }
}

/*
 * 获取num 中从右边数第index位的数字,index从0 开始计数
 *
 */
int getCurPlaceV(int num, int index)
{
    int placeValue;
    int i = 0;
    while(i <= index) {
        placeValue = num%10;
        num /= 10;
        ++i;
    }

    return placeValue;
}

/*
 * |data| 数据元素序列,|len|数据元素的个数,|radix|是基数
 */
void radixSort(int *data, int len, int radix)
{
    int maxd = maxDig(data, len);
    printf("maxd = %d\n", maxd);
    QLink *phead = transferArrToQLink(data, len);

    QLink *Front[radix], *End[radix];
    QLink *q, *rear;
    int i, j, curplaceV;
    for(i = 0; i < maxd; ++i) {
        printf("the %d times sort\n", i);
        for(j = 0; j < radix; ++j)
            Front[j] = End[j] = NULL;

        q = phead;
        while(q != NULL) {
            curplaceV = getCurPlaceV(q->data, i);
            if(Front[curplaceV] == NULL)
                Front[curplaceV] = q;
            else
                End[curplaceV]->next = q;
            End[curplaceV] = q;

            q = q->next;
        }

        j = 0;
        while(Front[j] == NULL)
            ++j;

        phead = Front[j];
        rear = End[j];
        ++j;
        for(; j < radix; ++j) {
            if(Front[j] != NULL) {
                rear->next = Front[j];
                rear = End[j];
            }
        }
        rear->next = NULL;
    }

    q = phead;
    i = 0;
    while(q != NULL) {
        data[i++] = q->data;
        q = q->next;
    }

    freeQLink(phead);
    phead = NULL;
}

抱歉!评论已关闭.