十大排序算法


/***
 * @brief  十大排序算法
 * @author hyill
 * @date 2023.1.13
 * @details 冒泡排序(bubbleSort)
 *          选择排序(selectionSort
 *          插入排序(insertionSort)    
 *          希尔排序(shellSort)   
 *          归并排序(mergeSort)   
 *          快速排序(quickSort)   
 *          堆排序  (heapSort)   
 *          计数排序(countsort)   
 *          基数排序(radixsort)   
 *          桶排序  (bucket sort)   
 * 
 * 
***/

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


#define MAX_SIZE 15

typedef int elemType;




/****时间复杂度(time complexity) 
 * 在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,
 * 它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。
 * 不包括这个函数的低阶项和首项系数。使用这种方式时,
 * 时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。
 * time complexity 时间复杂度常用大O符号表述 n代表输入量
 * ****/



int savefile(elemType* list,int length, const char *file)
{
    FILE* fp = fopen(file, "wb");
    if(list==NULL) return -1;  
    if(fp==NULL) return -2;       
    fwrite(list,sizeof(elemType),length,fp);
    fclose(fp);return 0;
    
}
int loadfile(elemType* list,int length, const char *file)
{
    FILE* fp = fopen(file, "r");
    if(list==NULL) return -1;  
    if(fp==NULL) return -2;  
    int i=0;
    elemType value=0;
       while(fread(&value,sizeof(elemType),1,fp)&&i<length)
     {
       list[i]=value;i++;
     }  
    
    
}
void initRandArrary(int arr[],int length,int min,int max)
{
     for (int i = 0; i < length; i++) arr[i]= rand()%max;
}
void swap(elemType *a,elemType *b) 
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
void showArrary(elemType* arr,int length)
{
    //printf("----------------------------\n");
    if(arr && length>0)  {
    for (int i = 0; i < length; i++)
    printf(" %d",arr[i]);printf("\n");}
    printf("----------------------------\n");

}

elemType* reverse(elemType *arr ,int length)
{
    int start=0,end=length-1;
     do {swap(&arr[start],&arr[end]);}
     while(start++<end--);
    return arr;
}



/**** 冒泡排序
 * 冒泡排序是一个非常好理解的排序,顾名思义——冒泡。
 * 此时将要排序的数据从上至下排列,从最上面的数(第一个数据)开始对相邻的两个数据进行比较,
 * 较小的数据往上浮,较大的数据往下沉,达到排序的效果。
 * 这种如同水底的气泡逐步冒出水面一样,故称为冒泡法 或 起泡法。
 * 算法的时间复杂度为O(n^2)
*****/
elemType* bubbleSort(elemType* arr,int length,int desc)
{   if(!arr || length<1)return arr;    
    while (length--){ for (int i = 0; i < length; i++){   
    if((desc && arr[i]<arr[i+1])||(!desc && arr[i]>arr[i+1]))
    {swap(&arr[i],&arr[i+1]); }else{continue;} }} 
    return arr;    
}
/*** 选择排序(每次都选一个最小(大)元素,然后放到已排序的序列的末尾)
 * 1.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
 * ​2.再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
 * 3.重复第二步,直到所有元素均排序完毕。 
 * 无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。
 * 唯一的好处可能就是不占用额外的内存空间了吧。
 * ***/
elemType *selectionSort(elemType *arr, int length, int desc)
{
     if(!arr || length<1)return arr; 
    for (int i = 0; i < length; i++)  
    {int k=i;
    for (int j = i+1; j < length; j++)
    {           
    if((desc && arr[k]<arr[j])||(!desc && arr[k]>arr[j]))k=j;
    }  swap(&arr[i],&arr[k]);}
    return arr;
}
/*** 插入排序 (相当于打扑克起牌)
 * 1.从第一个元素开始,该元素可以认为已经被排序;
 * 2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
 * 3.如果该元素(已排序)大于新元素,将该元素移到下一位置;
 * 4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
 * 5.将新元素插入到该位置后;
 * 6.重复步骤2~5。
 * 算法的时间复杂度为O(n^2)
 * ***/
