美文网首页
《算法精解:C语言描述》笔记(排序)

《算法精解:C语言描述》笔记(排序)

作者: oldSix_Zhu | 来源:发表于2017-10-29 15:41 被阅读33次
第12章 排序和搜索

通常有两种排序方法:升序排列和降序排列
排序算法分为两大类:比较排序和线性时间排序
比较排序依赖于比较和交换,线性时间排序依赖于数据集合中的某些特征。

1、插入排序
2、快速排序
3、归并排序
4、计数排序
5、基数排序

1、插入排序

每次从无序数据集中取出一个元素,扫描已排好序的数据集,并将它插入有序集合合适的位置上。
但并不需要额外的存储空间。
处理大型数据集时并不高效。在决定将元素插入之前,需要将被插入元素和有序数据集中其他元素进行比较,会随着数据集的增大而增加额外的开销。
优点是只需要对有序数据集最多进行一次遍历,而不需要完整地运行算法。在增量排序中非常高效。
复杂度 O(n²)

.c文件:

#include "issort.h"
#include <stdlib.h>
#include <string.h>

/*
 *插入排序
 *参数:data:待排序的数组
 *      size:数组元素的个数
 *      esize:每个元素的大小
 *      compare:函数指针,比较函数大小的函数
 *返回值:成功返回0,否则-1,data包含已排序的元素
 */

int issort(void *data,int size,int esize,int (*compare)(const void *key1,const void *key2) )
{
    char *a = data;
    void *key;
    int i;
    if ((key = (char *)malloc(esize)) == NULL)
    {
        return -1;
    }
    for (int j=1; j<size; j++)
    {
        memcpy(key, &a[j * esize], esize);
        i = j-1;
        while (i>=0 && compare(&a[i * esize],key) > 0)
        {
            memcpy(&a[(i+1) * esize], &a[i * esize], esize);
            i--;
        }
        memcpy(&a[(i+1) * esize], key, esize);
    }
    free(key);
    return 0;
}

自定义比较函数,在其他排序中也能用到

int compare(const void *key1,const void *key2){
    if (*(int *)key1 > *(int *)key2) {
        return 1;
    }
    else if (*(int *)key1 == *(int *)key2){
        return 0;
    }
    else{
        return -1;
    }
}

调用

#include <stdio.h>
#include "issort.h"

int main(int argc, const char * argv[]) {
    int a[10]={0,2,1,3,5,7,9,8,6,4};
    int result = issort(a, 10, sizeof(int), compare);
    for (int i=0; i<10; i++)
    {
        printf("%d\n",a[i]);
    }
    return 0;
}

2、快速排序

不断地将无序元素集递归分割,直到所有的分区都只包含单个元素。
一种分治排序算法,比较排序的一种。也不需要额外的存储空间。
处理中到大型数据集的一个比较好的选择,解决一般问题的最佳算法
复杂度最坏为O(n²) 平均为O(nlgn)

分:设定一个分割值将数据分为两部分,这点很关键,影响到性能。
治:分别在两部分用递归继续调用
合:对分割部分排序直至完成

.c文件:

#include "qksort.h"
#include <stdlib.h>
#include <string.h>
#include "issort.h"

//分割数据
int partition(void *data,int esize,int i,int k,int(*compare)(const void *key1,const void *key2))
{
    char *a = data;
    void *pval,*temp;
    int r[3];
    //初始化
    if ((pval = malloc(esize)) == NULL) {
        return -1;
    }
    if ((temp = malloc(esize)) == NULL) {
        free(pval);
        return -1;
    }
    //中位数法找出分割值
    r[0] = (rand()%(k-i+1)) +i;
    r[1] = (rand()%(k-i+1)) +i;
    r[2] = (rand()%(k-i+1)) +i;
    issort(r, 3, sizeof(int), compare);
    memcpy(pval, &a[r[1]*esize], esize);
    //根据分割值分成两个区
    i--;
    k++;
    while (1) {
        //移动左边直到找到一个元素在错误的分区
        do {
            k--;
        } while (compare(&a[k * esize],pval) > 0);
        //移动右边直到找到一个元素在错误的分区
        do {
            i++;
        } while (compare(&a[i * esize],pval) < 0);
       
        if (i >= k) {
            //一旦i和k重合,那么左边的元素将小于等于它,右边的元素将大于等于它,停止分区
            break;
        }else{
            //交换元素
            memcpy(temp, &a[i * esize], esize);
            memcpy(&a[i * esize], &a[k * esize], esize);
            memcpy(&a[k * esize], temp, esize);
        }
    }
    free(pval);
    free(temp);
    //返回划分两个区的位置
    return k;
}

int qksort(void *data,int size,int esize,int i,int k,int(*compare)(const void *key1,const void *key2))
{
    int j;
    //可以进一步划分时停止递归
    while (i<k){
        //确定分区的元素
        if ((j = partition(data, esize, i, k, compare)) < 0) {
            return -1;
        }
        //递归左边的分区
        if (qksort(data, size, esize, i, j, compare) < 0) {
            return -1;
        }
        //迭代和排序右边的分区
        i = j+1;
    }
    return 0;
}

3、归并排序

将一个无序元素集分割成许多个只包含一个元素的集,然后不断地将这些小集合并,直到成为一个新的大有序数据集。
一种分治排序算法,比较排序的一种。但是需要额外的存储空间。
与快速排序最大的不同在于它的归并过程。将两个有序的数据集合并成一个有序的数据集,并且合并过程不能在无序数据集本身中进行,要有两倍于无序数据集的空间来运行算法。
对于海量数据处理还是很有价值的,因为能够按预期将数据集分开,成为更加可管理的数据,然后合并,并不需要一次存储所有的数据。
复杂度平均为O(nlgn)

