七种机器内部排序的原理与C语言实现,并计算它们的比较次数与移动次数。

内部排序是指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列。

排序是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个相知有序的序列。排序分为两类:内排序和外排序。

其中快速排序的是目前排序方法中被认为是最好的方法。

内部排序方法:

1.插入排序(直接插入排序);

2.快速排序;

3.选择排序(简单选择排序);

4.归并排序;

5.冒泡排序;

6.希尔排序(希尔排序是对直接插入排序方法的改进);

7.堆排序;

——摘自百度百科

 

#ifndef SORT_H_
#define SORT_H_

#define ARRAY_LEN 1000        //    数组长度
#define MIN 1                 //    数组的最小值
#define MAX 1000              //    数组的最大值

int Comparisons_num;          //    比较次数
int Mobile_num;               //    移动次数

void Create_data(int *a, int n, int min, int max);                            //    建立伪随机
void Copy_array(int *tar, int *arr, int len);                                 //    复制数组
void Swap_element(int *a, int *b);                                            //    交换元素

void Insert_sort(int *arr, int len);                                          //    #1 直接插入排序
void Shell_sort(int *arr, int len);                                           //    #2 希尔排序
void Bubble_sort(int *arr, int len);                                          //    #3 冒泡排序
int Division(int *a, int left, int right);                                    //    分隔过程(快速排序)
void Quick_sort(int *arr, int left, int right, int count);                    //    #4 快速排序(left和count初始值为0,right初始值为数组长度 - 1)
void Select_sort(int *arr, int len);                                          //    #5 选择排序
void Heap_adjust(int arr[], int i, int len);                                  //    构成堆过程 (堆排序)
void Heap_sort(int arr[], int len);                                           //    #6 堆排序
void Merge(int arr[], int target[], int start, int mid, int end);             //    归并过程(归并排序)
void Merge_sort(int arr[], int target[], int start, int end, int count);      //    #7 归并排序(start和count初始值为0,end初始值为数组长度 - 1)

void Print_sort_positive(int *arr, int len);                                  //    正序输出
void Print_sort_negative(int *arr, int len);                                  //    逆序输出
void Print_mob_com();                                                         //    显示移动次数和比较次数

#endif                    

  在实现排序前,先定义函数的功能模块,即sort.h。

  头文件中定义了三个常量为ARRAY_LEN,MIN和MAX,代表数组的长度为1000,最大值为1000,最小值为1;建立伪随机函数Create_data()、复制数组函数Copy_array()和交换元素函数Swap_element(),这三个函数功能为初始化数组的元素;七种内部排序的函数与过程的定义,最后定义了数组的正序和逆序输出,以及打印排序过程中元素的移动次数和比较次数。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <conio.h>
#include <string.h>
//#pragma warning (disable:4996)

extern int Comparisons_num;       //    比较次数
extern int Mobile_num;            //    移动次数

//  建立伪随机数组
void Create_data(int *a, int n, int min, int max)
{
    int flag;                    //  避免取重复取值
    srand(time(NULL));

    if (n > max - min + 1)
        return 0;
    for (int i = 0, j = 0; i<n; i++)
    {
        do
        {
            a[i] = (max - min + 1) * rand() / (RAND_MAX + 1) + 1;
            flag = 0;
            for (j = 0; j < i; j++)
            {
                if (a[i] == a[j])
                    flag = 1;
            }
        } while (flag);
    }
}

//  复制数组
void Copy_array(int *tar, int *arr, int len)
{
    int i;

    for (i = 0; i < len; i++)
        tar[i] = arr[i];
}

//  交换元素
void Swap_element(int *a, int *b)
{
    int tmp = *a;

    *a = *b;
    *b = tmp;

    Mobile_num += 3;    //  一次关键字交换计为3次移动
}

  以上为文件sort.c中实现数组的初始化程序,函数Create_data()创建没有重复取值的数组,函数Copy_array()将数组arr全部内容复制到数组tar中,函数Swap_element()负责交换元素内容,每次调用移动次数Mobile_num将会增加三次。

  