elemType *insertionSort(elemType *arr, int length, int desc)
{
    if(!arr || length<1)return arr;
    for (int i = 1; i < length; i++)
        for (int j = 0; j <i; j++)
        {
           if((desc && arr[j]<arr[i])||(!desc && arr[j]>arr[i]))
           {swap(&arr[j],&arr[i]);}
        }
     return arr; 
}
/*** 希尔插入  ***/
elemType *shellInsert(elemType *arr, int length, int desc,int increasement)
{ if(!arr || length<1)return arr;
   for (int i = 0; i < length; i++)
    for (int j=i+increasement ;  j < length &&((desc && arr[j-increasement]<arr[j])||(!desc && arr[j-increasement]>arr[j])); j+=increasement)
      swap(&arr[j],&arr[i]);
    return arr;
}
/*** 希尔排序
 * 希尔排序(Shell’s Sort)在插入排序算法的基础上进行了改进,
 * 算法的时间复杂度与前面几种算法相比有较大的改进,但希尔排序是非稳定排序算法
 * 1.选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
 * 2.按增量序列个数 k,对序列进行 k 趟排序;
 * 3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,
 * 分别对各子表进行直接插入排序。仅增量因子为 1 时,
 * 整个序列作为一个表来处理,表长度即为整个序列的长度。
 * 该算法时间复杂度为O(n log n)

 ***/
elemType* shellSort(elemType *arr, int length, int desc)
{   
    if(!arr || length<1)return arr;
    int increasement = length;    
    do { // 确定分组的增量
        increasement = increasement / 3+1;   
        shellInsert(arr,length,desc,increasement);  
    }  while (increasement >1) ;
     shellInsert(arr,length,desc,1);  
    return arr;
}
int  mergeSort_merge(elemType *arr,int left,int right,int middle,int desc)
{
    int* rel;
    int i = left;//i表示的是middle前面的数组的元素
    int j = middle+1;//j表示的是middle之后的数组的元素
    int k = 0;//k表示的是aux数组中的元素
    rel= (elemType*)calloc((right-left+1),sizeof(elemType));//为了开辟够足够的空间开存放数组各元素的内容
    for (k= left; k <= right; k++)
    {//先将两个子数组中的元素全部输入进aux数组中
        *(rel + k - left) = *(arr + k);
    }
    for (k = left; k <= right; k++)
    {
        if (i > middle)//左边没有数据,从右边取
        {*(arr + k) = *(rel + j - left);j++;} 
        else if (j > right)//右边没有数据,从左边取
        {*(arr + k) = *(rel + i - left);i++;}
        //else if (*(rel + i - left) > *(rel + j - left))//两边都有数据
        else if((desc && *(rel + i - left) < *(rel + j - left))||(!desc && *(rel + i - left) > *(rel + j - left)))
               {*(arr + k) = *(rel + j - left);j++;  }
        else { *(arr + k) = *(rel + i - left);i++;  }
    }
    free(rel);
    return 0;
}
int  mergeSort_split(elemType *arr,int left,int right, int desc)
{   int middle=0;
    if(left>=right) return 0;
    middle=(left+right)/2;
    mergeSort_split(arr,left,middle,desc);
    mergeSort_split(arr,middle+1,right,desc);
    return mergeSort_merge(arr,left,right,middle,desc);
    
}
/**** 归并排序 
 * 归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。
 * 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用
 * 1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
 * 2、设定两个指针,最初位置分别为两个已经排序序列的起始位置;
 * 3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
 * 4、重复步骤 3 直到某一指针达到序列尾;
 * 5、将另一序列剩下的所有元素直接复制到合并序列尾。
 * 该算法时间复杂度为O(n log n)
 * ****/
elemType *mergeSort(elemType *arr, int length, int desc)
{  if(!arr || length<1 )return arr; 
   mergeSort_split(arr,0,length-1,desc);
  return arr;    
}
int _quickSort(elemType *arr,int lefte,int righte,int desc)
{
    if( lefte>=righte )return 0;
    int i=lefte,j=righte;
    int tmp=arr[lefte];
      
     while(i<j)    
        {
            while (((desc && tmp<arr[i])||(!desc && tmp>arr[i]))&&(i<righte)) i++;
            while (((desc && tmp>arr[j])||(!desc && tmp<arr[j]))&&(j>=i)) j--;
            if(i<j)
            {swap(&arr[i],&arr[j]);
           //  showArrary(arr,ARR_MAX_SIZE);
            }
          }
          if((desc && tmp>arr[j])||(!desc && tmp>arr[j]))
          { swap(&arr[lefte],&arr[j]); 
          //showArrary(arr,ARR_MAX_SIZE);
          }
              
           _quickSort(arr,lefte,j-1,desc);
           _quickSort(arr,j+1,righte,desc);
       
    
}
/**** 快速排序 
 * 是对冒泡排序算法的一种改进
 * 快速排序是这五类中平均性能最优的排序算法,其中运用了分治的思想
 * 1、首先设定一个分界值,通过该分界值将数组分成左右两部分。
 * 2、将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。
 *    此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。
 * 3、然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,
 *      同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
 * 4、重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。
 *    当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
 * 时间复杂度也是O(nlog2n),空间复杂度为O(log2n)
 * ****/
