七种机器内部排序的原理与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 + )
return ;
for (int i = , j = ; i<n; i++)
{
do
{
a[i] = (max - min + ) * rand() / (RAND_MAX + ) + ;
flag = ;
for (j = ; j < i; j++)
{
if (a[i] == a[j])
flag = ;
}
} while (flag);
}
} // 复制数组
void Copy_array(int *tar, int *arr, int len)
{
int i; for (i = ; i < len; i++)
tar[i] = arr[i];
} // 交换元素
void Swap_element(int *a, int *b)
{
int tmp = *a; *a = *b;
*b = tmp; Mobile_num += ; // 一次关键字交换计为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 = ; i < len; i++)
{
tmp = arr[i];
for (j = i - ; j >= && tmp < arr[j]; j--)
{
Swap_element(arr + j, arr + j + ); // tmp < arr[j],因此arr[j]向后移动
Comparisons_num++;
}
arr[j + ] = tmp;
}
}
// 希尔排序
void Shell_sort(int *arr, int len)
{
int i, j;
int d = len / ;
int lookouts; // 监视哨 while (d >= )
{
for (i = d; i < len; i++)
{
lookouts = arr[i];
for (j = i - d; j >= && 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 /= ;
}
}
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
先取一个len/2的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2=d1/2,重复上述的分组和排序,直至所取的增量 =1( < …<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
// 冒泡排序
void Bubble_sort(int *arr, int len)
{
int i, j;
int flag = ; // 标记循环过程是否进行过交换,如果为1则进行了交换 for (i = ; i < len && flag; i++)
{
flag = ;
for (j = ; j < len - i; j++)
{
if (arr[j - ] > arr[j])
{
Swap_element(arr + j, arr + j - );
flag = ;
}
Comparisons_num++;
}
}
}
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。(就好比大的气泡浮出了水面)
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
// 分隔(快速排序)
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 + ; if (left < right)
{
i = Division(arr, left, right);
Quick_sort(arr, left, i - , count_temp);
Quick_sort(arr, i + , right, count_temp);
}
}
- 设置两个变量left和right,排序开始的时候:left = 0, right = len - 1;
- 每次分割过程中,以第一个数组元素作为关键数据,赋值给base,即base = a[left];
- 从right开始向前搜索,即由后开始向前搜索(right--),找到第一个小于base的值a[right],将A[left]和A[right]互换;
- 接着从left开始向后搜索,即由前开始向后搜索(left++),找到第一个大于base的a[left],将A[left]和A[right]互换;
- 重复第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 = ; i < len - ; i++)
{
tmp = i;
for (j = i + ; 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]; * parent + < len; parent = child)
{
child = * parent + ; if (child < len - && arr[child + ] > 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 - ) / ; i >= ; i--)
Heap_adjust(arr, i, len); for (i = len - ; i > ; i--)
{
Swap_element(arr, arr + i); // 每次将最大的数排在最后
Heap_adjust(arr, , i); // 重新构成堆,将最大的数放在第一位
}
}
堆分为大根堆和小根堆,是完全二叉树。
堆排序的算法:
- 建堆,建堆是不断调整堆的过程,从len/2处开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(hlen/2) 其中h表示节点的深度,len/2表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
- 调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
- 堆排序:堆排序是利用上面的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 + , 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 = ; k <= mid - start; k++)
{
target[j + k] = arr[start + k];
Mobile_num++;
}
} if (i <= end)
{
for (k = ; 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 + ;
int * temp_arr = (int *)calloc(end + , sizeof(int)); if (start == end)
{
target[start] = arr[start];
Mobile_num++;
}
else
{
mid = (start + end) / ;
Merge_sort(arr, temp_arr, start, mid, count_temp);
Merge_sort(arr, temp_arr, mid + , end, count_temp);
Merge(temp_arr, target, start, mid, end, count_temp);
} if (count == )
{
free(temp_arr);
}
}
归并排序采用了分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
归并排序的算法通常用递归实现,先把待排序区间[s, e]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s, e]。
其算法如下:
- 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置
- 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
- 重复步骤3直到某一指针超出序列尾
- 将另一序列剩下的所有元素直接复制到合并序列尾
// 显示打印
// 正序输出
void Print_sort_positive(int *arr, int len)
{
int i; for (i = ; i < len; i++)
{
if (i % == && i != )
putchar('\n');
printf("%3d ", arr[i]);
}
putchar('\n');
} // 逆序输出
void Print_sort_negative(int *arr, int len)
{
int i; for (i = ; i < len; i++)
{
if (i % == && i != )
putchar('\n');
printf("%3d ", arr[len - i - ]);
}
putchar('\n');
} // 显示移动次数和比较次数
void Print_mob_com()
{
printf("移动次数:%d\n", Mobile_num);
printf("比较次数:%d\n\n", Comparisons_num);
// 初始化
Mobile_num = Comparisons_num = ;
}
最后是实现打印信息的函数:正序输出函数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, , ARRAY_LEN - , );
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, , ARRAY_LEN - , );
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 ;
}
编写测试程序的文件use_sort.c,并比较七种排序的结果,如下图:
其中移动和比较次数最多的排序方法为冒泡排序,而移动次数最少的则是快速排序,比较次数最少为希尔排序。。
七种机器内部排序的原理与C语言实现,并计算它们的比较次数与移动次数。的更多相关文章
- 七种常见经典排序算法总结(C++实现)
排序算法是非常常见也非常基础的算法,以至于大部分情况下它们都被集成到了语言的辅助库中.排序算法虽然已经可以很方便的使用,但是理解排序算法可以帮助我们找到解题的方向. 1. 冒泡排序 (Bubble S ...
- 七种常见经典排序算法总结(C++)
最近想复习下C++,很久没怎么用了,毕业时的一些经典排序算法也忘差不多了,所以刚好一起再学习一遍. 除了冒泡.插入.选择这几个复杂度O(n^2)的基本排序算法,希尔.归并.快速.堆排序,多多少少还有些 ...
- Java中8种常见的排序方法
排序方法的演示1)插入排序(直接插入排序.希尔排序)2)交换排序(冒泡排序.快速排序)3)选择排序(直接选择排序.堆排序)4)归并排序5)分配排序(基数排序)所需辅助空间最多:归并排序所需辅助空间最少 ...
- 七种经典排序算法及Java实现
排序算法稳定性表示两个值相同的元素在排序前后是否有位置变化.如果前后位置变化,则排序算法是不稳定的,否则是稳定的.稳定性的定义符合常理,两个值相同的元素无需再次交换位置,交换位置是做了一次无用功. 下 ...
- 数据结构(三) 用java实现七种排序算法。
很多时候,听别人在讨论快速排序,选择排序,冒泡排序等,都觉得很牛逼,心想,卧槽,排序也分那么多种,就觉得别人很牛逼呀,其实不然,当我们自己去了解学习后发现,并没有想象中那么难,今天就一起总结一下各种排 ...
- 模板化的七种排序算法,适用于T* vector<T>以及list<T>
最近在写一些数据结构以及算法相关的代码,比如常用排序算法以及具有启发能力的智能算法.为了能够让写下的代码下次还能够被复用,直接将代码编写成类模板成员函数的方式,之所以没有将这种方式改成更方便的函数模板 ...
- 基于python的七种经典排序算法
参考书目:<大话数据结构> 一.排序的基本概念和分类 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法. ...
- 深度分析Linux下双网卡绑定七种模式 多网卡的7种bond模式原理
http://blog.csdn.net/abc_ii/article/details/9991845多网卡的7种bond模式原理 Linux网卡绑定mode共有七种(~) bond0.bond1.b ...
- 七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)
写在前面: 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的随意序列,又一次排列成一个按keyword有序的序列.因此排序掌握各种排序算法很重要. 对以下介绍的各个排序,我们假定全部排 ...
随机推荐
- javase--反射
//书写规则 package cn.reflex; public interface PCI { public void open(); public void close(); } //调用方法 p ...
- Toad for Oracle
# 设置schema browser 多标签
- word
# word中向下箭头 原文:http://jingyan.baidu.com/article/c1465413b0a9c70bfcfc4cbc.html 使用替换功能,把^l(l是小写的L)替换为^ ...
- C++ 关键字浅谈
这里有一个游戏:要求写一个符合C++标准的程序,包含至少十个连续而且不同的关键字.连续是指不能被标识符.运算符.标点符号分割.注意这里的“不同”要求,别想用 int main() { return s ...
- 第二十二篇:在SOUI中使用代码向窗口中插入子窗口
使用SOUI开发客户端UI程序,通常也推荐使用XML代码来创建窗口,这样创建的窗口使用方便,当窗口大小改变时,内部的子窗口也更容易协同变化. 但是最近不断有网友咨询如何使用代码来创建SOUI子窗口,特 ...
- eclipse怎么打开工程文件的所在位置
首先得有eclipse 一.在eclipse的菜单栏中点击 Run -->External Tools -->External Tools Configurations.. 如 ...
- python学习5 常用三方模块
watchdog: 监控指定目录/文件的变化并自定义处理事件 例子
- 如何理解typedef void (*pfun)(void)
问题: 在刚接触typedef void (*pfun)(void) 这个结构的时候,存在疑惑,为什么typedef后只有一"块"东西,而不是两"块"东西呢?那 ...
- Sprint2团队贡献分
团队贡献分: 郭志豪:31% http://www.cnblogs.com/gzh13692021053/ 杨子健:22%http://www.cnblogs.com/yzj666/ 谭宇森:23% ...
- java基础-注释
注释是一种形式的元数据,提供了非程序自身的数据,注释对于被注释的代码没有直接的影响. 本文主要概括注释的使用,java平台(SE)预定义的注释,类型注释是如跟可插入类型系统连用达到更强的类型检查的,以 ...