//    直接插入排序
void Insert_sort(int *arr, int len)
{
    int i, j;
    int tmp;            //    待排序的元素

    for (i = 0; i < len; i++)
    {
        tmp = arr[i];
        for (j = i - 1; j >= 0 && tmp < arr[j]; j--)
        {
            Swap_element(arr + j, arr + j + 1);    // tmp < arr[j],因此arr[j]向后移动
            Comparisons_num++;
        }
        arr[j + 1] = tmp;
    }
}

  直接插入排序:按顺序每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。

  第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从后向前扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。

  直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。

  插入排序的基本方法是:每步将一个待排序的记录按其关键字的大小插到前面已经排序的序列中的适当位置,直到全部记录插入完毕为止。

//    希尔排序
void Shell_sort(int *arr, int len)
{
    int i, j;
    int d = len / 2;
    int lookouts;                //    监视哨

    while (d >= 1)
    {
        for (i = d; i < len; i++)
        {
            lookouts = arr[i];
            for (j = i - d; j >= 0 && lookouts < arr[j]; j = j - d)
            {
                Swap_element(arr + j + d, arr + j);
                Comparisons_num++;
            }
            if (arr[j + d] != lookouts)
            {
                Swap_element(arr + j + d, &lookouts);
                Comparisons_num++;
            }
        }
        d /= 2;
    }
}

  希尔排序是基于插入排序的以下两点性质而提出改进方法的,算法如下:

  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

  先取一个len/2的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2=d1/2,重复上述的分组和排序,直至所取的增量  =1(  <  …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

//    冒泡排序
void Bubble_sort(int *arr, int len)
{
    int i, j;
    int flag = 1;            //    标记循环过程是否进行过交换,如果为1则进行了交换    

    for (i = 0; i < len && flag; i++)
    {
        flag = 0;
        for (j = 1; j < len - i; j++)
        {
            if (arr[j - 1] > arr[j])
            {
                Swap_element(arr + j, arr + j - 1);
                flag = 1;
            }
            Comparisons_num++;
        }
    }
}

  冒泡排序的算法:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。(就好比大的气泡浮出了水面)
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
//  分隔(快速排序)
int Division(int *a, int left, int right)
{
    int base = a[left];
    while (left < right)
    {
        while (left < right && base < a[right])
        {
            right--;
            Comparisons_num++;
        }

        a[left] = a[right];
        Mobile_num++;

        while (left < right && a[left] < base)
        {
            left++;
            Comparisons_num++;
        }
        a[right] = a[left];
        Mobile_num++;
    }
    a[left] = base;
    return left;
}

//  快速排序
//  left 和 count初始值为0  right 初始值为数组长度 - 1
void Quick_sort(int *arr, int left, int right, int count)
{
    int i;
    int count_temp = count + 1;

    if (left < right)
    {
        i = Division(arr, left, right);
        Quick_sort(arr, left, i - 1, count_temp);
        Quick_sort(arr, i + 1, right, count_temp);
    }
}

  快速排序的算法:

  1. 设置两个变量left和right,排序开始的时候:left = 0, right = len - 1;
  2. 每次分割过程中,以第一个数组元素作为关键数据,赋值给base,即base = a[left];
  3. 从right开始向前搜索,即由后开始向前搜索(right--),找到第一个小于base的值a[right],将A[left]和A[right]互换;
  4. 接着从left开始向后搜索,即由前开始向后搜索(left++),找到第一个大于base的a[left],将A[left]和A[right]互换;
  5. 重复第3、4步,直到left >= right; (3,4步中,没找到符合条件的值,即3中a[right]不小于base,4中a[left]不大于base的时候改变left和right的值,使得right--,left++,直至找到为止。找到符合条件的值,进行交换的时候left和right指针位置不变。另外,left >= right这一过程一定正好是left++或right--完成的时候,此时令循环结束)。
//    选择排序
void Select_sort(int *arr, int len)
{
    int i, j;
    int tmp;  //  记录待排序元素的下标

    for (i = 0; i < len - 1; i++)
    {
        tmp = i;
        for (j = i + 1; j < len; j++)
        {
            if (arr[tmp] > arr[j])
                tmp = j;
            Comparisons_num++;
        }
        if (tmp != i)
            Swap_element(arr + tmp, arr + i);
    }
}

  直接选择排序的算法:

  程序采用双层嵌套循环,外循环按顺序每次选择一个待排序的元素,内循环每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

//    构成堆 (堆排序)
void Heap_adjust(int arr[], int parent, int len)
{
    int child;
    int temp;

    for (temp = arr[parent]; 2 * parent + 1 < len; parent = child)
    {
        child = 2 * parent + 1;

        if (child < len - 1 && arr[child + 1] > arr[child])
        {
            child++;
            Comparisons_num++;
        }

        Comparisons_num++;
        if (temp < arr[child])
        {
            Swap_element (arr + child, arr + parent);
        }
        else
            break;
    }
}
//    堆排序
void Heap_sort(int arr[], int len)
{
    int i;

    for (i = (len - 1) / 2; i >= 0; i--)
        Heap_adjust(arr, i, len);

    for (i = len - 1; i > 0; i--)
    {
        Swap_element(arr, arr + i);            //    每次将最大的数排在最后
        Heap_adjust(arr, 0, i);                //    重新构成堆,将最大的数放在第一位
    }
}

  堆分为大根堆和小根堆,是完全二叉树。

  堆排序的算法:

  1. 建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
  2. 调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
  3. 堆排序:堆排序是利用上面的1、2两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)
