数据结构(排序)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jijerry/article/details/78640757

插入排序

实验简介

学习了前面一章的查找,我们知道二分查找需要先对数据进行排序,那么这章开始我们就来讲解一下几种经典的排序算法。

一、直接插入排序

首先我们来讲直接插入排序,它的做法是:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程,如下图所示。

直接插入排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 直接插入排序
 */
void InsertSort(int *array)
{
    int i, j;
    for (i = 2; i <= n; i++)
    {
        if (array[i] < array[i - 1])
        {
            array[0] = array[i];
            array[i] = array[i - 1];
            for (j = i - 2; array[0] < array[j]; j--)
            {
                array[j + 1] = array[j];
            }
            array[j + 1] = array[0];
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("请输入数据(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    InsertSort(array);
    printf("排序后为:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

二、希尔排序

希尔排序也是插入排序的一种,但它在效率上要比上面的直接插入排序高,它是对直接插入排序的改进,它的基本思想是先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2< d1重复上述的分组和排序,直至所取的增量dt=1(dt<…< d2< d1),即所有记录放在同一组中进行直接插入排序为止,增量序列尤为关键,一般的初次取序列的一半为增量,以后每次减半,直到增量为1,大致过程如下图所示。

希尔排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 希尔排序
 */
void ShellSort(int *array)
{
    int k = n / 2; //增量序列(仅作举例)
    while (k > 0)
    {
        int i, j;
        for (i = k + 1; i <=n; i++)
        {
            if (array[i] < array[i - k])
            {
                array[0] = array[i];
                for (j = i - k; j > 0 && array[0] < array[j]; j -= k)
                {
                    array[j + k] = array[j];
                }
                array[j + k] = array[0];
            }
        }
        k = k / 2;
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("请输入数据(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    ShellSort(array);
    printf("排序后为:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小结

这一章讲了插入排序中的两个经典算法,直接插入排序和希尔排序。直接插入排序的主要思想是每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序,它属于稳定的排序,最坏时间复杂性为O(n^2),空间复杂度为O(1)。希尔排序的主要思想是分组插入,它是不稳定的排序,它的时间复杂度跟增量序列有关,在取增量序列时要保证其中的值没有除1之外的公因子,并且最后一个增量值必为1



交换排序

介绍两种经典的交换排序——冒泡排序和快速排序。

一、冒泡排序

冒泡排序是一种交换排序,它的主要过程是:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。比较一趟之后,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 冒泡排序
 */
void BubbleSort(int *array)
{
    int i, j, temp;
    for (i = 0; i < n - 1; i++)
    {
        for (j = 0; j < n - 1 - i; j++)
        {
            if (array[j] > array[j + 1])
            {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("请输入数据(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    BubbleSort(array);
    printf("排序后为:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

二、快速排序

快速排序是对冒泡排序的改进,它的基本思想是通过一趟排序将数据分成两部分,一部分中的数据都比另一部分中的数据小,再对这两部分中的数据再排序,直到整个序列有序,如下图所示。

快速排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 分割使枢轴记录的左边元素比右边元素小
 */
int Partition(int *array, int low, int high)
{
    int pivotkey = array[low];
    array[0] = array[low];
    while (low < high)
    {
        while (low < high && array[high] >= pivotkey)
        {
            high--;
        }
        array[low] = array[high];
        while (low < high && array[low] <= pivotkey)
        {
            low++;
        }
        array[high] = array[low];
    }
    array[low] = array[0];
    return low;
}

/*
 * 快速排序递归实现
 */
void QuickSort(int *array, int low, int high)
{
    if (low < high)
    {
        int pivotloc = Partition(array, low, high);
        QuickSort(array, low, pivotloc - 1);
        QuickSort(array, pivotloc + 1, high);
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("请输入数据(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    QuickSort(array, 1, n);
    printf("排序后为:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小结

这一章讲了交换排序的两个经典算法,冒泡排序和快速排序。冒泡排序就像水中的气泡一样,小的数据往上浮,它是稳定的排序,它的时间复杂度是O(n^2)。快速排序是对冒泡排序的改进,它的主要思想是通过一趟排序将数据分成两部分,一部分中的数据都比另一部分中的数据小,再对这两部分中的数据再排序,直到整个序列有序,它是不稳定的排序,它的时间复杂度是O(nlogn)


选择排序

实验简介

选择排序中的两个经典算法:简单选择排序和堆排序。简单排序的思想是通过n-1次数据元素的比较,从n-i+1个记录中选择最小的数据,并与第i个数据进行交换,它的时间复杂度是O(n^2)。堆排序就是利用堆的特征来进行排序,它的时间复杂度是O(nlogn)。

一、简单选择排序

这一章我们来讲解选择排序,首先我们来讲解其中最简单的简单选择排序。

简单选择排序的基本思想是通过n-1次数据元素的比较,从n-i+1个记录中选择最小的数据,并与第i个数据进行交换,如下图所示。

简单选择排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 选择排序
 */
void SelectSort(int *array)
{
    int i, j, k, temp;
    for (i = 0; i < n; i++)
    {
        k = i;
        for (j = i + 1; j < n; j++)
        {
            if (array[j] < array[k])
            {
                k = j;
            }
        }
        if (k != i)
        {
            temp = array[i];
            array[i] = array[k];
            array[k] = temp;
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("请输入数据(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    SelectSort(array);
    printf("排序后为:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

二、堆排序

通过前面二叉树的学习,我们知道堆是完全二叉树,有最大堆和最小堆,其中最大堆是父结点的值比子结点大,相应的最小堆就是父结点的值比子节点小。

堆排序就是利用了最大堆(或最小堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字变得简单。以最大堆为例,它的基本思想就是:

  1. 先将初始文件R[1..n]建成一个最大堆,此堆为初始的无序区;
  2. 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key;
  3. 由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n1..n].keys,同样要将R[1..n-2]调整为堆; 重复此操作直到全部有序。

下面是示例图:

堆排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 生成堆
 */
void HeapAdjust(int *array, int s, int m)
{
    int i;
    array[0] = array[s];
    for (i = s * 2; i <= m; i *= 2)
    {
        if (i < m && array[i] < array[i + 1])
        {
            i++;
        }
        if (!(array[0] < array[i]))
        {
            break;
        }
        array[s] = array[i];
        s = i;
    }
    array[s] = array[0];
}

/*
 * 堆排序
 */
void HeapSort(int *array)
{
    int i;
    for (i = n / 2; i > 0; i--)
    {
        HeapAdjust(array, i, n);
    }
    for (i = n; i > 1; i--)
    {
        array[0] = array[1];
        array[1] = array[i];
        array[i] = array[0];
        HeapAdjust(array, 1, i - 1);
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * (n + 1));
    printf("请输入数据(用空格分隔):");
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &array[i]);
    }
    HeapSort(array);
    printf("排序后为:");
    for (i = 1; i <= n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小结

这一章讲解了选择排序中的两个经典算法,简单选择排序和堆排序,这两种都是不稳定的算法。简单排序的思想是通过n-1次数据元素的比较,从n-i+1个记录中选择最小的数据,并与第i个数据进行交换,它的时间复杂度是O(n^2)。堆排序就是利用堆的特征来进行排序,它的时间复杂度是O(nlogn),相比于快速排序来说,它最大的优点就是在最坏情况下的时间复杂度也为O(nlogn)。


归并排序和基数排序

实验简介

这章讲解两个经典排序算法,归并排序和基数排序。归并排序是建立在归并操作上的一种有效的排序算法,时间复杂度是O(nlogn)。基数排序不需要进行数据元素间的比较,时间复杂度为O(kn)。

一、归并排序

归并排序是建立在归并操作上的一种有效的排序算法,它过程为:比较a[i]和a[j]的大小,若a[i]≤a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素a[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元,如下图所示。

归并排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;

/*
 * 合并
 */
void Merge(int *source, int *target, int i, int m, int n)
{
    int j, k;
    for (j = m + 1, k = i; i <= m && j <= n; k++)
    {
        if (source[i] <= source[j])
        {
            target[k] = source[i++];
        }
        else
        {
            target[k] = source[j++];
        }
    }
    while (i <= m)
    {
        target[k++] = source[i++];
    }
    while (j <= n)
    {
        target[k++] = source[j++];
    }
}

/* 
 * 归并排序
 */
 void MergeSort(int *source, int *target, int s, int t)
 {
     int m, *temp;
     if (s == t)
     {
         target[s] = source[s];
     }
     else
     {
         temp = (int*) malloc(sizeof(int) * (t - s + 1));
         m = (s + t) / 2;
         MergeSort(source, temp, s, m);
         MergeSort(source, temp, m + 1, t);
         Merge(temp, target, s, m, t);
     }
 }

 int main()
 {
     int i;
    int *array;
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("请输入数据(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    MergeSort(array, array, 0, n - 1);
    printf("排序后为:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
 }

二、基数排序

基数排序是跟前面的几种排序算法完全不一样的排序算法,前面的排序算法主要通过关键字之间的比较和移动来实现,而基数排序不需要进行关键字之间的比较,它是借助多关键字的思想来实现的。对于数字,每一位上的数字就是一个关键字,每一位的数字范围就是关键字范围,它的主要过程为:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列,如下图所示。类似从低位到高位比较,就是从次关键字到主关键字比较,这种称为最低位优先(LSD),反之称为最高位优先(MSD)。

基数排序的代码实现:

#include <stdio.h>
#include <stdlib.h>

int n;    //元素个数
int bit_num;    //最大数字位数

/*
 * 获取相应位置上的数(从右到左)
 */
int GetNumInPos(int num, int pos)
{
    int i, temp = 1;
    for (i = 0; i < pos - 1; i++)
    {
        temp *= 10;
    }
    return (num / temp) % 10;
}

/*
 * 基数排序(LSD)
 */
void RadixSort(int *array)
{
    int radix = 10;
    int *count, *bucket, i, j, k;
    count = (int*) malloc(sizeof(int) * radix);
    bucket = (int*) malloc(sizeof(int) * n);
    for (k = 1; k <= bit_num; k++)
    {
        for (i = 0; i < radix; i++)
        {
            count[i] = 0;
        }
        //统计各个桶中所盛数据个数
        for (i = 0; i < n; i++)
        {
            count[GetNumInPos(array[i], k)]++;
        }
        //count[i]表示第i个桶的右边界索引
        for (i = 1; i < radix; i++)
        {
            count[i] = count[i] + count[i - 1];
        }
        //分配
        for (i = n - 1; i >= 0; i--)
        {
            j = GetNumInPos(array[i], k);
            bucket[count[j] - 1] = array[i];
            count[j]--;
        }
        //收集
        for (i = 0, j = 0; i < n; i++, j++)
        {
            array[i] = bucket[j];
        }
    }
}

int main()
{
    int i;
    int *array;
    printf("请输入最大数字的位数:");
    scanf("%d", &bit_num);
    printf("请输入数组的大小:");
    scanf("%d", &n);
    array = (int*) malloc(sizeof(int) * n);
    printf("请输入数据(用空格分隔):");
    for (i = 0; i < n; i++)
    {
        scanf("%d", &array[i]);
    }
    RadixSort(array);
    printf("排序后为:");
    for (i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n");
}

三、小结

这一章讲解了归并排序和基数排序,它们都是稳定的排序算法。归并排序是建立在归并操作上的一种有效的排序算法,它的时间复杂度是O(nlogn)。基数排序不需要进行数据元素间的比较,它是一种借助多关键字的思想对单逻辑关键字进行排序的方法,它分为最低位优先(LSD)和最高位优先(MSD),它的时间复杂度为O(kn)




猜你喜欢

转载自blog.csdn.net/jijerry/article/details/78640757