//归并排序
#include "mgsort.h"

//将两个有序集合合并成一个有序集合
int merge(void *data,int esize,int i,int j,int k,int(*compare)(const void *key1,const void *key2))
{
    char *a = data,*m;
    int ipos,jpos,mpos;
    //初始化
    ipos = i;
    jpos = j+1;
    mpos = 0;
    if ((m = (char *)malloc(esize *((k-i)+1))) == NULL){
        return -1;
    }
    //当两个部分都有元素合并时继续
    while (ipos <= j || jpos <= k){
        if (ipos > j){
            //左部分没有元素要合并了
            while (jpos <= k) {
                memcpy(&m[mpos * esize], &a[jpos * esize], esize);
                jpos++;
                mpos++;
            }
            continue;
        }
        else if (jpos > k){
            //右部分没有元素要合并了
            while (ipos <= j) {
                memcpy(&m[mpos * esize], &a[ipos * esize], esize);
                ipos++;
                mpos++;
            }
            continue;
        }
        //追加下一个元素到合并元素
        if (compare(&a[ipos * esize],&a[jpos * esize]) < 0) {
            memcpy(&m[mpos * esize], &a[ipos * esize], esize);
            ipos++;
            mpos++;
        }
        else{
            memcpy(&m[mpos * esize], &a[jpos * esize], esize);
            jpos++;
            mpos++;
        }
    }
    memcpy(&a[i * esize], m, esize * ((k-i)+1));
    free(m);
    return 0;
}


int mgsort(void *data,int size,int esize,int i,int k,int(*compare)(const void *key1,const void *key2))
{
    int j;
    if (i < k) {
        //确定分割点
        j = (int)((i+k-1)/2);
        //递归排序
        if (mgsort(data, size, esize, i, j, compare) < 0) {
            return -1;
        }
        if (mgsort(data, size, esize, j+1, k, compare) < 0) {
            return -1;
        }
        //合并
        if (merge(data, esize, i, j, k, compare) < 0) {
            return -1;
        }
    }
    return 0;
}

4、计数排序

计算一个集合中元素出现的次数来确定集合如何排列。
一种线性排序。
只能用于整型或者那些可以用整型来表示的数据集合,还需要知道集合中最大整数的值用来分配空间。
优点是速度快,稳定。
时间复杂度为O(n+k) k为data中最大整数加1

//计数排序

#include "ctsort.h"
#include <stdlib.h>
#include <string.h>

int ctsort(int *data,int size,int k)
{
    int *counts,*temp;
    int i,j;
    if ((counts = (int *)malloc(k * sizeof(int))) == NULL) {
        return -1;
    }
    if ((temp = (int *)malloc(size * sizeof(int))) == NULL) {
        return -1;
    }
    for (i = 0; i < k; i++) {
        counts[i] = 0;
    }
    for (j = 0; j < size; j++) {
        counts[data[j]] = counts[data[j]] + 1;
    }
    for (i = 1; i < k; i++) {
        counts[i] = counts[i] + counts[i-1];
    }
    for (j = size-1; j>=0; j--) {
        temp[counts[data[j]] - 1] = data[j];
        counts[data[j]] = counts[data[j]] - 1;
    }
    memcpy(data, temp, size * sizeof(int));
    free(counts);
    free(temp);
    return 0;
}

5、基数排序

将数据按位分开,并从数据的最低有效位到最高有效位进行比较,依次排序,(从个位数排序,然后排序十位数...)
一种线性排序
不局限于整型,只要能把元素分割成整型即可。
时间复杂度为O(pn+pk) k为基数(10进制),p为位的个数(100就是3位)

//基数排序

#include "rxsort.h"
#include <stdlib.h>
#include <string.h>
#include <math.h>

int rxsort(int *data,int size,int p,int k)
{
    int *counts,*temp;
    int index,pval,i,j,n;
    if ((counts = (int *)malloc(k * sizeof(int))) == NULL) {
        return -1;
    }
    if ((temp = (int *)malloc(size * sizeof(int))) == NULL) {
        return -1;
    }
    
    for (n=0; n<p; n++) {
        for (i=0; i<k; i++) {
            counts[i] = 0;
        }
        pval = (int)pow((double)k, (double)n);
        for (j = 0; j < size; j++) {
            index = (int)(data[j] / pval) % k;
            counts[index]=counts[index] + 1;
        }
        for (i = 1; i < k; i++) {
            counts[i] = counts[i] + counts[i-1];
        }
        for (j = size-1; j >= 0; j--) {
            index = (int)(data[j] / pval) % k;
            temp[counts[index]-1] = data[j];
            counts[index] = counts[index] - 1;
        }
        memcpy(data, temp, size * sizeof(int));
    }
    free(counts);
    free(temp);
    return 0;
}
问与答

1、假设有一个全球性投资公司,需要将其客户按照名字排序,由于数据量巨大,不可能一次将所有数据读入内存,排序应该选择哪种算法?
归并排序。O(nlgn),还以可预见的方式合并有序分区和数据,让我们很容易管理数据。

2、在一个用户界面中维护一个有序元素的列表,相对较小,排序用哪种算法?
插入排序。 O(n)

3、在生物研究中,用1000w个80个字符宽的字符串来表示DNA的信息,排序用哪种算法?
基数排序。性能取决于基值的选择和空间的限制。可以把

4、假设有1w个c数据结构,包含一个航空公司的航班时刻表,排序用那种算法
快速排序。一般情况下最好的。

作者还在该章的末尾讲了下二分查找,更多的排序与查找算法就没有了,略失望,感觉少了点。

相关文章

网友评论

      本文标题:《算法精解:C语言描述》笔记(排序)

      本文链接:https://www.haomeiwen.com/subject/pdvnsxtx.html