elemType *quickSort(elemType *arr, int length, int desc)
{
    _quickSort(arr,0,length-1,desc);
    return arr;
}
void max_heapify(int *arr, int start, int end,int desc) {
    if(start>=end) return ;
    // 建立父節點指標和子節點指標
    /***
     * 父节点parent=(this-1)/2
     * 子节点son1 = 2*this+1
     * 子节点son2 = 2*this+2 
    ***/    
    int parent = start;
    int son = parent * 2 + 1;
    while (son <= end) { // 若子節點指標在範圍內才做比較
        if (son + 1 <= end )
        if ((!desc &&arr[son] < arr[son + 1])|| (desc &&arr[son] > arr[son + 1])) // 先比較兩個子節點大小,選擇最大的
            son++;
        if ((!desc&&arr[parent] > arr[son])||(desc&&arr[parent] < arr[son])) // 如果父節點大於子節點代表調整完畢,直接跳出函數
            return;
        else { // 否則交換父子內容再繼續子節點和孫節點比較
            swap(&arr[parent], &arr[son]);
            parent = son;
            son = parent * 2 + 1;
        }
    }
}


/** 堆积 (小顶堆/大顶堆)***/
void heapify(int *arr, int start, int end,int desc) {
    if(start>=end) return ; 
    int parent = start;
    int son = parent * 2 + 1;
    if (son + 1 <= end )
        if ((!desc &&arr[son] < arr[son + 1])|| (desc &&arr[son] > arr[son + 1]))son++; 
        if ((!desc&&arr[parent] > arr[son])||(desc&&arr[parent] < arr[son])) return;
        else { 
            swap(&arr[parent], &arr[son]);
            heapify(arr,son,end,desc);
        }
    
}
/*** 堆排序
 * 是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,
 * 并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
 * 1、堆积成一个大顶堆/小顶堆。
 * 2、将堆积好的大顶堆/小顶堆第一個元素和最后一个元素进行交换
 * 3、去掉最后一元素后
 * 4、重复步骤1~3,直到排序完畢。
 * ***/
elemType *heapSort(elemType *arr, int length, int desc)
{
 // 初始化,i從最後一個父節點開始調整 
 //lastNode=length-1,lastParent=(lastNode-1)/2后者 lastParent=length/2-1
    for (int i = length / 2 - 1; i >= 0; i--)
        heapify(arr, i, length - 1,desc);
    // 先將第一個元素和已经排好的元素前一位做交換,
    //再從新調整(刚调整的元素之前的元素),直到排序完畢
    for (int i = length - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);
        heapify(arr, 0, i - 1,desc);}
        return arr;
}
/*** 计数排序
 * 计数排序是一个非基于比较的排序算法,
 * 它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。
 * 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序
 * (基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)
 * 1、扫描整个集合S,对每一个Si∈S,找到在线性表L中小于等于Si的元素的个数T(Si);
 * 2、扫描整个线性表L,对L中的每一个元素Li,将Li放在输出线性表的第T(Li)个位置上。
 * ***/
elemType *countSort(elemType *arr, int length, int desc) 
{  int i,j,count,temp;
   if(!arr || length<1)return arr;
  elemType * rel= (elemType*)calloc(length,sizeof(elemType));
  for(i=0;i<length;i++)
    {
        count=0;
        for(j=0;j<length;j++)//扫描待排序数组
           //统计比arr[i]值小的值的个数
        if((desc && arr[j]>arr[i])||(!desc && arr[j]<arr[i]))count++;                
        while(rel[count]!=0)  count++;//对于相等非0的数据,应向后措一位。数据为0时,因数组rel被初始化为0,故不受影响。
        /* 注意此处应使用while循环进行判断,若用if条件则超过三个重复值后有0出现 */ 
        rel[count]=arr[i];//存放到rel中的对应位置
    }
    for(i=0;i<length;i++)//把排序完的数据复制到arr中
        arr[i]=rel[i];
    free(rel);//释放rel   
    return arr;
}
int getMax(elemType* arr,int length)
{
    int max=arr[0];
  for (int i = 1; i < length; i++)  
   if(arr[i]>max)max=arr[i];
   return max;
  
}

