排序算法之我观

版权声明:未经允许,欢迎转载 https://blog.csdn.net/weixin_43798170/article/details/86592897

笔者今年是xmu大一新生
9月初学编程
学到泡排的时候就对排序这一块深入了解
(也只是很粗浅地学习了一下)
写这篇文章的初衷就是复习一下之前所学,有不足之处请不吝赐教
所谓排序
就是将杂乱无章的数据变得有规律
这其中有五花八门的算法,时间复杂度相同的算法不一而足
目前笔者只给读者展示几种基础算法
(冒泡排序,选择排序,插入排序,快速排序,基数排序,希尔排序,归并排序)
(之所以没有介绍堆排序的原因是笔者也不是很懂这方面,大一上还没学数据结构)

有低效但好用,高效但不好写之类的
1.冒泡排序(Bubble Sort)
相信大家对这个应该也不陌生吧
应该要熟到半分钟就能把模板打出来
具体运作过程如下:
比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
分析:
平均时间复杂度:两重循环:o(n^2)
稳定的算法
上代码(笔者目前只学一门c,IDE是cb)
图源:https://blog.csdn.net/qq_39741605/article/details/80821595
在这里插入图片描述

#include <stdio.h>
void swap(int *a, int *b); //交换两个数
int main()
{
    int n;
    printf("请输入要排序的数个数:");
    scanf("%d",&n);
 int   num[100000];
 int     i, j;
for(i=0;i<n;i++)
{
    scanf("%d",&num[i]);
}
for(i=0;i<n-1;i++)
{
    for(j=0;j<n-i-1;j++)        //i<n-1与j<n-i-1的由来可以由上图得到,这里不再叙述
    {
        if(num[j]>num[j+1])
        {
           swap(&num[j],&num[j+1]);
        }
    }
}
for(i=0;i<n;i++)
{
    printf("%d ",num[i]);
}
 return  0;
}
void swap(int *a, int *b)
{
 int    c;
  c = *a;
 *a = *b;
 *b =  c;
}

码风丑陋勿怪
泡排其实和选择排序有点像
都是在一轮排序后将最大或最小的元素“沉”到尾部或前排
//笔者注:若下标为1开头,怎么修改?
其实也简单:
改为:

for(i=1;i<n;i++)
{
    for(j=1;j<=n-i;j++)
    {
        if(num[j]>num[j+1])
        {
           swap(&num[j],&num[j+1]);
        }
    }
}

2.桶排序(Bucket sort)
说起来,这种排序方法时间复杂度不高(很低o(n)左右)
快到上天
一个桶代表一个数字,数组内数字应分配到与桶编码相等的桶中去。
如果一个数组中最大数不是很大的话,可以直接取数据范围的最大值
如果不是的话
(要先求出数组的最大值,因为数组的最大值是决定桶数量的重要依据)。
每个桶都有一个编号,编号从0开始。然后每个桶内装的是数组内等于该编号的元素的个数,初始值为0。接下来对数组内的数字进行检测,检测到该数字,对应编码的桶就+1。当检测完毕时,各个桶就完成了对数组内数字的统计。接下来,我们可以按照桶的编号从0开始一次输出桶的编号,根据桶内数字决定这个桶输出编号的次数。如此,输出的数据必是从小到大。
废话不说,上代码(我给的桶排不是空间复杂度优化过的)

#include <stdio.h>
#include <stdlib.h>
#include<memory.h>
int a[100];
int main()
{
    memset(a,0,sizeof(a));
    int n;
    printf("请输入要排序的数个数:");
    scanf("%d",&n);
    int i,b,j;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&b);
        a[b]++;
    }
    for(i=1;i<=100;i++)
    {
        for(j=0;j<a[i];j++)
        {
            printf("%d ",i);
        }
    }
    return 0;
}

代码很短,但是占用内存其实不小
适用范围很窄,但是在特定问题上可以把时间复杂度做到O(n)。
3.选择排序(Selection sort)
是一种简单直观的排序算法。
初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