//    归并 (归并排序)
void Merge(int arr[], int target[], int start, int mid, int end)
{
    int i, j, k;

    for (i = mid + 1, j = start; start <= mid && i <= end; j++)
    {
        if (arr[start] < arr[i])
            target[j] = arr[start++];
        else
            target[j] = arr[i++];
        Mobile_num++;
        Comparisons_num++;
    }

    if (start <= mid)
    {
        for (k = 0; k <= mid - start; k++)
        {
            target[j + k] = arr[start + k];
            Mobile_num++;
        }
    }

    if (i <= end)
    {
        for (k = 0; k <= end - i; k++)
        {
            target[j + k] = arr[i + k];
            Mobile_num++;
        }
    }
}

//    归并排序
//    start 和 count初始值为0    end 初始值为数组长度 - 1
void Merge_sort(int arr[], int target[], int start, int end, int count)
{
    int mid;
    int count_temp = count + 1;
    int * temp_arr = (int *)calloc(end + 1, sizeof(int));

    if (start == end)
    {
        target[start] = arr[start];
        Mobile_num++;
    }
    else
    {
        mid = (start + end) / 2;
        Merge_sort(arr, temp_arr, start, mid, count_temp);
        Merge_sort(arr, temp_arr, mid + 1, end, count_temp);
        Merge(temp_arr, target, start, mid, end, count_temp);
    }

    if (count == 0)
    {
        free(temp_arr);
    }
}

  归并排序采用了分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

  归并排序的算法通常用递归实现,先把待排序区间[s, e]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s, e]。

  其算法如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针超出序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾
//    显示打印
//    正序输出
void Print_sort_positive(int *arr, int len)
{
    int i;

    for (i = 0; i < len; i++)
    {
        if (i % 10 == 0 && i != 0)
            putchar(‘\n‘);
        printf("%3d ", arr[i]);
    }
    putchar(‘\n‘);
}

//    逆序输出
void Print_sort_negative(int *arr, int len)
{
    int i;

    for (i = 0; i < len; i++)
    {
        if (i % 10 == 0 && i != 0)
            putchar(‘\n‘);
        printf("%3d ", arr[len - i - 1]);
    }
    putchar(‘\n‘);
}

