7. 排序

● 各种排序算法

//开始部分

#include <stdio.h>

#include <string.h>

#include <ctype.h>

#include <stdlib.h>

#include <io.h>

#include <math.h>

#include <time.h>

 

#define OK 1

#define ERROR 0

#define TRUE 1

#define FALSE 0

 

#define MAX_LENGTH_INSERT_SORT 7 /* 用于快速排序时判断是否选用插入排序阙值*/

 

typedef int Status;

 

//定义一个待排序的顺序表结构

#define MAXSIZE 10000 /* 用于要排序的数组的个数最大值,可根据需要修改*/

 

typedef struct

{

    int r[MAXSIZE+1];    /* 用于存储要排序的数组,r[0]用作哨兵或临时变量*/

    int length;            /* 用于记录顺序表的长度*/

}SqList;

 

 

/* 交换顺序表L中数组r的下标为ij的值*/

void swap(SqList *L,int i,int j)

{

    int temp=L->r[i];

    L->r[i]=L->r[j];

    L->r[j]=temp;

}

 

 

//打印顺序表的元素

void print(SqList L)

{

    int i;

    for(i=1;i<L.length;i++)

        printf("%d,",L.r[i]);

    printf("%d",L.r[i]);        //此时i的值为上面for循环后得到的值

    //其实上面的代码可以写成for(i=1;i<=L.length;i++), 但这样的话, 最后一个打印的数字后还会有一个逗号

    printf("\n");

}

 

● 冒泡排序

交换排序的基本思想是两两比较待排序记录的关键码,如果发生逆序(排列顺序与排序后的次序相反),则交换之,直到所有记录都排好序为止。

交换排序的主要算法有: 1) 冒泡排序 2) 快速排序

//每趟不断将记录两两比较,并按"前小后大"(或"前大后小")规则交换。

//冒泡排序总原则: 大的往下沉, 小的往上浮

情况1: 最简单的交换排序(非标准的冒泡排序)

 

/* 对顺序表L作交换排序(冒泡排序初级版) */

void BubbleSort0(SqList *L)

{

    int i,j;

    for(i=1;i<L->length;i++)    //外循环

    {

        for(j=i+1;j<=L->length;j++)    //内循环

        {

            if(L->r[i]>L->r[j])

            {

                 swap(L,i,j);/* 交换L->r[i]L->r[j]的值 */

            }

        }

    }

}

//第一内循环, 第一内循环比较r[1]r[2], 此时, i=1, j=i+1=2,

                 第二次内循环比较r[1]r[3], 此时, i=1, j++=3, ...

第二轮内循环, 第一内循环比较r[2]r[3], 此时, i=2, j=i+1=3,

第二次内循环比较r[2]r[4], 此时, i=2, j++=4, ...

 

标准冒泡排序法

/* 对顺序表L作冒泡排序 */

void BubbleSort(SqList *L)

{

    int i,j;

    for(i=1;i<L->length;i++)

    {

        for(j=L->length-1;j>=i;j--) /* 注意j是从后往前循环 */

        {

            if(L->r[j]>L->r[j+1]) /* 若前者大于后者(注意这里与上一算法的差异), 则前者往下沉, 后者往上浮*/

            {

                 swap(L,j,j+1);/* 交换L->r[j]L->r[j+1]的值 */

            }

        }

    }

}

 

/* 对顺序表L作改进冒泡算法, 适合类似{2,1,3,4,5,6,7,8,9} 这种只需交换前面若干元素的情况*/

void BubbleSort2(SqList *L)

{

    int i,j;

    Status flag=TRUE;            /* flag用来作为标记 */

    for(i=1;i<L->length && flag;i++) /* flagTRUE, 说明发生过数据交换,否则停止循环 */

    {

        flag=FALSE; //初始flag为FALSE, 如果下面没有进行数据交换, 那么flag的值就不会变为TRUE, flag依然等于0, 此时退出循环

        for(j=L->length-1;j>=i;j--)

        {

            if(L->r[j]>L->r[j+1])

            {

                 swap(L,j,j+1);    /* 交换L->r[j]L->r[j+1]的值 */

                 flag=TRUE;        /* 如果有数据交换,则flagtrue */

            }    //这里须注意flag

        }

    }

}

 

