/***
* @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;
}
十大排序算法
最新推荐文章于 2024-03-19 17:12:27 发布