#include <stdio.h>
#include <stdlib.h>
int n,a[100]={0};
void selective_sort(int a[],int n)
{
    int i,j,k;
    for(i=1;i<n;i++)
    {
        k=i;
        for(j=i+1;j<=n;j++)
        {
            if(a[j]<a[k])
            {
                k=j;
            }
            if(k!=i)
            {
                int temp;
                temp=a[i];
                a[i]=a[j];
                a[j]=temp;
            }
        }
    }
    for(i=1;i<=n;i++)
{
   printf("%d ",a[i]);
}
}
int main()
{
    printf("请输入要排序的数个数:");
scanf("%d",&n);
int i;
for(i=1;i<=n;i++)
{
    scanf("%d",&a[i]);
}
selective_sort(a,n);
    return 0;
}

分析:
时间复杂度:o(n^2);空间复杂度:o(1);
不稳定算法
4.希尔排序(Shell Sort)
是一种对插入排序的优化
平均时间复杂度:o(n^1.3);空间复杂度:o(1);
不稳定
主要思想:有点类似分治
将序列分割成许多元素个数相同的子序列,进行插入排序
最后对整个序列进行插入排序
由于在进行最后的排序之前,子序列已然保持很高的有序性,所以效率通常会比泡排,选择排序快上一些。
下面以十个元素为例:初始步长一般选择为数组长度一半 ,10/2=5
第一趟 分为5组将每一组中的元素按非递减次序排列
第二趟 分为5/2=2组,依次执行
最后一趟 整个数组进行插排

具体代码:

#include <stdio.h>
#include <stdlib.h>
int a[100];
void shell_sort(int array[], int length)
{
 int i,j,temp,gap; //gap是分组的步长
 for(gap=length/2; gap>=1; gap=gap/2)
        {
  for(i=gap+1;i<=length;i++)
  {       temp=array[i];
    for(j=i-gap;j>=1&&array[j]>temp;j-=gap)
            {array[j+gap]=array[j];}
           array[j+gap]=temp;
           int k;
                     for(k=1;k<=length;k++)
   {
      printf("%d ",array[k]);
   }
   printf("\n");
        }
           for(i=1;i<=length;i++)
   {
      printf("%d ",array[i]);
   }
   printf("\n");
        }
}
int main()
{
   int n;
   printf("请输入要排序的数个数:");
   scanf("%d",&n);
   int  i;
   for(i=1;i<=n;i++)
   {
       scanf("%d",&a[i]);
   }
   shell_sort(a,n);
    return 0;
}

5.插入排序(Insert Sort)
时间空间复杂度平均为o(n^2)
稳定
直接插入排序即是在要排序的数组中,假设前n-1(n>=2)个数已经是排好序的,现在要把第n个数插入到前n个已经排好序的数组中,使得这n个数也变成有序的,如此反复循环,使得要排序的数组中的最后一个元素也排好序,
我们可以先假设第一个数是排好序的,然后第二个数和第一个数进行比较,如果第二个数比第一个数大,那么说明前两个数排好序,无需做调整,如果第二个数比第一个数小,那么就把第一个数向后移,将第二个数放在第一个数的位置上,抽象出来就是用a[i]和a[i-1]进行比较,如果a[i]>a[i-1],那么就说明前i个数是已经排好序的,
如果a[i]<a[i-1],就让j=i-1,temp=a[i],然后一边将数据a[j]向后移动,一边向前搜索,直到找到a[j]<a[i]时停止,并将temp放到a[j+1]处即可。

void InsertSort(int arr[], int left, int right)
{
    int i, v;
    for (i = left; i <= right; i++) {
        v = arr[i];
        int l = left, r = i;
        int j;
        while (l < r) 
        {//在l与r之间插入排序,可以理解为解决子问题1→2→...→n
            int mid = (l + r) / 2;
            if (arr[mid] <= v)
                l = mid + 1;
            else
                r = mid;
        }
        for (j = i - 1; l <= j; j--)
            arr[j + 1] = arr[j];
        arr[l] = v;
    }
}
int main()
{  int n,i; 
 printf("请输入要排序的数个数:");  
 scanf("%d",&n);  
 for(i=1;i<=n;i++) 
  {      
  scanf("%d",&a[i]);
  } 
     InsertSort(a,1,n);  
     for(i=1;i<=n;i++)  
     {    
       printf("%d ",a[i]); 
     }   
         return 0;
  }
