C语言八大排序算法,一文带你弄清所有
判断各种排序算法的稳定性
常用(内部)排序算法
八大排序算法
因为我对选择排序的不稳定性有点不理解,所以我找了一篇相关的博客,希望能帮助到有相同困惑的朋友
理解选择排序的不稳定性
(如有错误请指正,谢谢)
#define MAXSIZE 100 /*参加排序元素的最大个数*/
typedef int KeyType;
typedef struct{
KeyType key; //按关键字排序
int judge; //判断排序的稳定性
}RedType;
检测某排序算法的稳定性,只需将原序列每个元素的判断关键字设置为0到n-1,待排序完成,比较排序关键字相同的元素的判断关键字的大小即可,索引小的元素,判断关键字也小,为稳定的,反之不稳定。
但是如果一个序列中没有相同元素,不能判断出排序算法的稳定性,但是如果没有相同元素,也就没有判断稳定不稳定的必要了。(为自己的菜找借口,哈哈哈哈哈)
bool Judge_Stability(RedType nums[],int n){
int i,j;
for(i=0;i<n;i++){
for(j=i+1;j<n;j++){
if(nums[i].key==nums[j].key){
if(nums[i].judge>nums[j].judge){
return 0;
}
}
}
}
return 1;
}
设置一个标志,当一趟下来没有数组元素交换时,则表示已有序,退出循环,减少不必要的操作。
void Bubble_Sort(){
int n, i, p;
int com_num=0, move_num=0, num=0;
int flag;
RedType nums[MAXSIZE];
printf("您已进入冒泡排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for(i=0;i<n;i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
for(p=n-1;p>=0;p--){
flag=0;
//每趟寻找一个最大值
for(i=0;i<p;i++){
com_num++;
if(nums[i].key>nums[i+1].key){
RedType temp=nums[i];
nums[i]=nums[i+1];
nums[i+1]=temp;
move_num++;
flag=1;
}
}
printf("第%d趟排序结果:",++num);
for(i=0;i<n;i++)
printf("%d ",nums[i].key);
printf("\n");
//如果某次比较无元素移动,则序列已经有序,退出循环
if(flag==0) break;
}
printf("排序过程比较次数:%d\n",com_num);
printf("排序过程移动次数:%d\n",move_num);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取冒泡排序后的结果是稳定的\n");
else printf("该序列采取冒泡排序后的结果是不稳定的\n");
printf("\n");
}
void Insertion_Sort(){
RedType nums[MAXSIZE];
int i, p, n;
RedType temp;
int com_num=0, move_num=0;
int num=0;
printf("您已进入直接插入排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for(i=0 ; i<n ; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
for(p=1;p<n;p++){
temp=nums[p];
com_num++;
//找到插入的位置
for(i=p;i>0&&nums[i-1].key>temp.key;i--){
nums[i]=nums[i-1];
move_num++;
com_num++;
}
nums[i]=temp;
printf("第%d趟排序结果:",++num);
for(i=0;i<n;i++){
printf("%d ", nums[i].key);
}
printf("\n");
}
printf("排序过程比较次数:%d\n",com_num);
printf("排序过程移动次数:%d\n",move_num);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取直接插入排序后的结果是稳定的\n");
else printf("该序列采取直接插入排序后的结果是不稳定的\n");
printf("\n");
}
定义一个临时变量,把每一趟中比较的较小值赋值到这个临时变量,在这趟结束之后,将临时变量的值赋值给此趟所选择的数,减少交换次数,提高效率
void Select_Sort(){
RedType nums[MAXSIZE];
int i, j, n, t;
RedType temp;
int com_num=0, move_num=0;
int num=0;
int flag;
printf("您已进入简单选择排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for(i=0 ; i<n ; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
for(i=0;i<n;i++){
temp = nums[i];
t = i;
flag=0;
for(j=i+1;j<n;j++){
com_num++;
if(temp.key>nums[j].key){
temp=nums[j];
t=j;
flag=1;
}
}
//如果存在比nums[i]小的值,才进行交换
if(flag==1){
temp=nums[i];
nums[i]=nums[t];
nums[t]=temp;
move_num++;
}
printf("第%d趟排序结果:",++num);
for(j=0 ; j<n ; j++){
printf("%d ", nums[j].key);
}
printf("\n");
}
printf("排序过程比较次数:%d\n",com_num);
printf("排序过程移动次数:%d\n",move_num);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取简单选择排序后的结果是稳定的\n");
else printf("该序列采取简单选择排序后的结果是不稳定的\n");
printf("\n");
}
利用折半查找的思想,找到一个元素应该在有序数组插入的位置
void Bisection_Sort(){
RedType nums[MAXSIZE];
int i, j, n;
RedType temp;
int low, high, middle;
int com_num=0, move_num=0;
int num=0;
printf("您已进入折半插入排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for(i=0;i<n;i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
for(i=1;i<n;i++){
low = 0; //有序数组的开头下标
high = i-1; //有序数组的末尾下标
temp = nums[i]; //要被插入的数
middle = 0;
//查找要被插入的下标
while(low <= high){
middle = (low + high) / 2;
if(temp.key < nums[middle].key) high = middle-1;
else low = middle+1;
com_num++;
}
//被插入的下标的数及右边所有数右移,移完之后插入目标数
for(j=i;j>low;j--){
nums[j]=nums[j-1];
move_num++;
}
//要被插入的下标与i不等时,将nums[i]的数据写入nums[low]的位置
if(low!=i) {
move_num++;
nums[low] = temp;
}
printf("第%d趟排序结果:",++num);
for(j=0 ; j<n ; j++){
printf("%d ",nums[j].key);
}
printf("\n");
}
printf("排序过程比较次数:%d\n",com_num);
printf("排序过程移动次数:%d\n",move_num);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取折半插入排序后的结果是稳定的\n");
else printf("该序列采取折半插入排序后的结果是不稳定的\n");
printf("\n");
}
void Shell_Sort(){
RedType nums[MAXSIZE];
int p, i, j, n, dk;
RedType temp;
int com_num=0, move_num=0;
int num=0;
printf("您已进入希尔排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for(i=0 ; i<n ; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
for(dk=n/2;dk>0;dk=dk/2){
for(j=0;j<dk;j++){
//单独的一次插入排序
for(p=j+dk;p<n;p=p+dk){
temp=nums[p];
com_num++;
for(i=p;i>=dk&&nums[i-dk].key>temp.key;i=i-dk){
nums[i]=nums[i-dk];
move_num++;
com_num++;
}
nums[i]=temp;
}
}
printf("第%d趟排序结果:",++num);
for(i=0;i<n;i++){
printf("%d ", nums[i].key);
}
printf("\n");
}
printf("排序过程比较次数:%d\n",com_num);
printf("排序过程移动次数:%d\n",move_num);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取希尔排序后的结果是稳定的\n");
else printf("该序列采取希尔排序后的结果是不稳定的\n");
printf("\n");
}
快速排序算法找基值是非常重要的,基值找的好,算法效率就高
该算法简单,但是如果数组已经有序或基本有序,算法效率较低
/***********快速排序***********/
/**下面三个全局变量,
用于快速排序统计比较次数、交换次数,
及第几次排序的计数*/
int CN,MN,T;
//交换数组中的两个元素
void Q_swap(RedType nums[], int i, int j){
RedType temp;
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
MN++;
}
//快速排序
void Q_Sort(RedType nums[], int left, int right, int n){
if(left>=right) return;
RedType pivot=nums[left];
int low=left;
int high=right;
//以pivot为轴,将大于它的放在后边,小于它的放在前面
while(low<high){
while(low<high&&nums[high].key>=pivot.key) {
CN++;
high--;
}
Q_swap(nums,low,high);
while(low<high&&nums[low].key<=pivot.key) {
CN++;
low++;
}
Q_swap(nums,low,high);
}
printf("第%d趟排序结果:",++T);
for(int i=0 ; i<n ; i++){
printf("%d ",nums[i].key);
}
printf("\n");
//对低子表递归排序
Q_Sort(nums,left,low-1,n);
//对高子表递归排序
Q_Sort(nums,low+1,right,n);
}
//快速排序的入口,用于主函数调用
void Quick_Sort(){
int i, n;
RedType nums[MAXSIZE];
CN = 0;
MN = 0;
T = 0;
printf("您已进入快速排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for (i=0; i<n; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
Q_Sort(nums, 0, n-1, n);
printf("排序过程比较次数:%d\n",CN);
printf("排序过程移动次数:%d\n",MN);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取快速排序后的结果是稳定的\n");
else printf("该序列采取快速排序后的结果是不稳定的\n");
printf("\n");
}
下面的程序寻找基值的方法为:选第一个元素、中间元素和最后一个元素的中位数作基值,可改善上述情况。并且设置一个值(比如100),当数组的长度小于这个值的时候,采用直接插入排序(因为当数组规模较小时,快速排序的性能不如直接插入排序)。
因为当数组规模小时,采用直接插入排序,所以稳定性是不确定的。
/**下面三个全局变量,
用于快速排序统计比较次数、交换次数,
及第几次排序的计数*/
int CN,MN,T;
//直接插入排序
void Insertion_Sort(RedType nums[],int n){
int i, p;
RedType temp;
for(p=1;p<n;p++){
temp=nums[p];
CN++;
for(i=p;i>0&&nums[i-1].key>temp.key;i--){
nums[i]=nums[i-1];
CN++;
MN++;
}
nums[i]=temp;
printf("第%d趟排序结果:",++T);
for(i=0 ; i<n ; i++){
printf("%d ",nums[i].key);
}
printf("\n");
}
}
//交换数组中的两个元素
void Q_swap(RedType nums[], int i, int j){
RedType temp;
temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
//取第一个元素、中间元素、最后一个元素的中位数作为主元,即基准Pivot
RedType median3(RedType nums[], int low, int high){
int center=(low+high)/2;
if(nums[low].key>nums[center].key) Q_swap(nums,low,center);
if(nums[low].key>nums[high].key) Q_swap(nums,low,high);
if(nums[center].key>nums[high].key) Q_swap(nums,center,high);
Q_swap(nums,center,high-1); //将基准pivot藏到右边
return nums[high-1];
}
//快速排序
void Q_Sort(RedType nums[], int left, int right){
if(right-left<100) {
Insertion_Sort(nums+left,right-left+1);
}
//如果序列元素充分多,进入快排
else{
int low,high;
RedType pivot;
pivot=median3(nums,left,right);
low=left; high=right-1;
//将序列中比基准小的移到基准左边,大的移到右边
while(1){
while(nums[++low].key<pivot.key);
while(nums[--high].key>pivot.key);
if(low<high) Q_swap(nums,low,high);
else break;
}
Q_swap(nums,low,right-1); //将基准换到正确的位置
printf("第%d趟排序结果:",++T);
for(int i=0 ; i<right-left+1 ; i++){
printf("%d ",nums[i].key);
}
printf("\n");
//对低子表递归排序
Q_Sort(nums,left,low-1);
//对高子表递归排序
Q_Sort(nums,low+1,right);
}
}
//快速排序的入口,用于主函数调用
void Quick_Sort(){
int i, n;
RedType nums[MAXSIZE];
CN = 0;
MN = 0;
T = 0;
printf("您已进入快速排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for (i=0; i<n; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
// Q_Sort(nums, 0, n-1);
Insertion_Sort(nums,n);
printf("排序过程比较次数:%d\n",CN);
printf("排序过程移动次数:%d\n",MN);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取快速排序后的结果是稳定的\n");
else printf("该序列采取快速排序后的结果是不稳定的\n");
printf("\n");
}
/************堆排序************/
//下滤:将堆中以某节点为根的子堆调整为最大堆
void PercDown(RedType nums[], int p, int n){
int parent,child;
RedType temp;
//将n个元素的数组中以nums[p]为根的子堆调整为最大堆
for(parent=p;parent*2+1<n;parent=child){
child=parent*2+1;
if(child<n-1&&nums[child].key<nums[child+1].key){
//Child指向左右子结点的较大者
child=child+1;
HCN++;
}
if(nums[parent].key<nums[child].key){
temp=nums[parent];
nums[parent]=nums[child];
nums[child]=temp;
HCN++;
HMN++;
}
else break;
}
}
//堆排序
void H_Sort(RedType nums[], int n){
int i,j;
RedType temp;
//建立一个大根堆
for(i=n/2-1;i>=0;i--){
PercDown(nums,i,n);
printf("第%d趟排序结果:",++H);
for(j=0 ; j<n ; j++){
printf("%d ",nums[j].key);
}
printf("\n");
}
//排序
for(i=n-1;i>0;i--){
//删除最大堆顶
temp = nums[i];
nums[i] = nums[0];
nums[0] = temp;
HMN++;
//调整大根堆
PercDown(nums,0,i);
printf("第%d趟排序结果:",++H);
for(j=0 ; j<n ; j++){
printf("%d ",nums[j].key);
}
printf("\n");
}
}
//堆排序的入口,用于主函数调用
void Heap_Sort(){
int i , n;
RedType nums[MAXSIZE];
HCN = 0;
HMN = 0;
H = 0;
printf("您已进入堆排序\n");
printf("请输入排序序列长度:");
scanf("%d",&n);
printf("请输入排序序列:");
for (i=0; i<n; i++){
scanf("%d",&nums[i].key);
nums[i].judge=i;
}
printf("\n");
H_Sort(nums, n);
printf("排序过程比较次数:%d\n",HCN);
printf("排序过程移动次数:%d\n",HMN);
//判断排序算法的稳定性
bool f=Judge_Stability(nums,n);
if(f) printf("该序列采取堆排序后的结果是稳定的\n");
else printf("该序列采取堆排序后的结果是不稳定的\n");
printf("\n");
}
//判断排序算法的稳定性
bool Judge_Stability(RedType nums[],int n){
int i,j;
for(i=0;i<n;i++){
for(j=i+1;j<n;j++){
if(nums[i].key==nums[j].key){
if(nums[i].judge>nums[j].judge){
return 0;
}
}
}
}
return 1;
}
当数组里面有重复元素时,冒泡排序、折半排序、直接插入排序是稳定的,希尔排序、简单选择排序、快速排序、堆排序是不稳定的。
当数组规模大小、有序性强弱不同时,每个排序算法的性能是不一样的。当规模较大时,快速排序和堆排序性能较好;当数组元素基本有序时,冒泡排序、希尔排序、插入排序性能较好;当数组规模很大,且只取前面几个元素时,堆排序是最好的选择。目前的内部排序算法中,综合考虑下,快速排序是性能最好的排序算法。