选择排序

//每一次从待排序的数据元素中选出最小的一个元素,存放在已排序列的后面, 直到全部待排序的数据元素排完。

void SelectSort(SqList *L)

{

    int i,j,min;

    //i代表当前元素的下标, j代表任意一个元素的下标, min代表最小元素的下标

    for(i=1;i<L->length;i++)

    //第一次循环中, 先假设L.r[1]是顺序表中的最小元素

    {

        min = i;                        /* 假设当前元素的下标就是最小元素的下标 */

        for (j = i+1;j<=L->length;j++)

{

            if (L->r[min]>L->r[j])    //如果有一个元素小于当前最小值

min = j;            //那么将此关键字的下标赋值给min, 故在下次循环中, min的值是发生了变化的

}

        if(i!=min) //min不等于i(例如: 若第一轮循环中, 1!=m),说明找到真正的最小值

         //将这个真正的最小值的下标m与之前假设最小值的下标i进行交换

            swap(L,i,min);                /* 交换L->r[i]L->r[min]的值 */

    }

}

 

● 简单插入排序

插入排序: 每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。

插入排序有多种具体实现算法:1) 直接插入排序 2) 希尔排序

 

简单插入排序: 先将序列中第1个记录看成一个有序子序列,然后从第2个记录开始,逐个进行插入, 直至整个序列有序(类似小朋友排队).

 

/* 对顺序表L作直接插入排序 */

先将序列中第1个记录看成一个有序子序列,然后从第2个记录开始,逐个进行插入, 直至整个序列有序(类似小朋友排队).

void InsertSort(SqList *L)

{

    int i,j;    //i代表原始顺序表的下标, j代表有序子表的下标

    for(i=2;i<=L->length;i++)

    {

        if (L->r[i]<L->r[i-1]) /* 如果后面的元素r[i]小于前面的元素r[i-1], 则需将L->r[i]插入有序子表 */

        {

            L->r[0]=L->r[i]; /* 将待插入的元素放入"哨兵"位置 */

            for(j=i-1;L->r[j]>L->r[0];j--)

                L->r[j+1]=L->r[j]; /* 只要有序子表元素比哨兵大就不断后移 */

            //如果for()后面不加括号, 循环的只是for语句下面的第一条语句而已

            L->r[j+1]=L->r[0]; /* 插入到正确位置 */

        }

    }

}

 

堆排序

自堆顶至叶子的调整过程称为"筛选".

从一个无序序列建堆的过程就是一个反复"筛选"的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第[n/2]个元素,由此"筛选"只需从第n/2个元素开始.

/* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */

//s代表最后一个非终端节点, m代表节点的个数

void HeapAdjust(SqList *L,int s,int m)

{

    int temp,j;    //temp用来代表较大记录的下标

    temp=L->r[s];

    for(j=2*s;j<=m;j*=2) /* 沿关键字较大的孩子结点向下筛选; j*=2是为了检验孩子节点的孩子节点 */

    {

        if(j<m && L->r[j]<L->r[j+1])    //

            ++j; /* j为关键字中较大的记录的下标, 下一句执行的是 */

        //如果L.r[j]<L.r[j+1],则说明左孩子小于右孩子, 我们的目的是要找到较大值,当然需要让j+1以便使j变成指向右孩子的下标。

        if(temp>=L->r[j])

            break; /* r退出循环 */

        L->r[s]=L->r[j];

        s=j;    //因为下一步会执行j*=2, 所以要在这里把j赋值给s.

        //如果上面第一个if语句成立, 说明当前的父节点应该与其较大的孩子节点交换位置

    }

    L->r[s]=temp; //如果上面for循环里面的第一个if语句成立, 那么s=j;这一句就会执行, 然后最后一个非终端节点也就与它的一个较大的孩子节点交换; 如果的第二个if语句成立,那么s=j;这一句就不会执行, 然后最后一个非终端节点也就不会与它的一个较大的孩子节点交换;

}

 