//    显示移动次数和比较次数
void Print_mob_com()
{
    printf("移动次数:%d\n", Mobile_num);
    printf("比较次数:%d\n\n", Comparisons_num);
    //    初始化
    Mobile_num = Comparisons_num = 0;
}

  最后是实现打印信息的函数:正序输出函数Print_sort_positive()、逆序输出函数Print_sort_negative()和显示移动次数和比较次数函数Print_mob_com()。

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

int main(void)
{
    int arr[ARRAY_LEN];
    int temp_arr[ARRAY_LEN];
    int target_arr[ARRAY_LEN];

    Create_data(arr, ARRAY_LEN, MIN, MAX);
    Copy_array(target_arr, arr, ARRAY_LEN);

    printf("排序前: \n");
    Print_sort_positive(target_arr, ARRAY_LEN);
    Bubble_sort(target_arr, ARRAY_LEN);

    printf("\n完全正序: \n");
    Print_sort_positive(target_arr, ARRAY_LEN);

    printf("\n完全逆序: \n");
    Print_sort_negative(target_arr, ARRAY_LEN);

    //  开始进行七种排序比较
    Copy_array(target_arr, arr, ARRAY_LEN);
    Bubble_sort(target_arr, ARRAY_LEN);
    printf ("冒泡排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Quick_sort(target_arr, 0, ARRAY_LEN - 1, 0);
    printf ("快速排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Copy_array(temp_arr, arr, ARRAY_LEN);
    Merge_sort(temp_arr, target_arr, 0, ARRAY_LEN - 1, 0);
    printf ("归并排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Heap_sort(target_arr, ARRAY_LEN);
    printf ("堆排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Insert_sort(target_arr, ARRAY_LEN);
    printf ("直接插入排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Select_sort(target_arr, ARRAY_LEN);
    rintf ("选择排序:\n");
    Print_mob_com();

    Copy_array(target_arr, arr, ARRAY_LEN);
    Shell_sort(target_arr, ARRAY_LEN);
    printf ("希尔排序:\n");
    Print_mob_com();

    return 0;
}

  编写测试程序的文件use_sort.c,并比较七种排序的结果,如下图:

  其中移动和比较次数最多的排序方法为冒泡排序,而移动次数最少的则是快速排序,比较次数最少为希尔排序。。

时间: 2024-12-29 07:15:46

七种机器内部排序的原理与C语言实现,并计算它们的比较次数与移动次数。的相关文章

七种常见经典排序算法总结(C++)

最近想复习下C++,很久没怎么用了,毕业时的一些经典排序算法也忘差不多了,所以刚好一起再学习一遍. 除了冒泡.插入.选择这几个复杂度O(n^2)的基本排序算法,希尔.归并.快速.堆排序,多多少少还有些晦涩难懂,幸好又博客园大神dreamcatcher-cx都总结成了图解,一步步很详细,十分感谢. 而且就时间复杂度来说,这几种算法到底有什么区别呢,刚好做了下测试. 代码参考: http://yansu.org/2015/09/07/sort-algorithms.html //: basic_so

常见的9种内部排序(C语言实现)

现在已经把常见的9种内部排序算法都用C语言实现了,为了方便自己和大家查看,就弄了这么一个类似于导航目录的东西. 一.冒泡排序 冒泡排序(C语言版) 二.选择排序 选择排序(C语言版) 三.直接插入排序 直接插入排序(C语言版) 四.希尔排序 希尔排序(C语言版) 五.归并排序 归并排序(C语言版) 六.基数排序 基数排序(C语言版) 七.快速排序 快速排序(C语言版) 八.计数排序 计数排序(C语言版) 九.堆排序 堆排序(C语言版) 介绍完这九个常用的排序算法,怎么能没有一个比较呢?下面是我对

七种设计原则

七种设计原则 1.单一职责原则 单一职责原则(SRP:Single responsibility principle)又称单一功能原则 核心:解耦和增强内聚性(高内聚,低耦合). 描述: 类被修改的几率很大,因此应该专注于单一的功能.如果你把多个功能放在同一个类中, 功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题. 2.里氏替换原则 里氏替换原则(LSP:Liskov Substitution Principle) 核心: 在任何父类出现

七种经典排序算法最全攻略

经典排序算法在面试中占有很大的比重,也是基础.包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序.希望能帮助到有需要的同学.全部程序采用JAVA实现. 本篇博客所有排序实现均默认从小到大. 一.冒泡排序 BubbleSort 介绍: 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. 步骤: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对第0个到第n-1个数据做同样的工作.这时,最大的数就"浮"到了

八种排序算法(内部排序)

八种排序算法很长时间没有使用了,今天做一个总结,方便以后自己用的时候参考. 这八种排序算法都是内部算法,这八种排序算法分别是: 1. 插入排序 1)直接插入排序 2)希尔排序 2.选择排序 1)简单选择排序 2)堆排序 3.交换排序 1)冒泡排序 2)快速排序 4.归并排序 5.基数排序 一.直接插入排序 将一个记录插入到已经排好序的有序表中,从而得到一个新的.记录数增1的有序表.在实际操作中,先将序列的第一个记录看成是一个有序的子序列,然后从第二个.第三个.……记录逐个进行插入,直至整个序列有

