排序(二)

来源:互联网 发布:51秀啦网站源码 编辑:程序博客网 时间:2024/06/06 14:16

数据存储结构体,这里默认数据都是从下标1开始存储

const static int MAX = 1024;// 存储数据的机构体typedef struct{    int r[MAX];    int length;}SqList;

交换函数

// 交换两个数void swap(SqList *L, int i,int j){    int tmp = L->r[i];    L->r[i] = L->r[j];    L->r[j] = tmp;}

1.希尔排序

希尔排序其实就是插入排序的改进,插入排序可以看做每次比较的步长为1,而希尔排序设置一个初始的步长,步长迭代缩减,最后以1结束。希尔排序的复杂度主要和设置这个初始步长有关。当步长选择的比较差时复杂度就接近插入排序的复杂度。

这是插入排序的代码

void InsertSort(SqList *L){    int i,j;    for(i=2;i<=L->length;i++)// 从第二个数开始依次比较    {        if(L->r[i]<L->r[i-1])        {            L->[0] = L->r[i];// 将当前数记录在r[0]位置            // 循环和前边数比较,确定应该插入的位置,并将该位置后边的数都后移一位            for(j=i-1; L->r[j]>L->r[0];j--)                L->[j+1] = L->r[j];            L->r[j+1] = L->r[0];// 将该数插入对应位置        }    }}

希尔排序则需要更改初始的比较步长,并在外部加一个循环,用来迭代步长,使步长最终减小为1.
希尔排序

void ShellSort(SqList *L){    int i,j;    int step = L->length;    do{        step = step/3+1;        for(i=step+1;i<=L->length;i++)// 从第step+1个数开始依次比较        {            if(L->r[i]<L->r[i-step])// 比较当前数和它前step位置的数大小            {                L->[0] = L->r[i];// 将当前数记录在r[0]位置                // 循环和前边相隔step个数比较,确定应该插入的位置                for(j=i-step; j>0 && L->r[j]>L->r[0];j-=step)                    L->[j+step] = L->r[j];// 每次找到的比它大的数移动到后边位置                L->r[j+step] = L->r[0];// 将该数插入对应位置            }        }    }    while(step>1);}

步长选取是希尔排序的关键,希尔排序是不稳定的排序算法,而且必须保证最后一次循环步长以1结束。
最好时间复杂度O(n),最坏O(n^2)

2.堆排序

堆是具有下列性质的完全二叉树:

  • 每个节点的值都大于或等于其左右孩子的节点值,称为大顶堆;
  • 每个节点的值都小于等于其左右孩子的节点值,称为小顶堆;

大顶堆和小顶堆使用层序遍历按顺序存储在数组中。
堆排序:利用大顶堆排序,首先将待排序的序列构造成一个大顶堆。此时堆顶元素就为最大的,将堆顶元素和末尾元素交换,然后调整前n-1个数为大顶堆,再将堆顶元素交换到n-1位置,继续将前n-2个数构调整成大顶堆。如此反复执行便能得到一个有序序列。
算法实现时主要需要解决两个问题:

  • 由原始序列构建一个大顶堆
  • 输出堆顶元素后,调整剩余元素为一个大顶堆

堆排序的代码如下

// 堆排序void HeapSort(SqList *L){    int i = 0;    // 由初始序列构建大顶堆,从非叶子节点开始,从下到上调整每个非叶子结点    for (i = L->length / 2; i > 0; i--)    {        HeapAdjust(L, i, L->length);    }    // 依次取出堆顶元素调整大顶堆    for (i = L->length; i > 1; i--)    {        swap(L, 1, i);      // 将堆顶元素换到末尾        HeapAdjust(L, 1, i - 1);    // 将堆的元素个数进行-1,并将新交换的根节点重新调整堆为大顶堆    }}

这里两个for循环,第一个是由初始给的数组序列构建一个大顶堆,第二个是每次讲大顶堆堆顶元素和末尾元素交换,并调整剩余元素为一个大顶堆。
其中void HeapAdjust(SqList *L, int s,int length)函数是初始假设L中除s节点外都满足大顶堆条件,然后从s节点开始调整为一个合法的大顶堆,将s节点与左右孩子进行比较,如果孩子比节点s值大,则将较大的孩子和s交换位置,继续判断交换后的s节点和他的子节点,代码如下

// 调整大顶堆void HeapAdjust(SqList *L, int s,int length){    // 初始假设L中除s节点外都满足大顶堆条件    int i = 0;              int tmp = L->r[s];    for (i = 2 * s; i < length;i*=2)    {        // 判断i是否出界而且判断左右孩子哪个比较大,让i指向大的孩子        if (i < length && L->r[i] < L->r[i + 1])            i++;        // 如果子结点的最大值小于需要判断的初始节点值,则表示初始节点放在该位置满足大顶堆条件,直接跳出        if (L->r[i] < tmp)            break;        // 将较大孩子的值赋给父节点        L->r[s] = L->r[i];        // 继续判断当前改动的节点对于他的孩子是否满足大顶堆条件,即记录下当前改动的节点        s = i;    }    // 将初始节点值放到对应位置    L->r[s] = tmp;}

复杂度分析
运行时间主要消耗在初始简历大顶堆和后边调整大顶堆上,总体的时间复杂度为O(nlogn),最坏O(nlogn),最好O(nlogn)。
稳定性:由于记录的比较与交换是跳跃式的,所以堆排序是一种不稳定的排序算法。
由于初始时建堆时需要的比较次数较多,所以不适合待排序列个数较少的情况。

3.归并排序

时间复杂度:平均:O(nlogn),最好:O(nlogn),最坏:O(nlogn)
稳定性:稳定
归并排序原理:假设有n个数的序列需要排序,首先进行两两比较排序,然后将相邻的两两子序列进行归并排序,一直归并到n/2子列,然后对1-n/2子列和n/2+1到length子列进行归并,得到有序数列n,这种排序方法称为2路归并排序。
归并排序的递归实现

// 合并TR2中两个子序列s-m和m+1-t到数组TR1中void Merge(int TR2[], int TR1[], int s, int m, int t){    int i, j, k;    for (i = s, j = m + 1,k=s; i <= m&&j <= t;k++)    {        if (TR2[i]>TR2[j])        {            TR1[k] = TR2[j];            j++;        }        else        {            TR1[k] = TR2[i];            i++;        }    }    if (i <= m)    {        for (int l = i; l <= m; l++,k++)        {            TR1[k] = TR2[l];            //将剩余的i-m复制到TR1中        }    }    if (j <= t)    {        for (int l = j; l <= t; l++, k++)        {            TR1[k] = TR2[l];            // 将剩余的m+1 -- t复制到TR1中        }    }}//SR数组归并排序为TR1数组void Msort(int SR[], int TR1[], int s, int t){    int TR2[MAX];    if (s == t)        TR1[s] = SR[s];    else    {        int m = (s + t) / 2;        // 将SR[s...t]平分为SR[s...m]和SR[m+1...t]        Msort(SR, TR2, s, m);       // 递归对SR[s...m]进行归并排序为TR2        Msort(SR, TR2, m + 1, t);   // 递归对SR[m+1...t]进行归并排序为TR2        Merge(TR2, TR1, s, m, t);   // 实际的归并排序 将TR2中s到m和m+1到t归并排序为TR1    }}// 归并排序void MergeSort(SqList *L){    Msort(L->r, L->r, 1, L->length);}

非递归实现

// 将数组SR中长度为k的两个相邻子列进行归并放入TR数组中void MergePass(int SR[], int TR[], int k, int length){    int i = 1;    while (i <= length - (2 * k)+1)    {        Merge(SR, TR, i, i + k - 1, i + 2 * k - 1);        i = i + 2 * k;    }    // 归并最后两个序列    if (i < length - k + 1)    {        Merge(SR, TR, i, i + k - 1, length);    }    else    {        // 剩下单个子序列        for (int j = i; j <= length; j++)        {            TR[j] = SR[j];        }    }}// 归并排序非递归实现void MergeSort2(SqList *L){    int *TR = (int *)malloc(sizeof(int)*(L->length + 1)); // 申请一个辅助空间    int k = 1;    while (k < L->length)    {        MergePass(L->r, TR, k, L->length);        k = k * 2;        MergePass(TR, L->r, k, L->length);        k = k * 2;    }}

4.快速排序

思想:快速排序通过一趟排序将数列划分为两个部分,前边的都比选中的关键字小,后边的都比选中的关键字大,然后继续对前后两部分分别进行排序,以达到整个序列有序。
递归实现代码如下

// 将L-r中low到high划分为两部分,并返回划分点的位置int Partition(SqList *L, int low, int high){    int pivotKey = L->r[low];           // 用子序列的第一个作为划分点    // 进行划分:这里使用两端交替向中间划分    while (low < high)    {        while (low < high && pivotKey <= L->r[high])            high--;        swap(L, low, high);        while (low < high && pivotKey >= L->r[low])            low++;        swap(L, low, high);    }    return high;}// 对表中序列r[low-high]进行快速排序void Qsort(SqList *L, int low, int high){    int pivot;    if (low < high)    {        pivot = Partition(L, low, high);    // 将L->r[low-high]一分为二,前边都比L->r[pivot]小,后边都比这个大         Qsort(L, low, pivot - 1);           // 对low - pivot-1这部分继续进行递归划分        Qsort(L, pivot + 1, high);          // 进行递归划分    }}// 快速排序void QuickSort(SqList *L){    Qsort(L, 1, L->length);}

最坏情况下复杂度为O(n^2),平均时间复杂度为O(nlogn),最好O(nlogn)
稳定性:不稳定