/* 对顺序表L进行堆排序 */

void HeapSort(SqList *L)

{

    int i;

    for(i=L->length/2;i>0;i--) /* L中的r构建成一个大根堆, r是数组名 */

         HeapAdjust(L,i,L->length);

 

    for(i=L->length;i>1;i--)

    {

         swap(L,1,i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */

         HeapAdjust(L,1,i-1); /* L->r[1..i-1]重新调整为大根堆 */

    }

}

//第一个循环要完成的就是将现在的待排序序列构建成一个大顶堆。第二个循环要完成的就是逐步将每个最大值的根结点与末尾元素交换,并且再调整其成为大顶堆。

 

● 希尔排序:

希尔(Shell)排序的基本思想是:先取一个小于n的整数d1作为第一个增量把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取得第二个增量d2< d1重复上述的分组和排序,直至所取的增量di=1,即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。

一般取d1=n/2,di+1=di/2。如果结果为偶数,则加1,保证di为奇数。

希尔排序是不稳定的,希尔排序的执行时间依赖于增量序列,其平均时间复杂度为O(n^1.3).

#include<stdio.h>

void Shell(int A[],int n);

 

int main()

{

    int array[9]={50,10,90,30,70,40,80,60,20};

    Shell(array, 9);

    for(int i=0; i<9; i++)

        printf("%d ", array[i]);

}

 

 

void Shell(int A[],int n) //Shell排序

{

    int i,j,k,t;    //ij用来表示被分成一组的数组参数元素的左下标和右下标, k为增量, t用来暂时存放数组参数元素的值

    (n/2)%2 == 0 ? k = n/2+1 : k = n/2; //保证增量为奇数

    while(k > 0)

    {

        for(j=k;j<n; j++)

        {

            t = A[j];    //A[j]表示希尔排序中被分配成一组的右边的数, 将它的值暂时存放在变量t里面

            i = j - k;    //i是表示希尔排序中被分配成一组的左边的数的下标, 例如在第一次循环中, i=0

            while(i>=0 && A[i]>t) //A[i]>t的意思是如果左边的数大于右边的数

            {

                A[i+k]=A[i];    //如果while条件表达式成立, 那么将左边的数赋值给右边的数, 此时用A[i+k]表示A[j]

                i=i-k;

            }

            A[i+k]=t;    //上面i=i-k, 故此处i+k=i-k+k=i, 即将t暂存的右边的数赋值给左边的数

        }

        if(k == 1) break;    //k == 1意味着紧挨着的两个数组元素会分成一组, k==1是分组的终结条件

        (k/2)%2 ==0 ? k=k/2+1 : k=k/2;

    }

}

 

● 归并排序(递归法)

//可以把一个长度为n 的无序序列看成是 n 个长度为 1 的有序子序列 ,首先做两两归并,得到 n / 2 个长度为 2 的有序子序列 ;再做两两归并, …,如此重复,直到最后得到一个长度为 n 的有序序列。

 

先由"长"无序变成"短"有序,再由"短"有序归并为"长"有序。

#include<stdio.h>

#define MAXSIZE 10

//下面的MergeSort()函数是不断拆分的过程(所谓的"递"), Merging()函数是拆分后, 进行比较, 然后一层层地进行有序归并的过程(所谓的"归")

//实现合并, 并把最后的结果存放到list1

void Merging(int *list1, int list1_size, int *list2, int list2_size)

{

    int i, j, k, m;

    int temp[MAXSIZE];

 

    i=j=k=0;    //将这三个变量初始化为0

 

    while(i<list1_size && j<list2_size)

    {

        if(list1[i]<list2[j])    //i是拆分的数组的左边部分的下标, j是拆分的数组的右边部分的下标

        {

            temp[k++]=list1[i++];    //temp[k++]的意思是先取temp[k]的值, 再让k1, 即让temp数组的指针指向下一个位置

        }

        else

        {

            temp[k++]=list2[j++];

        }

    }

 

    while(i<list1_size)    //若分开的数组的左边部分有剩余(一般剩一个),直接拷贝出来粘到合并序列尾

    {

        temp[k++]=list1[i++];

    }

 

    while(j<list2_size) //若分开的数组的右边部分有剩余,直接拷贝出来粘到合并序列尾

    {

        temp[k++]=list2[j++];

    }

    //temp的所有内容放到list1里面

 

    for(m=0; m<(list1_size+list2_size);m++)

    {

        list1[m]=temp[m];

    }

}

 

void MergeSort(int k[], int n) //n代表数组的长度, 即待排序的元素的个数

{

    if(n>1) //设置递归结束的条件, 这样才能首先递进去, 然后归回来

    {

        int *list1=k;    //list1指分开的数组的左边部分; k是数组名, 相当于一个指针变量, 相当于数组的首地址

        int list1_size=n/2;

        int *list2=k+n/2;    //list2指拆分的数组的右边部分, 右边部分的首地址为左边部分的首地址加上左边部分的大小

        int list2_size=n-list1_size;

        

        MergeSort(list1, list1_size); //递归, 让刚才拆分的左半部分继续往下拆分

        MergeSort(list2, list2_size); //递归, 让刚才拆分的右半部分继续往下拆分

        

        Merging(list1, list1_size, list2, list2_size);    //递归完以后, 进行合并

    }

}

 

 

void main()

{

    int i, a[9]={50,10,90,30,70,40,80,60,20};

    MergeSort(a, 9);

 

    printf("排序后:");

    for(i=0; i<9; i++)

    {

        printf("%d ", a[i]);

    }

}

 

 

● 归并排序(迭代法)

//用迭代法的原因: 递归法耗用较多空间和时间, 虽然迭代法的设计相对困难, 但是效率更高.

/**

非递归归并排序思路: 构成n/2组长度为2的排序好的子数组段,然后再将他们排序成长度为4的子数组段......如此继续下去,直至整个数组排好序。

**/

 

#include <stdio.h>

#include <stdlib.h>

#define MAXSIZE 10

 

// 非递归实现-自底向上

// 将原数组划分为left[min...max] right[min...max]两部分

void MergeSort(int *list, int length)

{

int i, left_min, left_max, right_min, right_max, next;

int *tmp = (int*)malloc(sizeof(int) * length);

 

if (tmp == NULL)

{

fputs("Error: out of memory\n", stderr);

        //stderr: 标准输出(设备)文件,对应终端的屏幕

abort();

        //exit()是正常退出,abort()是异常退出,退出时会输出错误信息,然后再调用exit()退出

}

 

for (i = 1; i < length; i *= 2)

    {// i为步长,下面第一轮循环步长为1(一个元素跟一个元素对比),第二轮为2(两个元素跟两个元素对比),第三轮为4(四个元素跟四个元素对比),第四轮为8(八个元素跟八个元素对比)……

for (left_min = 0; left_min < length - i; left_min = right_max)

{

right_min = left_max = left_min + i;    //初始化各变量

right_max = left_max + i;

 

if (right_max > length)

right_max = length;        //right_max的最大值新否定为数组长度

 

next = 0;    //nexttmp这一临时数组的下标, 初始化为0

 

            while (left_min < left_max && right_min < right_max) //如果while后的条件表达式成立, 那么开始下面的比较

            {

                if(list[left_min]<list[right_min])

                {

                    tmp[next++]=list[left_min++];    //先将k[left]赋值给tmp[next], 然后nextleft_min都加1

                }

                else

                {

                    tmp[next++]=list[right_min++];

                }

            }

 

while (left_min < left_max)

list[--right_min] = list[--left_max];

 

while (next > 0)

list[--right_min] = tmp[--next];

}

}

free(tmp);

 

}

 

void main()

{

    int i, a[9]={50,10,90,30,70,40,80,60,20};

    MergeSort(a, 9);

 

    printf("排序后:");

    for(i=0; i<9; i++)

    {

        printf("%d ", a[i]);

    }

}

 

 

 

//主函数

#define N 9

int main()

{

int i;

 

/* int d[N]={9,1,5,8,3,7,4,6,2}; */

int d[N]={50,10,90,30,70,40,80,60,20};

/* int d[N]={9,8,7,6,5,4,3,2,1}; */

 

SqList l0,l1,l2,l3,l4,l5,l6,l7,l8,l9,l10;

 

for(i=0;i<N;i++)

l0.r[i+1]=d[i];

l0.length=N;

l1=l2=l3=l4=l5=l6=l7=l8=l9=l10=l0;

printf("排序前:\n");

print(l0);

 

printf("初级冒泡排序:\n");

BubbleSort0(&l0);

print(l0);

 

printf("冒泡排序:\n");

BubbleSort(&l1);

print(l1);

 

printf("改进冒泡排序:\n");

BubbleSort2(&l2);

print(l2);

 

printf("选择排序:\n");

SelectSort(&l3);

print(l3);

 

printf("直接插入排序:\n");

InsertSort(&l4);

print(l4);

      

printf("堆排序:\n");

HeapSort(&l6);

print(l6);

 

    return 0;

}

 

 

 

● 快速排序

(快速排序是一步跨几个台阶, 冒泡排序是一步跨一个台阶)

快速排序过程:

r[0……n-1]中的记录快速排序,设两个指针lowhigh,基准记录pivot=r[0],初始时令low=0+1,high=n-1

① 从high所指向的位置向前查找第一个关键字小于pivot的记录,找到后让这个较小的数字和pivot交换; 否则, high--, 即指针high向前移一位;

② 从low所指向的位置向后查找第一个关键字大于pivot的记录,找到后让这个较大的数字和pivot交换; 否则, low---, 即指针high向后移一位;

③ 重复上述两步,直至low等于high(当待排序列有偶数个元素), high等于low+1(当待排序列有奇数个元素); 于是pivot将代排序列分成了两个子序列;

④ 分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止.

#include <stdio.h>

 

void swap(int k[], int low, int high)    //low最初是数组k的起始位置, 并向右移动, high是结束位置, 并向左移动

{

    int temp;

 

    temp=k[low];

    k[low]=k[high];

    k[high]=temp;

}

 

int Partition(int k[], int low, int high)    

//Partition()函数内部做了选择pivot; ②将比pivot小的数置于其左边, 将比pivot大的数置于其右边; ③返回当完成第步以后pivot所在的位置

{

    int pivot;    //pivot是比较的基准

    

    pivot=k[low];    //使pivot等于第一个元素, 并且保持不变

 

    while(low<high)    

    //当退出这个外层的while循环以后, lowhigh指针或者已经重叠(当数组有偶数个元素), 或者相邻(当数组有奇数个元素)

    {

        while(low<high && k[high]>=pivot)

        {

            high--;    

        //首先比较右边,如果右边的数大于等于pivot, 那么指针high向左移, 然后继续循环, 如果右边的数小于pivot, 那么就执行下面的swap()函数

        }

        swap(k, low, high);

//每次pivot和右边的数完成一次交换以后, 我们就应该开始让左边的数和pivot进行对比

        while(low<high && k[low]<=pivot)    //然后比较左边

        {

            low++;    //指针low向右移

        }

        swap(k, low, high);

    }

    return low;    //返回pivot此时所在的数组中间点的位置, low的值

}

 

void QSort(int k[], int low, int high)

//void QSort这个函数内部在low<high的条件下, 循环执行调用Partition()函数, 以获得pivot的值和位置; ②利用第步获得的pivot的值和位置, 递归处理pivot左边较小的数和pivot右边较大的数

{

    int pivot;

    if(low<high)

    {

        pivot=Partition(k, low, high);

        QSort(k, low, pivot-1);        //递归调用基准点的左边

        QSort(k, pivot+1, high);    //递归调用基准点的右边

    }

}

 

void QuickSort(int k[], int n)    //对快速排序算法进行封装

//QuickSort()这个函数就只是做了调用QSort()这个函数的作用

{

    QSort(k, 0, n-1);    //k是数组名, 0是数组k的最小下标, n-1是最大下标

}

 

void main()

{

    int i, a[9]={50,10,90,30,70,40,80,60,20};

    QuickSort(a, 9);

 

    printf("排序后:");

    for(i=0; i<9; i++)

    {

        printf("%d ", a[i]);

    }

}

 

 

 

//快速排序优化方案①----三数取中(位数)(median-of-three)

int Partition(int k[], int low, int high)

{

    int pivot;

 

    int m=low+(high-low)/2;    //mlowhigh之间的下标

 

    if(k[low]>k[high])

    {

        swap(k, low, high);

    }

    if(k[m]>k[high])

    {

        swap(k, m, high);

    }

    if(k[m]>k[low])

    {

        swap(k, m, low);

    }

 

    point=k[low]

    //上面所有的语句是为了让k[low]里面存的是不大不小的值

    //后面的代码和上文的Partition()函数的后半部分相同

}

 

//优化方案删除不必要的排序

int Partition(int k[], int low, int high)

{

    //前面一部分是上面优化方案的代码

    while(low<high)    

    {

        while(low<high && k[high]>=pivot)

        {

            high--;    

        }

        k[low]=k[high]; //采用替换而不是交换的方式进行操作, 因而之前算法中用到的swap()函数也用不到了

        //注意, 这里不用考虑k[low]里面的数据被覆盖的问题, 因为本函数关注的是枢轴pivotwenti

        

        while(low<high && k[low]<=pivot)

        {

            low++;

        }

        k[high]=k[low];

    }

    k[low]=pivot; //将枢轴的值传给k[low]

    return low;    

}

 

优化方案②----小规模用直接插入排序, 大规模用快速排序

研究表明, 快速排序用于7个数的排序效率最高(也有人认为是50)

#define MAX_LENTGTH_INSERT_SORT 7

 

void ISort(int k[], int n)

{

    int i, j, temp;

    

    for(i=1; i<n; i++)

    {

        if(k[1]<k[i-1])

        {

            temp=k[i];

            

            for(j=i-1; k[j]>temp;j--)

            {

                k[j+1]=k[j];

            }

            k[j+1]=temp;

        }

    }

}

 

void InsertSort(int k[], int low, int high)    //该函数的作用是封装上面ISort()的函数

{

    Isort(k+low, high-low+1);

}

 

void QSort(int k[], int low, int high)

{

    int pivot;

    

    if(high-low> MAX_LENTGTH_INSERT_SORT)    //如果待排序的元素的数目大于7

    {

        pivot=Partition(k, low, high);

        QSort(k, low, pivot-1);    //处理始于low, 终于pivot-1的数组

        QSort(k, pivot-1, high);//处理始于pivot-1, 终于high的数组

    }

    else

    {

        InsertSort(k, low, high);

    }

}

 

快速排序优化方案③----尾递归优化

void QSort(int k[], int low, int high)

{

    int pivot;

    

    if(high-low> MAX_LENTGTH_INSERT_SORT)    //如果待排序的元素的数目大于7

    {

        while(low<high)

        {

            pivot=Partition(k, low, high);

            

            if(point-low<high-point)

            //如果左边的长度小于右边, 那么先对左边进行递归,

            //这一步执行完以后会再执行pivot=Partition(k, low, high);

            //这就相当于执行了QSort(k, pivot+1, high);

            {

                QSort(k, low, pivot-1);    

                low=pivot+1;

            }

            else

            //如果左边的长度大于右边, 那么先对右边进行递归,

            //这一步执行完以后会再执行pivot=Partition(k, low, high);

            //这就相当于执行了QSort(k, low, pivot-1);

            {

                QSort(k, pivot+1, high);

                high=point-1;

            }

        }

    }

    else

    {

        InsertSort(k, low, high);

    }

}

 

折半才插入排序:

折半查找法的基本条件是序列已经有序, 这时用折半查找将快于顺序查找.

从直接插入排序的流程可以看出, 每次都是在一个有序的序列中插入一个新的记录, 所以在这个有序序列中寻找插入位置, 就可以用折半查找的方法来进行.

void BInsertSort(SqList &L) {

// 对顺序表L作折半插入排序。

int i,j,high,low,m;

for (i=2; i<=L.length; ++i) {

L.r[0] = L.r[i]; // L.r[i]暂存到L.r[0]

low = 1; high = i-1;

while (low<=high) { // r[low..high]中折半查找有序插入的位置

m = (low+high)/2; // 折半

if (LT(L.r[0].key, L.r[m].key)) high = m-1; // 插入点在低半区

else low = m+1; // 插入点在高半区

}

for (j=i-1; j>=high+1; --j) L.r[j+1] = L.r[j]; // 记录后移

L.r[high+1] = L.r[0]; // 插入

}

} // BInsertSort

 

 

基数排序radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort.

基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。

具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616}