数据结构6种内部排序算法的比较

1.需求分析 (1)输入数据的形式为:伪随机数产生程序产生,且每次输入数不少于100个,至少要用5组不同的输入数据 (2)输出的形式为:输出关键字参加的比较次数和关键字的移动次数(关键字交换计为3次移动)的数据 (3)程序能达到的功能:对起泡排序,直接插入排序,简单选择排序,快速排序,希尔排序,堆排序这6种常用的内部排序算法进行比较,比较的指标为有关键字参加的比较次数和关键字的移动次数(关键字交换计为3次移动) (4)测试数据:正确输入为由伪随机数产生程序产生100个随机数,然后输出比较结果,错

几种内部排序-分类-复杂性-稳定性

1. 简述 本文主要说明一些常用的内部排序算法的分类.复杂性和稳定性.主要基于现在的理解和学习,详细准确的复杂度可以参见维基百科等比较权威的网站,对于一些算法的不同实现,复杂度也不同,这里给出的复杂度都是相对较好的算法的复杂度. 2. 分类    3. 复杂性和稳定性 冒泡排序:在已经有序的情况,取得O(N)的复杂度.    快速排序:每次递归都是N的复杂度,递归次数根据序列有关系,当已经有序的情况下,递归N次,时间复杂度为O(N*LogN)    插入排序:在已经有序的情况,取得O(N)的复杂

模板化的七种排序算法,适用于T* vector&lt;T&gt;以及list&lt;T&gt;

最近在写一些数据结构以及算法相关的代码,比如常用排序算法以及具有启发能力的智能算法.为了能够让写下的代码下次还能够被复用,直接将代码编写成类模板成员函数的方式,之所以没有将这种方式改成更方便的函数模板纯属于偷懒,更方便于测试代码的有效性,等代码写完也懒得去改了.下面开始介绍这段代码,有什么不对的地方欢迎前来指正. 一共写了七种排序,插入排序InsertSort.堆排序HeapSort.快速排序QuickSort.合并排序MergeSort,计数排序CountingSort,基数排序RadixSo

七种常用排序算法

七种常用排序算法 一.常见排序算法一览: 时间复杂度: 是一个函数,它定量描述了该算法的运行时间. 空间复杂度:一个算法在运行过程中临时占用存储空间大小的量度. 稳定性:保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同就稳定,反之不稳定. 视觉直观感受 7 种常用的排序算法 二.算法C#实现: 1. 直接插入排序: using System; using System.Collections.Generic; using System.Linq; using Sys