```

6.归并排序(Merge Sort)
用一张图来阐述原理
图源:https://blog.csdn.net/yushiyi6453/article/details/76407640
在这里插入图片描述
归并排序是分治思想的一个很好应用
将已有序的子序列合并,得到完全有序的序列;
即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为归并。

#include <stdio.h>
#include <stdlib.h>
int n,a[10000];
void merge(int a[],int left,int mid,int right)
{
    int i=left,j=mid+1,k=0,c[10000]={0};    
    while(i<=mid&&j<=right) 
    {     
       if(a[i]<=a[j])     
      c[k++]=a[i++];      
      else    
        c[k++]=a[j++]; 
    }    
    while(i<=mid) 
       {     
           c[k++]=a[i++];
      }
          while(j<=right)  
       {    
              c[k++]=a[j++];   
        } 
                  int f=left; 
              for(i=0;i<k;i++)
          {        
                  a[f+i]=c[i]; 
           } 
           }
       void MergeSort(int arr[], int left, int right)
       {
       if(left<right)
       {   
        int mid=(left+right)/2; 
           MergeSort(arr,left,mid);  
             MergeSort(arr,mid+1,right);    
             merge(arr,left,mid,right);
             }
             else
              return ;
       }

            int main()
            {
            printf("请输入要排序的数个数:");
            scanf("%d",&n);
            int i;for(i=1;i<=n;i++)
            {    scanf("%d",&a[i]);
            }
            MergeSort(a,1,n);
            for(i=1;i<=n;i++)
            {    
            printf("%d ",a[i]);
            }    
            return 0;
            }

7.快速排序(Quick Sort)(二十世纪十大算法、与傅里叶变换齐名)
这是笔者讲述的重点
此法由Tony Hoare在1959年发明(1961年公布),类似于归并,也有分治的一种思想,即找到一个基准数,将比它大的放在它的右边,比它小的放在它的左边.
问题来了
基准数要怎么取?
不清楚的话先选开头的数
之后有优化
先上传统快排

void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

void quickSort(int a[], int left, int right)
{
    if (left >= right)
        return;
    int i = left, j = right;
    while (i < j)
    {
        while (j > i && a[j] >= a[left])
            j--;
        while (i < j && a[i] <= a[left])
            i++;
        swap(a[i], (i == j) ? a[left] : a[j]);  //i和j相遇则与pivot(基准元素)交换,否则a[i]与a[j]交换
    }
    quickSort(a, left, i-1);
    quickSort(a, j+1, right);
}

但是我们会发现这样的快速排序遇到某些情况序列原本就有序、有大量重复元素等等)会进行很多完全不必要的操作,耗费大量时间。
例如,处理一个本来按非递增次序排列的数组,排序效率就退化为与冒泡排序一样o(n^2)

在这里插入图片描述这样的循环要进行N轮
优化可以从这几方面入手:
1.分割地愈细,快排的优势显现越不明显,可用插排替代之。
多少开始变换排序方式呢?
取10个元素为基准
2.遇到连续有序数字怎么处理?
随机化
pivot取rand()%(right-left+1)+left

可以在一定程度上防止最坏情况的出现
做到以上两点
快排基本达到极致
但还有一种情况
3.如果所有元素都是相同的呢?
笔者有幸拜读了某洛谷dalao写的一篇有关快排的题解,
大家可以去品味一下
https://www.luogu.org/problemnew/solution/P1177?page=7
笔者不才只学习了一个可以解决以上问题,较为简短的代码

#include <stdio.h>
#include <stdlib.h>
int n,a[1000000]={0};
void swap(int *a,int *b)
{
    int temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
void quicksort(int left,int right)
{
    int mid=a[(left+right)/2];
    int i=left,j=right;
    do
    {
      while(a[i]<mid)   //在左端找比基准数大的数
      {
          i++;
      }
     while(a[j]>mid)      //在右端找比基准数小的数
     {
         j--;
     }
     if(i<=j)       //找到了即为不满足要求,交换之
     {
         swap(&a[i],&a[j]);
         i++;
         j--;
    }
    }while(i<=j);
    if(left<j)quicksort(left,j);
    if(i<right)quicksort(i,right);
}
int main()
{
    printf("请输入要排序的数个数:");
scanf("%d",&n);
int i;
for(i=1;i<=n;i++)
{
    scanf("%d",&a[i]);
}
quicksort(1,n);
for(i=1;i<=n;i++)
{
    printf("%d ",a[i]);
}
    return 0;
}

以上就是七种排序方法,有不足之处请指正,笔者不胜感激。

猜你喜欢

转载自blog.csdn.net/weixin_43798170/article/details/86592897