例如: 73, 22, 93, 43, 55, 14, 28, 65, 39, 81排序

 

第一步:

首先根据个位数的数值,在走访数值时将它们分配至编号09的桶子中:

0

1 81

2 22

3 73 93 43

4 14

5 55 65

6

7

8 28

9 39

第二步

 

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

81, 22, 73, 93, 43, 14, 55, 65, 28, 39

接着再进行一次分配,这次是根据十位数来分配:

0

1 14

2 22 28

3 39

4 43

5 55

6 65

7 73

8 81

9 93

第三步

 

接下来将这些桶子中的数值重新串接起来,成为以下的数列:

14, 22, 28, 39, 43, 55, 65, 73, 81, 93

这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。

radix_sort(a, n)的作用是对数组a进行排序。

1. 首先通过get_max(a)获取数组a中的最大值。获取最大值的目的是计算出数组a的最大指数。

 

2. 获取到数组a中的最大指数之后,再从指数1开始,根据位数对数组a中的元素进行排序。排序的时候采用了桶排序。

 

3. count_sort(a, n, exp)的作用是对数组a按照指数exp进行排序。

下面简单介绍一下对数组{53, 3, 542, 748, 14, 214, 154, 63, 616}按个位数进行排序的流程。

  1. 个位的数值范围是[0,10)。因此,参见桶数组buckets[],将数组按照个位数值添加到桶中。