/// @brief 对数组按照"某个位数"进行排序(桶排序)
/// @param arr 数组
/// @param n 数组长度
/// @param exp 指数。对数组a按照该指数进行排序。
void _radixsort(int *arr, int length, int exp,int desc)
{
    /***
     * 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
     * (01) 当exp=1表示按照"个位"对数组a进行排序
     * (02) 当exp=10表示按照"十位"对数组a进行排序
     * (03) 当exp=100表示按照"百位"对数组a进行排序
     * ***/
    int output[length]; // 存储"被排序数据"的临时数组
    int i, buckets[10] = {0};
    // 将数据出现的次数存储在buckets[]中
    for (i = 0; i < length; i++) buckets[ (arr[i]/exp)%10 ]++;
    // 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
    if(desc)
    for (i = 8; i >=0; i--) buckets[i] += buckets[i + 1];
    else
    for (i = 1; i < 10; i++) buckets[i] += buckets[i - 1];
    // 将数据存储到临时数组output[]中
    for (i = length - 1; i >= 0; i--)
    {
        output[buckets[ (arr[i]/exp)%10 ] - 1] = arr[i];
        buckets[ (arr[i]/exp)%10 ]--;
    }
    // 将排序好的数据赋值给a[]
    for (i = 0; i < length; i++) arr[i] = output[i];
}
/*** 基数排序
 * 属于“分配式排序”(distribution sort),桶排序的扩展
 * 它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较
 * 其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,
 * 在某些时候,基数排序法的效率高于其它的稳定性排序法。
 * 1、统一为统一位数长度,接着从最低位开始,依次进行排序。
 * 2、按位排序进行排序。
 * ***/
elemType *radixsort(elemType *arr, int length, int desc)
{
    if(!arr || length<1)return arr;
    int exp;    // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
    int max = getMax(arr, length);    // 数组a中的最大值
     for (exp = 1; max/exp > 0; exp *= 10) _radixsort(arr,length,exp,desc);
    //if(desc) reverse(arr,length);
    return arr;
}
/*** 桶排序
 * 桶排序是鸽巢排序的一种归纳结果。
 * 当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))
 * 但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响
 * 1.设置一个定量的数组当作空桶;
 * 2.遍历输入数据,并且把数据一个一个放到对应的桶里去;
 * 3.对每个不是空的桶进行排序;
 * 4.从不是空的桶里把排好序的数据拼接起来。
 * ***/ 
int *bucketSort(elemType *arr, int length, int desc)
{   
    if(!arr || length<1)return arr;
    int len=getMax(arr,length)+1;
    elemType *p=arr;
    elemType *bucket=NULL;
    bucket= (elemType*)calloc(len,sizeof(elemType));
    for (size_t i = 0; i < length; i++)
    bucket[arr[i]]++;
    if(!desc)for (int i = 0; i < len; i++)while(bucket[i]>0){*p++=i;bucket[i]--;}
    if(desc) for (int i = len-1; i >=0; i--) while(bucket[i]>0) {*p++=i;bucket[i]--;}
  return arr;
} 
 



//#define SORT(arr,length,desc) bubbleSort(arr,length,desc) //冒泡排序
//#define SORT(arr,length,desc) selectionSort(arr,length,desc) //选择排序
//#define SORT(arr,length,desc) insertionSort(arr,length,desc) //插入排序
//#define SORT(arr,length,desc) shellSort(arr,length,desc) //希尔排序
//#define SORT(arr,length,desc) mergeSort(arr,length,desc) //归并排序
//#define SORT(arr,length,desc) quickSort(arr,length,desc) //快速排序
//#define SORT(arr,length,desc) heapSort(arr,length,desc) //堆排序
//#define SORT(arr,length,desc) countSort(arr,length,desc) //计数排序
//#define SORT(arr,length,desc) radixsort(arr,length,desc) //基数排序
#define SORT(arr,length,desc) bucketSort(arr,length,desc) //桶排序



/// @brief 十大排序算法 (测试函数)
/// @param argc 参数个数
/// @param argv 参数内容
/// @return 状态 0:表示成功
//int sortAlgorithmTest(int argc, char const *argv[])dsdf
int main(int argc, char const *argv[])
{
    int arr[MAX_SIZE]={0,915,719,0,777,769,492,421,0,27,38,100,0,15,64};
    //initRandArrary(arr,ARR_MAX_SIZE,0,RAND_RANG);
    //savefile(arr,ARR_MAX_SIZE,"./sort.data"); 
    //loadfile(arr,ARR_MAX_SIZE,"./sort.data");
    showArrary(arr,MAX_SIZE); 
    showArrary(SORT(arr,MAX_SIZE,0),MAX_SIZE); 
    showArrary(SORT(arr,MAX_SIZE,1),MAX_SIZE);
    showArrary(SORT(arr,MAX_SIZE,0),MAX_SIZE); 
    showArrary(SORT(arr,MAX_SIZE,1),MAX_SIZE);
    printf("\n\n");
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值