(02) 接着是根据桶数组buckets[]来进行排序。假设将排序后的数组存在output[]中;找出output[]buckets[]之间的联系就可以对数据进行排序了。

/*

* 获取数组a中最大值

*

* 参数说明:

* a -- 数组

* n -- 数组长度

*/

int get_max(int a[], int n)

{

int i, max;

 

max = a[0];

for (i = 1; i < n; i++)

if (a[i] > max)

max = a[i];

return max;

}

 

/*

* 对数组按照"某个位数"进行排序(桶排序)

*

* 参数说明:

* a -- 数组

* n -- 数组长度

* exp -- 指数。对数组a按照该指数进行排序。

*

* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616}

* (01) exp=1表示按照"个位"对数组a进行排序

* (02) exp=10表示按照"十位"对数组a进行排序

* (03) exp=100表示按照"百位"对数组a进行排序

* ...

*/

void count_sort(int a[], int n, int exp)

{

int output[n]; // 存储"被排序数据"的临时数组

int i, buckets[10] = {0};

 

// 将数据出现的次数存储在buckets[]

for (i = 0; i < n; i++)

buckets[ (a[i]/exp)%10 ]++;

 

// 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。

for (i = 1; i < 10; i++)

buckets[i] += buckets[i - 1];

 

// 将数据存储到临时数组output[]

for (i = n - 1; i >= 0; i--)

{

output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];

buckets[ (a[i]/exp)%10 ]--;

}

 

// 将排序好的数据赋值给a[]

for (i = 0; i < n; i++)

a[i] = output[i];

}

 

/*

* 基数排序

*

* 参数说明:

* a -- 数组

* n -- 数组长度

 */

void radix_sort(int a[], int n)

{

int exp; // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10...

int max = get_max(a, n); // 数组a中的最大值

 

// 从个位开始,对数组a"指数"进行排序

for (exp = 1; max/exp > 0; exp *= 10)

count_sort(a, n, exp);

}

 

 

 posted on 2018-01-31 11:53  Arroz  阅读(217)  评论(0编辑  收藏  举报